[PATCH] riched20: implement ITextRange/ITextSelection Copy and Cut

Damjan Jovanovic damjan.jov at gmail.com
Wed Oct 14 14:17:24 CDT 2020


Clipboard handling is unified with the previous code in editor.c.
Manual tests show trying to cut from a read-only field beeps when
using WM_CUT or Ctrl+X, but not when using these APIs.

Signed-off-by: Damjan Jovanovic <damjan.jov at gmail.com>
---
 dlls/riched20/caret.c         |  2 +-
 dlls/riched20/editor.c        | 51 +++++++++++++++---------
 dlls/riched20/editor.h        |  3 +-
 dlls/riched20/richole.c       | 46 ++++++++++++++++++----
 dlls/riched20/tests/richole.c | 74 +++++++++++++++++++++++++++++++++++
 5 files changed, 148 insertions(+), 28 deletions(-)
-------------- next part --------------
diff --git a/dlls/riched20/caret.c b/dlls/riched20/caret.c
index e9ea64ed1bc..7c4d0d68217 100644
--- a/dlls/riched20/caret.c
+++ b/dlls/riched20/caret.c
@@ -312,7 +312,7 @@ void update_caret(ME_TextEditor *editor)
     hide_caret(editor);
 }
 
-BOOL ME_InternalDeleteText(ME_TextEditor *editor, ME_Cursor *start,
+BOOL ME_InternalDeleteText(ME_TextEditor *editor, const ME_Cursor *start,
                            int nChars, BOOL bForce)
 {
   ME_Cursor c = *start;
diff --git a/dlls/riched20/editor.c b/dlls/riched20/editor.c
index e7e33c27585..7752d540c8f 100644
--- a/dlls/riched20/editor.c
+++ b/dlls/riched20/editor.c
@@ -2331,13 +2331,13 @@ done:
     return hr == S_OK;
 }
 
-static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
+static HRESULT ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars, VARIANT *v)
 {
   LPDATAOBJECT dataObj = NULL;
   HRESULT hr = S_OK;
 
   if (editor->cPasswordMask)
-    return FALSE; /* Copying or Cutting masked text isn't allowed */
+    return E_ACCESSDENIED; /* Copying or Cutting masked text isn't allowed */
 
   if(editor->lpOleCallback)
   {
@@ -2349,34 +2349,49 @@ static BOOL ME_Copy(ME_TextEditor *editor, const ME_Cursor *start, int nChars)
   if(FAILED(hr) || !dataObj)
     hr = ME_GetDataObject(editor, start, nChars, &dataObj);
   if(SUCCEEDED(hr)) {
-    hr = OleSetClipboard(dataObj);
+    IUnknown **ppUnknown = NULL;
+    if (v != NULL && V_VT(v) == (VT_UNKNOWN | VT_BYREF))
+      ppUnknown = V_UNKNOWNREF(v);
+    if (ppUnknown != NULL)
+      hr = IDataObject_QueryInterface(dataObj, &IID_IUnknown, (void**)ppUnknown);
+    else
+      hr = OleSetClipboard(dataObj);
     IDataObject_Release(dataObj);
   }
-  return SUCCEEDED(hr);
+  return hr;
 }
 
-static BOOL copy_or_cut(ME_TextEditor *editor, BOOL cut)
+HRESULT ME_CopyOrCut(ME_TextEditor *editor, BOOL cut, BOOL silent, const ME_Cursor *start, int nChars, VARIANT *v)
 {
-    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;
+        if (!silent)
+            MessageBeep(MB_ICONERROR);
+        return E_ACCESSDENIED;
     }
 
-    num_chars -= offs;
-    result = ME_Copy(editor, sel_start, num_chars);
-    if (result && cut)
+    hr = ME_Copy(editor, start, nChars, v);
+    if (SUCCEEDED(hr) && cut)
     {
-        ME_InternalDeleteText(editor, sel_start, num_chars, FALSE);
+        ME_InternalDeleteText(editor, start, nChars, FALSE);
         ME_CommitUndo(editor);
         ME_UpdateRepaint(editor, TRUE);
     }
-    return result;
+    return hr;
+}
+
+static BOOL wm_copy_or_cut(ME_TextEditor *editor, BOOL cut)
+{
+    HRESULT result;
+    int offs, num_chars;
+    int start_cursor = ME_GetSelectionOfs(editor, &offs, &num_chars);
+    ME_Cursor *sel_start = &editor->pCursors[start_cursor];
+
+    num_chars -= offs;
+    result = ME_CopyOrCut(editor, cut, FALSE, sel_start, num_chars, NULL);
+    return SUCCEEDED(result);
 }
 
 /* helper to send a msg filter notification */
