}
else
TRACE("WM_SETTEXT - NULL\n");
+ if (editor->AutoURLDetect_bEnable)
+ {
+ int cMin = 0, cMax = -1;
+ while (ME_FindNextURLCandidate(editor, cMin, cMax, &cMin, &cMax))
+ {
+ if (ME_IsCandidateAnURL(editor, cMin, cMax)) {
+ CHARFORMAT2W link;
+ link.cbSize = sizeof(link);
+ link.dwMask = CFM_LINK;
+ link.dwEffects = CFE_LINK;
+ ME_SetCharFormat(editor, cMin, cMax - cMin, &link);
+ }
+ cMin = cMax;
+ cMax = -1;
+ }
+ }
ME_SetSelection(editor, 0, 0);
editor->nModifyStep = 0;
ME_CommitUndo(editor);
CopyMemory(buffer, item->member.run.strText->szData + nStart, sizeof(WCHAR)*nLen);
nChars -= nLen;
nWritten += nLen;
- if (!nChars)
- return nWritten;
buffer += nLen;
+ if (!nChars) {
+ *buffer = 0;
+ return nWritten;
+ }
nStart = 0;
item = ME_FindItemFwd(item, diRun);
}
}
return 0;
}
+
+
+static BOOL isurlspecial(WCHAR c)
+{
+ static const WCHAR special_chars[] = {'.','/','%','@','*','|','\\','+','#',0};
+ return strchrW( special_chars, c ) != NULL;
+}
+
+/**
+ * This proc takes a selection, and scans it forward in order to select the span
+ * of a possible URL candidate. A possible URL candidate must start with isalnum
+ * or one of the following special characters: *|/\+%#@ and must consist entirely
+ * of the characters allowed to start the URL, plus : (colon) which may occur
+ * at most once, and not at either end.
+ *
+ * sel_max == -1 indicates scan to end of text.
+ */
+BOOL ME_FindNextURLCandidate(ME_TextEditor *editor, int sel_min, int sel_max,
+ int * candidate_min, int * candidate_max)
+{
+ ME_DisplayItem * item;
+ ME_DisplayItem * para;
+ int nStart;
+ BOOL foundColon = FALSE;
+ WCHAR lastAcceptedChar = '\0';
+
+ TRACE("sel_min = %d sel_max = %d\n", sel_min, sel_max);
+
+ *candidate_min = *candidate_max = -1;
+ item = ME_FindItemAtOffset(editor, diRun, sel_min, &nStart);
+ if (!item) return FALSE;
+ TRACE("nStart = %d\n", nStart);
+ para = ME_GetParagraph(item);
+ if (sel_max == -1) sel_max = ME_GetTextLength(editor);
+ while (item && para->member.para.nCharOfs + item->member.run.nCharOfs + nStart < sel_max)
+ {
+ ME_DisplayItem * next_item;
+
+ if (!(item->member.run.nFlags & MERF_ENDPARA)) {
+ /* Find start of candidate */
+ if (*candidate_min == -1) {
+ while (nStart < ME_StrLen(item->member.run.strText) &&
+ !(isalnumW(item->member.run.strText->szData[nStart]) ||
+ isurlspecial(item->member.run.strText->szData[nStart]))) {
+ nStart++;
+ }
+ if (nStart < ME_StrLen(item->member.run.strText) &&
+ (isalnumW(item->member.run.strText->szData[nStart]) ||
+ isurlspecial(item->member.run.strText->szData[nStart]))) {
+ *candidate_min = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
+ lastAcceptedChar = item->member.run.strText->szData[nStart];
+ nStart++;
+ }
+ }
+
+ /* Find end of candidate */
+ if (*candidate_min >= 0) {
+ while (nStart < ME_StrLen(item->member.run.strText) &&
+ (isalnumW(item->member.run.strText->szData[nStart]) ||
+ isurlspecial(item->member.run.strText->szData[nStart]) ||
+ (!foundColon && item->member.run.strText->szData[nStart] == ':') )) {
+ if (item->member.run.strText->szData[nStart] == ':') foundColon = TRUE;
+ lastAcceptedChar = item->member.run.strText->szData[nStart];
+ nStart++;
+ }
+ if (nStart < ME_StrLen(item->member.run.strText) &&
+ !(isalnumW(item->member.run.strText->szData[nStart]) ||
+ isurlspecial(item->member.run.strText->szData[nStart]) )) {
+ *candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
+ nStart++;
+ if (lastAcceptedChar == ':') (*candidate_max)--;
+ return TRUE;
+ }
+ }
+ } else {
+ /* End of paragraph: skip it if before candidate span, or terminates
+ current active span */
+ if (*candidate_min >= 0) {
+ *candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs;
+ if (lastAcceptedChar == ':') (*candidate_max)--;
+ return TRUE;
+ }
+ }
+
+ /* Reaching this point means no span was found, so get next span */
+ next_item = ME_FindItemFwd(item, diRun);
+ if (!next_item) {
+ if (*candidate_min >= 0) {
+ /* There are no further runs, so take end of text as end of candidate */
+ *candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
+ if (lastAcceptedChar == ':') (*candidate_max)--;
+ return TRUE;
+ }
+ }
+ item = next_item;
+ para = ME_GetParagraph(item);
+ nStart = 0;
+ }
+
+ if (item) {
+ if (*candidate_min >= 0) {
+ /* There are no further runs, so take end of text as end of candidate */
+ *candidate_max = para->member.para.nCharOfs + item->member.run.nCharOfs + nStart;
+ if (lastAcceptedChar == ':') (*candidate_max)--;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * This proc evaluates the selection and returns TRUE if it can be considered an URL
+ */
+BOOL ME_IsCandidateAnURL(ME_TextEditor *editor, int sel_min, int sel_max)
+{
+ struct prefix_s {
+ const char *text;
+ int length;
+ } prefixes[12] = {
+ /* Code below depends on these being in decreasing length order! */
+ {"prospero:", 10},
+ {"telnet:", 8},
+ {"gopher:", 8},
+ {"mailto:", 8},
+ {"https:", 7},
+ {"file:", 6},
+ {"news:", 6},
+ {"wais:", 6},
+ {"nntp:", 6},
+ {"http:", 5},
+ {"www.", 5},
+ {"ftp:", 5},
+ };
+ LPWSTR bufferW = NULL;
+ WCHAR bufW[32];
+ int i;
+
+ if (sel_max == -1) sel_max = ME_GetTextLength(editor);
+ assert(sel_min <= sel_max);
+ for (i = 0; i < sizeof(prefixes) / sizeof(struct prefix_s); i++)
+ {
+ if (sel_max - sel_min < prefixes[i].length) continue;
+ if (bufferW == NULL) {
+ bufferW = (LPWSTR)heap_alloc((sel_max - sel_min + 1) * sizeof(WCHAR));
+ }
+ ME_GetTextW(editor, bufferW, sel_min, min(sel_max - sel_min, strlen(prefixes[i].text)), 0);
+ MultiByteToWideChar(CP_ACP, 0, prefixes[i].text, -1, bufW, 32);
+ if (!lstrcmpW(bufW, bufferW))
+ {
+ heap_free(bufferW);
+ return TRUE;
+ }
+ }
+ if (bufferW != NULL) heap_free(bufferW);
+ return FALSE;
+}
DestroyWindow(hwndRichEdit);
}
-static void check_CFE_LINK_rcvd(HWND hwnd, int is_url, const char * url)
+static int check_CFE_LINK_selection(HWND hwnd, int sel_start, int sel_end)
{
CHARFORMAT2W text_format;
- int link_present = 0;
text_format.cbSize = sizeof(text_format);
- SendMessage(hwnd, EM_SETSEL, 0, 1);
+ SendMessage(hwnd, EM_SETSEL, sel_start, sel_end);
SendMessage(hwnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &text_format);
- link_present = text_format.dwEffects & CFE_LINK;
+ return (text_format.dwEffects & CFE_LINK) ? 1 : 0;
+}
+
+static void check_CFE_LINK_rcvd(HWND hwnd, int is_url, const char * url)
+{
+ int link_present = 0;
+
+ link_present = check_CFE_LINK_selection(hwnd, 0, 1);
if (is_url)
{ /* control text is url; should get CFE_LINK */
ok(0 != link_present, "URL Case: CFE_LINK not set for [%s].\n", url);
{"wais:waisserver", 1}
};
- int i;
+ int i, j;
int urlRet=-1;
HWND hwndRichEdit, parent;
+ /* All of the following should cause the URL to be detected */
+ const char * templates_delim[] = {
+ "This is some text with X on it",
+ "This is some text with (X) on it",
+ "This is some text with X\r on it",
+ "This is some text with ---X--- on it",
+ "This is some text with \"X\" on it",
+ "This is some text with 'X' on it",
+ "This is some text with 'X' on it",
+ "This is some text with :X: on it",
+
+ "This text ends with X",
+
+ "This is some text with X) on it",
+ "This is some text with X--- on it",
+ "This is some text with X\" on it",
+ "This is some text with X' on it",
+ "This is some text with X: on it",
+
+ "This is some text with (X on it",
+ "This is some text with \rX on it",
+ "This is some text with ---X on it",
+ "This is some text with \"X on it",
+ "This is some text with 'X on it",
+ "This is some text with :X on it",
+ };
+ /* None of these should cause the URL to be detected */
+ const char * templates_non_delim[] = {
+ "This is some text with |X| on it",
+ "This is some text with *X* on it",
+ "This is some text with /X/ on it",
+ "This is some text with +X+ on it",
+ "This is some text with %X% on it",
+ "This is some text with #X# on it",
+ "This is some text with @X@ on it",
+ "This is some text with \\X\\ on it",
+ "This is some text with |X on it",
+ "This is some text with *X on it",
+ "This is some text with /X on it",
+ "This is some text with +X on it",
+ "This is some text with %X on it",
+ "This is some text with #X on it",
+ "This is some text with @X on it",
+ "This is some text with \\X on it",
+ };
+ /* All of these cause the URL detection to be extended by one more byte,
+ thus demonstrating that the tested character is considered as part
+ of the URL. */
+ const char * templates_xten_delim[] = {
+ "This is some text with X| on it",
+ "This is some text with X* on it",
+ "This is some text with X/ on it",
+ "This is some text with X+ on it",
+ "This is some text with X% on it",
+ "This is some text with X# on it",
+ "This is some text with X@ on it",
+ "This is some text with X\\ on it",
+ };
+ char buffer[1024];
+
parent = new_static_wnd(NULL);
hwndRichEdit = new_richedit(parent);
/* Try and pass EM_AUTOURLDETECT some test wParam values */
ok(urlRet==E_INVALIDARG, "Bad wParam2: urlRet is: %d\n", urlRet);
/* for each url, check the text to see if CFE_LINK effect is present */
for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
+
SendMessage(hwndRichEdit, EM_AUTOURLDETECT, FALSE, 0);
SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
- SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
check_CFE_LINK_rcvd(hwndRichEdit, 0, urls[i].text);
+
+ /* Link detection should happen immediately upon WM_SETTEXT */
SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) urls[i].text);
- SendMessage(hwndRichEdit, WM_CHAR, 0, 0);
check_CFE_LINK_rcvd(hwndRichEdit, urls[i].is_url, urls[i].text);
}
DestroyWindow(hwndRichEdit);
+
+ /* Test detection of URLs within normal text - WM_SETTEXT case. */
+ for (i = 0; i < sizeof(urls)/sizeof(struct urls_s); i++) {
+ hwndRichEdit = new_richedit(parent);
+
+ for (j = 0; j < sizeof(templates_delim) / sizeof(const char *); j++) {
+ char * at_pos;
+ int at_offset;
+ int end_offset;
+
+ at_pos = strchr(templates_delim[j], 'X');
+ at_offset = at_pos - templates_delim[j];
+ strncpy(buffer, templates_delim[j], at_offset);
+ buffer[at_offset] = '\0';
+ strcat(buffer, urls[i].text);
+ strcat(buffer, templates_delim[j] + at_offset + 1);
+ end_offset = at_offset + strlen(urls[i].text);
+
+ SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
+ SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) buffer);
+
+ /* This assumes no templates start with the URL itself, and that they
+ have at least two characters before the URL text */
+ ok(!check_CFE_LINK_selection(hwndRichEdit, 0, 1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", 0, 1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -2, at_offset -1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -2, at_offset -1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -1, at_offset),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -1, at_offset, buffer);
+
+ if (urls[i].is_url)
+ {
+ ok(check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
+ "CFE_LINK not set in (%d-%d), text: %s\n", at_offset, at_offset +1, buffer);
+ ok(check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
+ "CFE_LINK not set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
+ }
+ else
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset, at_offset + 1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
+ }
+ if (buffer[end_offset] != '\0')
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset, end_offset + 1, buffer);
+ if (buffer[end_offset +1] != '\0')
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +1, end_offset +2),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +1, end_offset +2, buffer);
+ }
+ }
+ }
+
+ for (j = 0; j < sizeof(templates_non_delim) / sizeof(const char *); j++) {
+ char * at_pos;
+ int at_offset;
+ int end_offset;
+
+ at_pos = strchr(templates_non_delim[j], 'X');
+ at_offset = at_pos - templates_non_delim[j];
+ strncpy(buffer, templates_non_delim[j], at_offset);
+ buffer[at_offset] = '\0';
+ strcat(buffer, urls[i].text);
+ strcat(buffer, templates_non_delim[j] + at_offset + 1);
+ end_offset = at_offset + strlen(urls[i].text);
+
+ SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
+ SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) buffer);
+
+ /* This assumes no templates start with the URL itself, and that they
+ have at least two characters before the URL text */
+ ok(!check_CFE_LINK_selection(hwndRichEdit, 0, 1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", 0, 1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -2, at_offset -1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -2, at_offset -1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -1, at_offset),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -1, at_offset, buffer);
+
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset, at_offset + 1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
+ if (buffer[end_offset] != '\0')
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset, end_offset + 1, buffer);
+ if (buffer[end_offset +1] != '\0')
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +1, end_offset +2),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +1, end_offset +2, buffer);
+ }
+ }
+ }
+
+ for (j = 0; j < sizeof(templates_xten_delim) / sizeof(const char *); j++) {
+ char * at_pos;
+ int at_offset;
+ int end_offset;
+
+ at_pos = strchr(templates_xten_delim[j], 'X');
+ at_offset = at_pos - templates_xten_delim[j];
+ strncpy(buffer, templates_xten_delim[j], at_offset);
+ buffer[at_offset] = '\0';
+ strcat(buffer, urls[i].text);
+ strcat(buffer, templates_xten_delim[j] + at_offset + 1);
+ end_offset = at_offset + strlen(urls[i].text);
+
+ SendMessage(hwndRichEdit, EM_AUTOURLDETECT, TRUE, 0);
+ SendMessage(hwndRichEdit, WM_SETTEXT, 0, (LPARAM) buffer);
+
+ /* This assumes no templates start with the URL itself, and that they
+ have at least two characters before the URL text */
+ ok(!check_CFE_LINK_selection(hwndRichEdit, 0, 1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", 0, 1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -2, at_offset -1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -2, at_offset -1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset -1, at_offset),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset -1, at_offset, buffer);
+
+ if (urls[i].is_url)
+ {
+ ok(check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
+ "CFE_LINK not set in (%d-%d), text: %s\n", at_offset, at_offset +1, buffer);
+ ok(check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
+ "CFE_LINK not set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
+ ok(check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
+ "CFE_LINK not set in (%d-%d), text: %s\n", end_offset, end_offset +1, buffer);
+ }
+ else
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, at_offset, at_offset +1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", at_offset, at_offset + 1, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset -1, end_offset),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset -1, end_offset, buffer);
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset, end_offset +1),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset, end_offset +1, buffer);
+ }
+ if (buffer[end_offset +1] != '\0')
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +1, end_offset +2),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +1, end_offset + 2, buffer);
+ if (buffer[end_offset +2] != '\0')
+ {
+ ok(!check_CFE_LINK_selection(hwndRichEdit, end_offset +2, end_offset +3),
+ "CFE_LINK incorrectly set in (%d-%d), text: %s\n", end_offset +2, end_offset +3, buffer);
+ }
+ }
+ }
+
+ DestroyWindow(hwndRichEdit);
+ hwndRichEdit = NULL;
+ }
+
DestroyWindow(parent);
}
test_WM_GETTEXT();
test_EM_GETTEXTRANGE();
test_EM_GETSELTEXT();
- test_EM_AUTOURLDETECT();
test_EM_SETUNDOLIMIT();
test_ES_PASSWORD();
test_EM_SETTEXTEX();
test_EM_GETMODIFY();
test_EM_EXSETSEL();
test_WM_PASTE();
+ test_EM_AUTOURLDETECT();
test_EM_STREAMIN();
test_EM_STREAMOUT();
test_EM_StreamIn_Undo();