@@ -2695,7 +2710,7 @@ ME_KeyDown(ME_TextEditor *editor, WORD nKey)
     case 'C':
     case 'X':
       if (ctrl_is_down)
-        return copy_or_cut(editor, nKey == 'X');
+        return wm_copy_or_cut(editor, nKey == 'X');
       break;
     case 'Z':
       if (ctrl_is_down)
@@ -4142,7 +4157,7 @@ LRESULT ME_HandleMessage(ME_TextEditor *editor, UINT msg, WPARAM wParam,
     return 0;
   case WM_CUT:
   case WM_COPY:
-    copy_or_cut(editor, msg == WM_CUT);
+    wm_copy_or_cut(editor, msg == WM_CUT);
     return 0;
   case WM_GETTEXTLENGTH:
   {
diff --git a/dlls/riched20/editor.h b/dlls/riched20/editor.h
index 910a87c33c4..fcef21d17f1 100644
--- a/dlls/riched20/editor.h
+++ b/dlls/riched20/editor.h
@@ -177,7 +177,7 @@ BOOL ME_IsSelection(ME_TextEditor *editor) DECLSPEC_HIDDEN;
 void ME_DeleteSelection(ME_TextEditor *editor) DECLSPEC_HIDDEN;
 void ME_SendSelChange(ME_TextEditor *editor) DECLSPEC_HIDDEN;
 void ME_InsertOLEFromCursor(ME_TextEditor *editor, const REOBJECT* reo, int nCursor) DECLSPEC_HIDDEN;
-BOOL ME_InternalDeleteText(ME_TextEditor *editor, ME_Cursor *start, int nChars, BOOL bForce) DECLSPEC_HIDDEN;
+BOOL ME_InternalDeleteText(ME_TextEditor *editor, const ME_Cursor *start, int nChars, BOOL bForce) DECLSPEC_HIDDEN;
 int ME_GetTextLength(ME_TextEditor *editor) DECLSPEC_HIDDEN;
 int ME_GetTextLengthEx(ME_TextEditor *editor, const GETTEXTLENGTHEX *how) DECLSPEC_HIDDEN;
 ME_Style *ME_GetSelectionInsertStyle(ME_TextEditor *editor) DECLSPEC_HIDDEN;
@@ -267,6 +267,7 @@ 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 ME_CopyOrCut(ME_TextEditor *editor, BOOL cut, BOOL silent, const ME_Cursor *start, int nChars, VARIANT *v);
 
 /* table.c */
 BOOL ME_IsInTable(ME_DisplayItem *pItem) DECLSPEC_HIDDEN;
diff --git a/dlls/riched20/richole.c b/dlls/riched20/richole.c
index 47d8442d5ce..4dfdae6cee3 100644
--- a/dlls/riched20/richole.c
+++ b/dlls/riched20/richole.c
@@ -2586,28 +2586,48 @@ 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;
+
+    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);
+    }
+    ME_CursorFromCharOfs(editor, start, &cursor);
+    return ME_CopyOrCut(editor, cut, TRUE, &cursor, end - start, v);
+}
+
 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)
@@ -5357,25 +5377,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..247c558808d 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(SUCCEEDED(hr), "SetRange failed: 0x%08x\n", hr);
+  hr = ITextRange_Copy(range, NULL);
+  ok(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();
 }


More information about the wine-devel mailing list