| /* |
| * RichEdit - functions dealing with editor object |
| * |
| * Copyright 2004 by Krzysztof Foltman |
| * Copyright 2005 by Cihan Altinay |
| * Copyright 2005 by Phil Krylov |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| /* |
| API implementation status: |
| |
| Messages (ANSI versions not done yet) |
| + EM_AUTOURLDETECT 2.0 |
| + EM_CANPASTE |
| + EM_CANREDO 2.0 |
| + EM_CANUNDO |
| + EM_CHARFROMPOS |
| - EM_DISPLAYBAND |
| + EM_EMPTYUNDOBUFFER |
| + EM_EXGETSEL |
| + EM_EXLIMITTEXT |
| + EM_EXLINEFROMCHAR |
| + EM_EXSETSEL |
| + EM_FINDTEXT (only FR_DOWN flag implemented) |
| + EM_FINDTEXTEX (only FR_DOWN flag implemented) |
| - EM_FINDWORDBREAK |
| - EM_FMTLINES |
| - EM_FORMATRANGE |
| + EM_GETAUTOURLDETECT 2.0 |
| - EM_GETBIDIOPTIONS 3.0 |
| - EM_GETCHARFORMAT (partly done) |
| - EM_GETEDITSTYLE |
| + EM_GETEVENTMASK |
| + EM_GETFIRSTVISIBLELINE (can be optimized if needed) |
| - EM_GETIMECOLOR 1.0asian |
| - EM_GETIMECOMPMODE 2.0 |
| - EM_GETIMEOPTIONS 1.0asian |
| - EM_GETIMESTATUS |
| - EM_GETLANGOPTIONS 2.0 |
| + EM_GETLIMITTEXT |
| + EM_GETLINE |
| + EM_GETLINECOUNT returns number of rows, not of paragraphs |
| + EM_GETMODIFY |
| - EM_GETOLEINTERFACE |
| + EM_GETOPTIONS |
| + EM_GETPARAFORMAT |
| + EM_GETPASSWORDCHAR 2.0 |
| - EM_GETPUNCTUATION 1.0asian |
| + EM_GETRECT |
| - EM_GETREDONAME 2.0 |
| + EM_GETSEL |
| + EM_GETSELTEXT (ANSI&Unicode) |
| + EM_GETSCROLLPOS 3.0 (only Y value valid) |
| ! - EM_GETTHUMB |
| - EM_GETTEXTEX 2.0 |
| + EM_GETTEXTLENGTHEX (GTL_PRECISE unimplemented) |
| - EM_GETTEXTMODE 2.0 |
| ? + EM_GETTEXTRANGE (ANSI&Unicode) |
| - EM_GETTYPOGRAPHYOPTIONS 3.0 |
| - EM_GETUNDONAME |
| + EM_GETWORDBREAKPROC |
| - EM_GETWORDBREAKPROCEX |
| - EM_GETWORDWRAPMODE 1.0asian |
| + EM_GETZOOM 3.0 |
| + EM_HIDESELECTION |
| + EM_LIMITTEXT (Also called EM_SETLIMITTEXT) |
| + EM_LINEFROMCHAR |
| + EM_LINEINDEX |
| + EM_LINELENGTH |
| + EM_LINESCROLL |
| - EM_PASTESPECIAL |
| + EM_POSFROMCHAR |
| + EM_REDO 2.0 |
| + EM_REQUESTRESIZE |
| + EM_REPLACESEL (proper style?) ANSI&Unicode |
| + EM_SCROLL |
| + EM_SCROLLCARET |
| - EM_SELECTIONTYPE |
| - EM_SETBIDIOPTIONS 3.0 |
| + EM_SETBKGNDCOLOR |
| + EM_SETCHARFORMAT (partly done, no ANSI) |
| - EM_SETEDITSTYLE |
| + EM_SETEVENTMASK (few notifications supported) |
| - EM_SETFONTSIZE |
| - EM_SETIMECOLOR 1.0asian |
| - EM_SETIMEOPTIONS 1.0asian |
| - EM_SETLANGOPTIONS 2.0 |
| - EM_SETLIMITTEXT |
| + EM_SETMODIFY (not sure if implementation is correct) |
| - EM_SETOLECALLBACK |
| + EM_SETOPTIONS (partially implemented) |
| - EM_SETPALETTE 2.0 |
| + EM_SETPARAFORMAT |
| + EM_SETPASSWORDCHAR 2.0 |
| - EM_SETPUNCTUATION 1.0asian |
| + EM_SETREADONLY no beep on modification attempt |
| + EM_SETRECT |
| + EM_SETRECTNP (EM_SETRECT without repainting) |
| + EM_SETSEL |
| + EM_SETSCROLLPOS 3.0 |
| - EM_SETTABSTOPS 3.0 |
| - EM_SETTARGETDEVICE |
| + EM_SETTEXTEX 3.0 (unicode only, no rich text insertion handling, proper style?) |
| - EM_SETTEXTMODE 2.0 |
| - EM_SETTYPOGRAPHYOPTIONS 3.0 |
| + EM_SETUNDOLIMIT 2.0 |
| + EM_SETWORDBREAKPROC (used only for word movement at the moment) |
| - EM_SETWORDBREAKPROCEX |
| - EM_SETWORDWRAPMODE 1.0asian |
| + EM_SETZOOM 3.0 |
| + EM_SHOWSCROLLBAR 2.0 |
| - EM_STOPGROUPTYPING 2.0 |
| + EM_STREAMIN |
| + EM_STREAMOUT |
| + EM_UNDO |
| + WM_CHAR |
| + WM_CLEAR |
| + WM_COPY |
| + WM_CUT |
| + WM_GETDLGCODE (the current implementation is incomplete) |
| + WM_GETTEXT (ANSI&Unicode) |
| + WM_GETTEXTLENGTH (ANSI version sucks) |
| + WM_PASTE |
| + WM_SETFONT |
| + WM_SETTEXT (resets undo stack !) (proper style?) ANSI&Unicode |
| - WM_STYLECHANGING |
| - WM_STYLECHANGED (things like read-only flag) |
| - WM_UNICHAR |
| |
| Notifications |
| |
| * EN_CHANGE (sent from the wrong place) |
| - EN_CORRECTTEXT |
| - EN_DROPFILES |
| - EN_ERRSPACE |
| - EN_HSCROLL |
| - EN_IMECHANGE |
| + EN_KILLFOCUS |
| - EN_LINK |
| - EN_MAXTEXT |
| - EN_MSGFILTER |
| - EN_OLEOPFAILED |
| - EN_PROTECTED |
| + EN_REQUESTRESIZE |
| - EN_SAVECLIPBOARD |
| + EN_SELCHANGE |
| + EN_SETFOCUS |
| - EN_STOPNOUNDO |
| * EN_UPDATE (sent from the wrong place) |
| - EN_VSCROLL |
| |
| Styles |
| |
| - ES_AUTOHSCROLL |
| - ES_AUTOVSCROLL |
| - ES_CENTER |
| + ES_DISABLENOSCROLL (scrollbar is always visible) |
| - ES_EX_NOCALLOLEINIT |
| - ES_LEFT |
| - ES_MULTILINE (currently single line controls aren't supported) |
| - ES_NOIME |
| - ES_READONLY (I'm not sure if beeping is the proper behaviour) |
| - ES_RIGHT |
| - ES_SAVESEL |
| - ES_SELFIME |
| - ES_SUNKEN |
| - ES_VERTICAL |
| - ES_WANTRETURN (don't know how to do WM_GETDLGCODE part) |
| - WS_SETFONT |
| - WS_HSCROLL |
| + WS_VSCROLL |
| */ |
| |
| /* |
| * RICHED20 TODO (incomplete): |
| * |
| * - messages/styles/notifications listed above |
| * - Undo coalescing |
| * - add remaining CHARFORMAT/PARAFORMAT fields |
| * - right/center align should strip spaces from the beginning |
| * - pictures/OLE objects (not just smiling faces that lack API support ;-) ) |
| * - COM interface (looks like a major pain in the TODO list) |
| * - calculate heights of pictures (half-done) |
| * - horizontal scrolling (not even started) |
| * - hysteresis during wrapping (related to scrollbars appearing/disappearing) |
| * - find/replace |
| * - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible) |
| * - italic caret with italic fonts |
| * - IME |
| * - most notifications aren't sent at all (the most important ones are) |
| * - when should EN_SELCHANGE be sent after text change ? (before/after EN_UPDATE?) |
| * - WM_SETTEXT may use wrong style (but I'm 80% sure it's OK) |
| * - EM_GETCHARFORMAT with SCF_SELECTION may not behave 100% like in original (but very close) |
| * - full justification |
| * - hyphenation |
| * - tables |
| * - ListBox & ComboBox not implemented |
| * |
| * Bugs that are probably fixed, but not so easy to verify: |
| * - EN_UPDATE/EN_CHANGE are handled very incorrectly (should be OK now) |
| * - undo for ME_JoinParagraphs doesn't store paragraph format ? (it does) |
| * - check/fix artificial EOL logic (bCursorAtEnd, hardly logical) |
| * - caret shouldn't be displayed when selection isn't empty |
| * - check refcounting in style management functions (looks perfect now, but no bugs is suspicious) |
| * - undo for setting default format (done, might be buggy) |
| * - styles might be not released properly (looks like they work like charm, but who knows? |
| * |
| */ |
| |
| #include "editor.h" |
| #include "commdlg.h" |
| #include "winreg.h" |
| #define NO_SHLWAPI_STREAM |
| #include "shlwapi.h" |
| #include "imm.h" |
| #include "textserv.h" |
| #include "rtf.h" |
| |
| #define STACK_SIZE_DEFAULT 100 |
| #define STACK_SIZE_MAX 1000 |
| |
| #define TEXT_LIMIT_DEFAULT 32767 |
| |
| WINE_DEFAULT_DEBUG_CHANNEL(richedit); |
| |
| static BOOL ME_RegisterEditorClass(HINSTANCE); |
| |
| static const WCHAR RichEdit20W[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '2', '0', 'W', 0}; |
| static const WCHAR RichEdit50W[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '5', '0', 'W', 0}; |
| static const WCHAR REListBox20W[] = {'R','E','L','i','s','t','B','o','x','2','0','W', 0}; |
| static const WCHAR REComboBox20W[] = {'R','E','C','o','m','b','o','B','o','x','2','0','W', 0}; |
| |
| int me_debug = 0; |
| HANDLE me_heap = NULL; |
| |
| static BOOL ME_ListBoxRegistered = FALSE; |
| static BOOL ME_ComboBoxRegistered = FALSE; |
| |
| static inline int is_version_nt(void) |
| { |
| return !(GetVersion() & 0x80000000); |
| } |
| |
| static ME_TextBuffer *ME_MakeText(void) { |
| |
| ME_TextBuffer *buf = ALLOC_OBJ(ME_TextBuffer); |
| |
| ME_DisplayItem *p1 = ME_MakeDI(diTextStart); |
| ME_DisplayItem *p2 = ME_MakeDI(diTextEnd); |
| |
| p1->prev = NULL; |
| p1->next = p2; |
| p2->prev = p1; |
| p2->next = NULL; |
| p1->member.para.next_para = p2; |
| p2->member.para.prev_para = p1; |
| p2->member.para.nCharOfs = 0; |
| |
| buf->pFirst = p1; |
| buf->pLast = p2; |
| buf->pCharStyle = NULL; |
| |
| return buf; |
| } |
| |
| |
| static LRESULT ME_StreamInText(ME_TextEditor *editor, DWORD dwFormat, ME_InStream *stream, ME_Style *style) |
| { |
| WCHAR wszText[STREAMIN_BUFFER_SIZE+1]; |
| WCHAR *pText; |
| |
| TRACE("%08x %p\n", dwFormat, stream); |
| |
| do { |
| long nWideChars = 0; |
| |
| if (!stream->dwSize) |
| { |
| ME_StreamInFill(stream); |
| if (stream->editstream->dwError) |
| break; |
| if (!stream->dwSize) |
| break; |
| } |
| |
| if (!(dwFormat & SF_UNICODE)) |
| { |
| /* FIXME? this is doomed to fail on true MBCS like UTF-8, luckily they're unlikely to be used as CP_ACP */ |
| nWideChars = MultiByteToWideChar(CP_ACP, 0, stream->buffer, stream->dwSize, wszText, STREAMIN_BUFFER_SIZE); |
| pText = wszText; |
| } |
| else |
| { |
| nWideChars = stream->dwSize >> 1; |
| pText = (WCHAR *)stream->buffer; |
| } |
| |
| ME_InsertTextFromCursor(editor, 0, pText, nWideChars, style); |
| if (stream->dwSize == 0) |
| break; |
| stream->dwSize = 0; |
| } while(1); |
| ME_CommitUndo(editor); |
| ME_UpdateRepaint(editor); |
| return 0; |
| } |
| |
| static void ME_RTFCharAttrHook(RTF_Info *info) |
| { |
| CHARFORMAT2W fmt; |
| fmt.cbSize = sizeof(fmt); |
| fmt.dwMask = 0; |
| fmt.dwEffects = 0; |
| |
| switch(info->rtfMinor) |
| { |
| case rtfPlain: |
| /* FIXME add more flags once they're implemented */ |
| fmt.dwMask = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_STRIKEOUT | CFM_COLOR | CFM_BACKCOLOR | CFM_SIZE | CFM_WEIGHT; |
| fmt.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR; |
| fmt.yHeight = 12*20; /* 12pt */ |
| fmt.wWeight = 400; |
| break; |
| case rtfBold: |
| fmt.dwMask = CFM_BOLD; |
| fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0; |
| break; |
| case rtfItalic: |
| fmt.dwMask = CFM_ITALIC; |
| fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0; |
| break; |
| case rtfUnderline: |
| fmt.dwMask = CFM_UNDERLINE; |
| fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0; |
| fmt.bUnderlineType = CFU_CF1UNDERLINE; |
| break; |
| case rtfNoUnderline: |
| fmt.dwMask = CFM_UNDERLINE; |
| fmt.dwEffects = 0; |
| break; |
| case rtfStrikeThru: |
| fmt.dwMask = CFM_STRIKEOUT; |
| fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0; |
| break; |
| case rtfSubScript: |
| case rtfSuperScript: |
| case rtfSubScrShrink: |
| case rtfSuperScrShrink: |
| case rtfNoSuperSub: |
| fmt.dwMask = CFM_SUBSCRIPT|CFM_SUPERSCRIPT; |
| if (info->rtfMinor == rtfSubScrShrink) fmt.dwEffects = CFE_SUBSCRIPT; |
| if (info->rtfMinor == rtfSuperScrShrink) fmt.dwEffects = CFE_SUPERSCRIPT; |
| if (info->rtfMinor == rtfNoSuperSub) fmt.dwEffects = 0; |
| break; |
| case rtfInvisible: |
| fmt.dwMask = CFM_HIDDEN; |
| fmt.dwEffects = info->rtfParam ? fmt.dwMask : 0; |
| break; |
| case rtfBackColor: |
| fmt.dwMask = CFM_BACKCOLOR; |
| fmt.dwEffects = 0; |
| if (info->rtfParam == 0) |
| fmt.dwEffects = CFE_AUTOBACKCOLOR; |
| else if (info->rtfParam != rtfNoParam) |
| { |
| RTFColor *c = RTFGetColor(info, info->rtfParam); |
| fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed); |
| } |
| break; |
| case rtfForeColor: |
| fmt.dwMask = CFM_COLOR; |
| fmt.dwEffects = 0; |
| if (info->rtfParam == 0) |
| fmt.dwEffects = CFE_AUTOCOLOR; |
| else if (info->rtfParam != rtfNoParam) |
| { |
| RTFColor *c = RTFGetColor(info, info->rtfParam); |
| if (c) |
| fmt.crTextColor = (c->rtfCBlue<<16)|(c->rtfCGreen<<8)|(c->rtfCRed); |
| else |
| fmt.crTextColor = 0; |
| } |
| break; |
| case rtfFontNum: |
| if (info->rtfParam != rtfNoParam) |
| { |
| RTFFont *f = RTFGetFont(info, info->rtfParam); |
| if (f) |
| { |
| MultiByteToWideChar(CP_ACP, 0, f->rtfFName, -1, fmt.szFaceName, sizeof(fmt.szFaceName)/sizeof(WCHAR)); |
| fmt.szFaceName[sizeof(fmt.szFaceName)/sizeof(WCHAR)-1] = '\0'; |
| fmt.bCharSet = f->rtfFCharSet; |
| fmt.dwMask = CFM_FACE | CFM_CHARSET; |
| } |
| } |
| break; |
| case rtfFontSize: |
| fmt.dwMask = CFM_SIZE; |
| if (info->rtfParam != rtfNoParam) |
| fmt.yHeight = info->rtfParam*10; |
| break; |
| } |
| if (fmt.dwMask) { |
| ME_Style *style2; |
| RTFFlushOutputBuffer(info); |
| /* FIXME too slow ? how come ? */ |
| style2 = ME_ApplyStyle(info->style, &fmt); |
| ME_ReleaseStyle(info->style); |
| info->style = style2; |
| info->styleChanged = TRUE; |
| } |
| } |
| |
| /* FIXME this function doesn't get any information about context of the RTF tag, which is very bad, |
| the same tags mean different things in different contexts */ |
| static void ME_RTFParAttrHook(RTF_Info *info) |
| { |
| PARAFORMAT2 fmt; |
| fmt.cbSize = sizeof(fmt); |
| fmt.dwMask = 0; |
| |
| switch(info->rtfMinor) |
| { |
| case rtfParDef: /* restores default paragraph attributes */ |
| fmt.dwMask = PFM_ALIGNMENT | PFM_TABSTOPS | PFM_OFFSET | PFM_STARTINDENT; |
| fmt.wAlignment = PFA_LEFT; |
| fmt.cTabCount = 0; |
| fmt.dxOffset = fmt.dxStartIndent = 0; |
| RTFFlushOutputBuffer(info); |
| ME_GetParagraph(info->editor->pCursors[0].pRun)->member.para.bTable = FALSE; |
| break; |
| case rtfInTable: |
| { |
| ME_DisplayItem *para; |
| |
| RTFFlushOutputBuffer(info); |
| para = ME_GetParagraph(info->editor->pCursors[0].pRun); |
| assert(para->member.para.pCells); |
| para->member.para.bTable = TRUE; |
| return; |
| } |
| case rtfFirstIndent: |
| ME_GetSelectionParaFormat(info->editor, &fmt); |
| fmt.dwMask = PFM_STARTINDENT | PFM_OFFSET; |
| fmt.dxStartIndent += info->rtfParam + fmt.dxOffset; |
| fmt.dxOffset = -info->rtfParam; |
| break; |
| case rtfLeftIndent: |
| ME_GetSelectionParaFormat(info->editor, &fmt); |
| fmt.dwMask = PFM_STARTINDENT; |
| fmt.dxStartIndent = -fmt.dxOffset + info->rtfParam; |
| break; |
| case rtfRightIndent: |
| fmt.dwMask = PFM_RIGHTINDENT; |
| fmt.dxRightIndent = info->rtfParam; |
| break; |
| case rtfQuadLeft: |
| case rtfQuadJust: |
| fmt.dwMask = PFM_ALIGNMENT; |
| fmt.wAlignment = PFA_LEFT; |
| break; |
| case rtfQuadRight: |
| fmt.dwMask = PFM_ALIGNMENT; |
| fmt.wAlignment = PFA_RIGHT; |
| break; |
| case rtfQuadCenter: |
| fmt.dwMask = PFM_ALIGNMENT; |
| fmt.wAlignment = PFA_CENTER; |
| break; |
| case rtfTabPos: |
| ME_GetSelectionParaFormat(info->editor, &fmt); |
| if (!(fmt.dwMask & PFM_TABSTOPS)) |
| { |
| fmt.dwMask |= PFM_TABSTOPS; |
| fmt.cTabCount = 0; |
| } |
| if (fmt.cTabCount < MAX_TAB_STOPS) |
| fmt.rgxTabs[fmt.cTabCount++] = info->rtfParam; |
| break; |
| } |
| if (fmt.dwMask) { |
| RTFFlushOutputBuffer(info); |
| /* FIXME too slow ? how come ?*/ |
| ME_SetSelectionParaFormat(info->editor, &fmt); |
| } |
| } |
| |
| static void ME_RTFTblAttrHook(RTF_Info *info) |
| { |
| ME_DisplayItem *para; |
| |
| switch (info->rtfMinor) |
| { |
| case rtfRowDef: |
| RTFFlushOutputBuffer(info); |
| para = ME_GetParagraph(info->editor->pCursors[0].pRun); |
| |
| /* Release possibly inherited cell definitions */ |
| ME_DestroyTableCellList(para); |
| |
| para->member.para.pCells = ALLOC_OBJ(ME_TableCell); |
| para->member.para.pCells->nRightBoundary = 0; |
| para->member.para.pCells->next = NULL; |
| para->member.para.pLastCell = para->member.para.pCells; |
| break; |
| case rtfCellPos: |
| RTFFlushOutputBuffer(info); |
| para = ME_GetParagraph(info->editor->pCursors[0].pRun); |
| |
| if (para->member.para.pLastCell->nRightBoundary) |
| { |
| ME_TableCell *pCell = ALLOC_OBJ(ME_TableCell); |
| |
| pCell->next = NULL; |
| para->member.para.pLastCell->next = pCell; |
| para->member.para.pLastCell = pCell; |
| } |
| para->member.para.pLastCell->nRightBoundary = info->rtfParam; |
| break; |
| } |
| } |
| |
| static void ME_RTFReadHook(RTF_Info *info) { |
| switch(info->rtfClass) |
| { |
| case rtfGroup: |
| switch(info->rtfMajor) |
| { |
| case rtfBeginGroup: |
| if (info->stackTop < maxStack) { |
| memcpy(&info->stack[info->stackTop].fmt, &info->style->fmt, sizeof(CHARFORMAT2W)); |
| info->stack[info->stackTop].codePage = info->codePage; |
| info->stack[info->stackTop].unicodeLength = info->unicodeLength; |
| } |
| info->stackTop++; |
| info->styleChanged = FALSE; |
| break; |
| case rtfEndGroup: |
| { |
| ME_Style *s; |
| RTFFlushOutputBuffer(info); |
| if (info->stackTop<=1) { |
| info->rtfClass = rtfEOF; |
| return; |
| } |
| info->stackTop--; |
| assert(info->stackTop >= 0); |
| if (info->styleChanged) |
| { |
| /* FIXME too slow ? how come ? */ |
| s = ME_ApplyStyle(info->style, &info->stack[info->stackTop].fmt); |
| ME_ReleaseStyle(info->style); |
| info->style = s; |
| info->codePage = info->stack[info->stackTop].codePage; |
| info->unicodeLength = info->stack[info->stackTop].unicodeLength; |
| } |
| break; |
| } |
| } |
| break; |
| case rtfControl: |
| switch(info->rtfMajor) |
| { |
| case rtfCharAttr: |
| ME_RTFCharAttrHook(info); |
| break; |
| case rtfParAttr: |
| ME_RTFParAttrHook(info); |
| break; |
| case rtfTblAttr: |
| ME_RTFTblAttrHook(info); |
| break; |
| case rtfSpecialChar: |
| if (info->rtfMinor == rtfCell) |
| { |
| RTFFlushOutputBuffer(info); |
| ME_InsertTableCellFromCursor(info->editor, 0); |
| } |
| } |
| break; |
| } |
| } |
| |
| void |
| ME_StreamInFill(ME_InStream *stream) |
| { |
| stream->editstream->dwError = stream->editstream->pfnCallback(stream->editstream->dwCookie, |
| (BYTE *)stream->buffer, |
| sizeof(stream->buffer), |
| (LONG *)&stream->dwSize); |
| stream->dwUsed = 0; |
| } |
| |
| static LRESULT ME_StreamIn(ME_TextEditor *editor, DWORD format, EDITSTREAM *stream) |
| { |
| RTF_Info parser; |
| ME_Style *style; |
| int from, to, to2, nUndoMode; |
| int nEventMask = editor->nEventMask; |
| ME_InStream inStream; |
| |
| TRACE("stream==%p hWnd==%p format==0x%X\n", stream, editor->hWnd, (UINT)format); |
| editor->nEventMask = 0; |
| |
| ME_GetSelection(editor, &from, &to); |
| if ((format & SFF_SELECTION) && (editor->mode & TM_RICHTEXT)) { |
| style = ME_GetSelectionInsertStyle(editor); |
| |
| ME_InternalDeleteText(editor, from, to-from); |
| } |
| else { |
| style = editor->pBuffer->pDefaultStyle; |
| ME_AddRefStyle(style); |
| SendMessageA(editor->hWnd, EM_SETSEL, 0, 0); |
| ME_InternalDeleteText(editor, 0, ME_GetTextLength(editor)); |
| from = to = 0; |
| ME_ClearTempStyle(editor); |
| /* FIXME restore default paragraph formatting ! */ |
| } |
| |
| |
| /* Back up undo mode to a local variable */ |
| nUndoMode = editor->nUndoMode; |
| |
| /* Only create an undo if SFF_SELECTION is set */ |
| if (!(format & SFF_SELECTION)) |
| editor->nUndoMode = umIgnore; |
| |
| inStream.editstream = stream; |
| inStream.editstream->dwError = 0; |
| inStream.dwSize = 0; |
| inStream.dwUsed = 0; |
| |
| if (format & SF_RTF) |
| { |
| /* Check if it's really RTF, and if it is not, use plain text */ |
| ME_StreamInFill(&inStream); |
| if (!inStream.editstream->dwError) |
| { |
| if (strncmp(inStream.buffer, "{\\rtf", 5) && strncmp(inStream.buffer, "{\\urtf", 6)) |
| { |
| format &= ~SF_RTF; |
| format |= SF_TEXT; |
| } |
| } |
| } |
| |
| if (!inStream.editstream->dwError) |
| { |
| if (format & SF_RTF) { |
| /* setup the RTF parser */ |
| memset(&parser, 0, sizeof parser); |
| RTFSetEditStream(&parser, &inStream); |
| parser.rtfFormat = format&(SF_TEXT|SF_RTF); |
| parser.hwndEdit = editor->hWnd; |
| parser.editor = editor; |
| parser.style = style; |
| WriterInit(&parser); |
| RTFInit(&parser); |
| RTFSetReadHook(&parser, ME_RTFReadHook); |
| BeginFile(&parser); |
| |
| /* do the parsing */ |
| RTFRead(&parser); |
| RTFFlushOutputBuffer(&parser); |
| RTFDestroy(&parser); |
| |
| style = parser.style; |
| } |
| else if (format & SF_TEXT) |
| ME_StreamInText(editor, format, &inStream, style); |
| else |
| ERR("EM_STREAMIN without SF_TEXT or SF_RTF\n"); |
| ME_GetSelection(editor, &to, &to2); |
| /* put the cursor at the top */ |
| if (!(format & SFF_SELECTION)) |
| SendMessageA(editor->hWnd, EM_SETSEL, 0, 0); |
| } |
| |
| /* Restore saved undo mode */ |
| editor->nUndoMode = nUndoMode; |
| |
| /* even if we didn't add an undo, we need to commit anything on the stack */ |
| ME_CommitUndo(editor); |
| |
| /* If SFF_SELECTION isn't set, delete any undos from before we started too */ |
| if (!(format & SFF_SELECTION)) |
| ME_EmptyUndoStack(editor); |
| |
| ME_ReleaseStyle(style); |
| editor->nEventMask = nEventMask; |
| if (editor->bRedraw) |
| { |
| ME_UpdateRepaint(editor); |
| } |
| if (!(format & SFF_SELECTION)) { |
| ME_ClearTempStyle(editor); |
| } |
| ME_MoveCaret(editor); |
| ME_SendSelChange(editor); |
| ME_SendRequestResize(editor, FALSE); |
| |
| return 0; |
| } |
| |
| |
| typedef struct tagME_RTFStringStreamStruct |
| { |
| char *string; |
| int pos; |
| int length; |
| } ME_RTFStringStreamStruct; |
| |
| static DWORD CALLBACK ME_ReadFromRTFString(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb) |
| { |
| ME_RTFStringStreamStruct *pStruct = (ME_RTFStringStreamStruct *)dwCookie; |
| int count; |
| |
| count = min(cb, pStruct->length - pStruct->pos); |
| memmove(lpBuff, pStruct->string + pStruct->pos, count); |
| pStruct->pos += count; |
| *pcb = count; |
| return 0; |
| } |
| |
| static void |
| ME_StreamInRTFString(ME_TextEditor *editor, BOOL selection, char *string) |
| { |
| EDITSTREAM es; |
| ME_RTFStringStreamStruct data; |
| |
| data.string = string; |
| data.length = strlen(string); |
| data.pos = 0; |
| es.dwCookie = (DWORD)&data; |
| es.pfnCallback = ME_ReadFromRTFString; |
| ME_StreamIn(editor, SF_RTF | (selection ? SFF_SELECTION : 0), &es); |
| } |
| |
| |
| ME_DisplayItem * |
| ME_FindItemAtOffset(ME_TextEditor *editor, ME_DIType nItemType, int nOffset, int *nItemOffset) |
| { |
| ME_DisplayItem *item = ME_FindItemFwd(editor->pBuffer->pFirst, diParagraph); |
| |
| while (item && item->member.para.next_para->member.para.nCharOfs <= nOffset) |
| item = ME_FindItemFwd(item, diParagraph); |
| |
| if (!item) |
| return item; |
| |
| nOffset -= item->member.para.nCharOfs; |
| if (nItemType == diParagraph) { |
| if (nItemOffset) |
| *nItemOffset = nOffset; |
| return item; |
| } |
| |
| do { |
| item = ME_FindItemFwd(item, diRun); |
| } while (item && (item->member.run.nCharOfs + ME_StrLen(item->member.run.strText) <= nOffset)); |
| if (item) { |
| nOffset -= item->member.run.nCharOfs; |
| if (nItemOffset) |
| *nItemOffset = nOffset; |
| } |
| return item; |
| } |
| |
| |
| static int |
| ME_FindText(ME_TextEditor *editor, DWORD flags, CHARRANGE *chrg, const WCHAR *text, CHARRANGE *chrgText) |
| { |
| const int nLen = lstrlenW(text); |
| const int nTextLen = ME_GetTextLength(editor); |
| int nStart, nEnd; |
| int nMin, nMax; |
| ME_DisplayItem *item; |
| ME_DisplayItem *para; |
| WCHAR wLastChar = ' '; |
| |
| TRACE("flags==0x%08x, chrg->cpMin==%d, chrg->cpMax==%d text==%s\n", |
| flags, chrg->cpMin, chrg->cpMax, debugstr_w(text)); |
| |
| if (flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD)) |
| FIXME("Flags 0x%08x not implemented\n", |
| flags & ~(FR_DOWN | FR_MATCHCASE | FR_WHOLEWORD)); |
| |
| nMin = chrg->cpMin; |
| if (chrg->cpMax == -1) |
| nMax = nTextLen; |
| else |
| nMax = chrg->cpMax > nTextLen ? nTextLen : chrg->cpMax; |
| |
| /* when searching up, if cpMin < cpMax, then instead of searching |
| * on [cpMin,cpMax], we search on [0,cpMin], otherwise, search on |
| * [cpMax, cpMin]. The exception is when cpMax is -1, in which |
| * case, it is always bigger than cpMin. |
| */ |
| if (!(flags & FR_DOWN)) |
| { |
| int nSwap = nMax; |
| |
| nMax = nMin > nTextLen ? nTextLen : nMin; |
| if (nMin < nSwap || chrg->cpMax == -1) |
| nMin = 0; |
| else |
| nMin = nSwap; |
| } |
| |
| if (!nLen || nMin < 0 || nMax < 0 || nMax < nMin) |
| { |
| if (chrgText) |
| chrgText->cpMin = chrgText->cpMax = -1; |
| return -1; |
| } |
| |
| if (flags & FR_DOWN) /* Forward search */ |
| { |
| /* If possible, find the character before where the search starts */ |
| if ((flags & FR_WHOLEWORD) && nMin) |
| { |
| nStart = nMin - 1; |
| item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart); |
| if (!item) |
| { |
| if (chrgText) |
| chrgText->cpMin = chrgText->cpMax = -1; |
| return -1; |
| } |
| wLastChar = item->member.run.strText->szData[nStart]; |
| } |
| |
| nStart = nMin; |
| item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart); |
| if (!item) |
| { |
| if (chrgText) |
| chrgText->cpMin = chrgText->cpMax = -1; |
| return -1; |
| } |
| |
| para = ME_GetParagraph(item); |
| while (item |
| && para->member.para.nCharOfs + item->member.run.nCharOfs + nStart + nLen <= nMax) |
| { |
| ME_DisplayItem *pCurItem = item; |
| int nCurStart = nStart; |
| int nMatched = 0; |
| |
| while (pCurItem && ME_CharCompare(pCurItem->member.run.strText->szData[nCurStart + nMatched], text[nMatched], (flags & FR_MATCHCASE))) |
| { |
| if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar)) |
| break; |
| |
| nMatched++; |
| if (nMatched == nLen) |
| { |
| ME_DisplayItem *pNextItem = pCurItem; |
| int nNextStart = nCurStart; |
| WCHAR wNextChar; |
| |
| /* Check to see if next character is a whitespace */ |
| if (flags & FR_WHOLEWORD) |
| { |
| if (nCurStart + nMatched == ME_StrLen(pCurItem->member.run.strText)) |
| { |
| pNextItem = ME_FindItemFwd(pCurItem, diRun); |
| nNextStart = -nMatched; |
| } |
| |
| if (pNextItem) |
| wNextChar = pNextItem->member.run.strText->szData[nNextStart + nMatched]; |
| else |
| wNextChar = ' '; |
| |
| if (isalnumW(wNextChar)) |
| break; |
| } |
| |
| nStart += para->member.para.nCharOfs + pCurItem->member.run.nCharOfs; |
| if (chrgText) |
| { |
| chrgText->cpMin = nStart; |
| chrgText->cpMax = nStart + nLen; |
| } |
| TRACE("found at %d-%d\n", nStart, nStart + nLen); |
| return nStart; |
| } |
| if (nCurStart + nMatched == ME_StrLen(pCurItem->member.run.strText)) |
| { |
| pCurItem = ME_FindItemFwd(pCurItem, diRun); |
| para = ME_GetParagraph(pCurItem); |
| nCurStart = -nMatched; |
| } |
| } |
| if (pCurItem) |
| wLastChar = pCurItem->member.run.strText->szData[nCurStart + nMatched]; |
| else |
| wLastChar = ' '; |
| |
| nStart++; |
| if (nStart == ME_StrLen(item->member.run.strText)) |
| { |
| item = ME_FindItemFwd(item, diRun); |
| para = ME_GetParagraph(item); |
| nStart = 0; |
| } |
| } |
| } |
| else /* Backward search */ |
| { |
| /* If possible, find the character after where the search ends */ |
| if ((flags & FR_WHOLEWORD) && nMax < nTextLen - 1) |
| { |
| nEnd = nMax + 1; |
| item = ME_FindItemAtOffset(editor, diRun, nEnd, &nEnd); |
| if (!item) |
| { |
| if (chrgText) |
| chrgText->cpMin = chrgText->cpMax = -1; |
| return -1; |
| } |
| wLastChar = item->member.run.strText->szData[nEnd]; |
| } |
| |
| nEnd = nMax; |
| item = ME_FindItemAtOffset(editor, diRun, nEnd, &nEnd); |
| if (!item) |
| { |
| if (chrgText) |
| chrgText->cpMin = chrgText->cpMax = -1; |
| return -1; |
| } |
| |
| para = ME_GetParagraph(item); |
| |
| while (item |
| && para->member.para.nCharOfs + item->member.run.nCharOfs + nEnd - nLen >= nMin) |
| { |
| ME_DisplayItem *pCurItem = item; |
| int nCurEnd = nEnd; |
| int nMatched = 0; |
| |
| if (nCurEnd - nMatched == 0) |
| { |
| pCurItem = ME_FindItemBack(pCurItem, diRun); |
| para = ME_GetParagraph(pCurItem); |
| nCurEnd = ME_StrLen(pCurItem->member.run.strText) + nMatched; |
| } |
| |
| while (pCurItem && ME_CharCompare(pCurItem->member.run.strText->szData[nCurEnd - nMatched - 1], text[nLen - nMatched - 1], (flags & FR_MATCHCASE))) |
| { |
| if ((flags & FR_WHOLEWORD) && isalnumW(wLastChar)) |
| break; |
| |
| nMatched++; |
| if (nMatched == nLen) |
| { |
| ME_DisplayItem *pPrevItem = pCurItem; |
| int nPrevEnd = nCurEnd; |
| WCHAR wPrevChar; |
| |
| /* Check to see if previous character is a whitespace */ |
| if (flags & FR_WHOLEWORD) |
| { |
| if (nPrevEnd - nMatched == 0) |
| { |
| pPrevItem = ME_FindItemBack(pCurItem, diRun); |
| if (pPrevItem) |
| nPrevEnd = ME_StrLen(pPrevItem->member.run.strText) + nMatched; |
| } |
| |
| if (pPrevItem) |
| wPrevChar = pPrevItem->member.run.strText->szData[nPrevEnd - nMatched - 1]; |
| else |
| wPrevChar = ' '; |
| |
| if (isalnumW(wPrevChar)) |
| break; |
| } |
| |
| nStart = para->member.para.nCharOfs + pCurItem->member.run.nCharOfs + nCurEnd - nMatched; |
| if (chrgText) |
| { |
| chrgText->cpMin = nStart; |
| chrgText->cpMax = nStart + nLen; |
| } |
| TRACE("found at %d-%d\n", nStart, nStart + nLen); |
| return nStart; |
| } |
| if (nCurEnd - nMatched == 0) |
| { |
| pCurItem = ME_FindItemBack(pCurItem, diRun); |
| /* Don't care about pCurItem becoming NULL here; it's already taken |
| * care of in the exterior loop condition */ |
| para = ME_GetParagraph(pCurItem); |
| nCurEnd = ME_StrLen(pCurItem->member.run.strText) + nMatched; |
| } |
| } |
| if (pCurItem) |
| wLastChar = pCurItem->member.run.strText->szData[nCurEnd - nMatched - 1]; |
| else |
| wLastChar = ' '; |
| |
| nEnd--; |
| if (nEnd < 0) |
| { |
| item = ME_FindItemBack(item, diRun); |
| para = ME_GetParagraph(item); |
| nEnd = ME_StrLen(item->member.run.strText); |
| } |
| } |
| } |
| TRACE("not found\n"); |
| if (chrgText) |
| chrgText->cpMin = chrgText->cpMax = -1; |
| return -1; |
| } |
| |
| |
| static BOOL |
| ME_KeyDown(ME_TextEditor *editor, WORD nKey) |
| { |
| BOOL ctrl_is_down = GetKeyState(VK_CONTROL) & 0x8000; |
| BOOL shift_is_down = GetKeyState(VK_SHIFT) & 0x8000; |
| |
| switch (nKey) |
| { |
| case VK_LEFT: |
| case VK_RIGHT: |
| case VK_UP: |
| case VK_DOWN: |
| case VK_HOME: |
| case VK_END: |
| case VK_PRIOR: |
| case VK_NEXT: |
| ME_ArrowKey(editor, nKey, shift_is_down, ctrl_is_down); |
| return TRUE; |
| case VK_BACK: |
| case VK_DELETE: |
| /* FIXME backspace and delete aren't the same, they act different wrt paragraph style of the merged paragraph */ |
| if (GetWindowLongW(editor->hWnd, GWL_STYLE) & ES_READONLY) |
| return FALSE; |
| if (ME_IsSelection(editor)) |
| ME_DeleteSelection(editor); |
| else if (nKey == VK_DELETE || ME_ArrowKey(editor, VK_LEFT, FALSE, FALSE)) |
| ME_DeleteTextAtCursor(editor, 1, 1); |
| else |
| return TRUE; |
| ME_CommitUndo(editor); |
| ME_UpdateRepaint(editor); |
| ME_SendRequestResize(editor, FALSE); |
| return TRUE; |
| |
| default: |
| if (ctrl_is_down) |
| { |
| if (nKey == 'W') |
| { |
| CHARFORMAT2W chf; |
| char buf[2048]; |
| chf.cbSize = sizeof(chf); |
| |
| ME_GetSelectionCharFormat(editor, &chf); |
| ME_DumpStyleToBuf(&chf, buf); |
| MessageBoxA(NULL, buf, "Style dump", MB_OK); |
| } |
| if (nKey == 'Q') |
| { |
| ME_CheckCharOffsets(editor); |
| } |
| } |
| } |
| return FALSE; |
| } |
| |
| static BOOL ME_ShowContextMenu(ME_TextEditor *editor, int x, int y) |
| { |
| CHARRANGE selrange; |
| HMENU menu; |
| int seltype = 0; |
| if(!editor->lpOleCallback) |
| return FALSE; |
| ME_GetSelection(editor, (int *)&selrange.cpMin, (int *)&selrange.cpMax); |
| if(selrange.cpMin == selrange.cpMax) |
| seltype |= SEL_EMPTY; |
| else |
| { |
| /* FIXME: Handle objects */ |
| seltype |= SEL_TEXT; |
| if(selrange.cpMax-selrange.cpMin > 1) |
| seltype |= SEL_MULTICHAR; |
| } |
| if(SUCCEEDED(IRichEditOleCallback_GetContextMenu(editor->lpOleCallback, seltype, NULL, &selrange, &menu))) |
| { |
| TrackPopupMenu(menu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, GetParent(editor->hWnd), NULL); |
| DestroyMenu(menu); |
| } |
| return TRUE; |
| } |
| |
| ME_TextEditor *ME_MakeEditor(HWND hWnd) { |
| ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor); |
| HDC hDC; |
| int i; |
| ed->hWnd = hWnd; |
| ed->bEmulateVersion10 = FALSE; |
| ed->pBuffer = ME_MakeText(); |
| hDC = GetDC(hWnd); |
| ME_MakeFirstParagraph(hDC, ed->pBuffer); |
| ReleaseDC(hWnd, hDC); |
| ed->bCaretShown = FALSE; |
| ed->nCursors = 2; |
| ed->pCursors = ALLOC_N_OBJ(ME_Cursor, ed->nCursors); |
| ed->pCursors[0].pRun = ME_FindItemFwd(ed->pBuffer->pFirst, diRun); |
| ed->pCursors[0].nOffset = 0; |
| ed->pCursors[1].pRun = ME_FindItemFwd(ed->pBuffer->pFirst, diRun); |
| ed->pCursors[1].nOffset = 0; |
| ed->nLastTotalLength = ed->nTotalLength = 0; |
| ed->nUDArrowX = -1; |
| ed->nSequence = 0; |
| ed->rgbBackColor = -1; |
| ed->hbrBackground = GetSysColorBrush(COLOR_WINDOW); |
| ed->bCaretAtEnd = FALSE; |
| ed->nEventMask = 0; |
| ed->nModifyStep = 0; |
| ed->nTextLimit = TEXT_LIMIT_DEFAULT; |
| ed->pUndoStack = ed->pRedoStack = ed->pUndoStackBottom = NULL; |
| ed->nUndoStackSize = 0; |
| ed->nUndoLimit = STACK_SIZE_DEFAULT; |
| ed->nUndoMode = umAddToUndo; |
| ed->nParagraphs = 1; |
| ed->nLastSelStart = ed->nLastSelEnd = 0; |
| ed->pLastSelStartPara = ed->pLastSelEndPara = ME_FindItemFwd(ed->pBuffer->pFirst, diParagraph); |
| ed->nZoomNumerator = ed->nZoomDenominator = 0; |
| ed->bRedraw = TRUE; |
| ed->bHideSelection = FALSE; |
| ed->nInvalidOfs = -1; |
| ed->pfnWordBreak = NULL; |
| ed->lpOleCallback = NULL; |
| ed->mode = TM_RICHTEXT | TM_MULTILEVELUNDO | TM_MULTICODEPAGE; |
| ed->AutoURLDetect_bEnable = FALSE; |
| GetClientRect(hWnd, &ed->rcFormat); |
| for (i=0; i<HFONT_CACHE_SIZE; i++) |
| { |
| ed->pFontCache[i].nRefs = 0; |
| ed->pFontCache[i].nAge = 0; |
| ed->pFontCache[i].hFont = NULL; |
| } |
| |
| ME_CheckCharOffsets(ed); |
| |
| if (GetWindowLongW(hWnd, GWL_STYLE) & ES_PASSWORD) |
| ed->cPasswordMask = '*'; |
| else |
| ed->cPasswordMask = 0; |
| |
| return ed; |
| } |
| |
| typedef struct tagME_GlobalDestStruct |
| { |
| HGLOBAL hData; |
| int nLength; |
| } ME_GlobalDestStruct; |
| |
| static DWORD CALLBACK ME_ReadFromHGLOBALUnicode(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb) |
| { |
| ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie; |
| int i; |
| WORD *pSrc, *pDest; |
| |
| cb = cb >> 1; |
| pDest = (WORD *)lpBuff; |
| pSrc = (WORD *)GlobalLock(pData->hData); |
| for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) { |
| pDest[i] = pSrc[pData->nLength+i]; |
| } |
| pData->nLength += i; |
| *pcb = 2*i; |
| GlobalUnlock(pData->hData); |
| return 0; |
| } |
| |
| static DWORD CALLBACK ME_ReadFromHGLOBALRTF(DWORD_PTR dwCookie, LPBYTE lpBuff, LONG cb, LONG *pcb) |
| { |
| ME_GlobalDestStruct *pData = (ME_GlobalDestStruct *)dwCookie; |
| int i; |
| BYTE *pSrc, *pDest; |
| |
| pDest = lpBuff; |
| pSrc = (BYTE *)GlobalLock(pData->hData); |
| for (i = 0; i<cb && pSrc[pData->nLength+i]; i++) { |
| pDest[i] = pSrc[pData->nLength+i]; |
| } |
| pData->nLength += i; |
| *pcb = i; |
| GlobalUnlock(pData->hData); |
| return 0; |
| } |
| |
| |
| void ME_DestroyEditor(ME_TextEditor *editor) |
| { |
| ME_DisplayItem *pFirst = editor->pBuffer->pFirst; |
| ME_DisplayItem *p = pFirst, *pNext = NULL; |
| int i; |
| |
| ME_ClearTempStyle(editor); |
| ME_EmptyUndoStack(editor); |
| while(p) { |
| pNext = p->next; |
| ME_DestroyDisplayItem(p); |
| p = pNext; |
| } |
| ME_ReleaseStyle(editor->pBuffer->pDefaultStyle); |
| for (i=0; i<HFONT_CACHE_SIZE; i++) |
| { |
| if (editor->pFontCache[i].hFont) |
| DeleteObject(editor->pFontCache[i].hFont); |
| } |
| DeleteObject(editor->hbrBackground); |
| if(editor->lpOleCallback) |
| IUnknown_Release(editor->lpOleCallback); |
| |
| FREE_OBJ(editor->pBuffer); |
| FREE_OBJ(editor->pCursors); |
| |
| FREE_OBJ(editor); |
| } |
| |
| BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) |
| { |
| TRACE("\n"); |
| switch (fdwReason) |
| { |
| case DLL_PROCESS_ATTACH: |
| DisableThreadLibraryCalls(hinstDLL); |
| me_heap = HeapCreate (0, 0x10000, 0); |
| if (!ME_RegisterEditorClass(hinstDLL)) return FALSE; |
| LookupInit(); |
| break; |
| |
| case DLL_PROCESS_DETACH: |
| UnregisterClassW(RichEdit20W, 0); |
| UnregisterClassW(RichEdit50W, 0); |
| UnregisterClassA("RichEdit20A", 0); |
| UnregisterClassA("RichEdit50A", 0); |
| if (ME_ListBoxRegistered) |
| UnregisterClassW(REListBox20W, 0); |
| if (ME_ComboBoxRegistered) |
| UnregisterClassW(REComboBox20W, 0); |
| LookupCleanup(); |
| HeapDestroy (me_heap); |
| me_heap = NULL; |
| break; |
| } |
| return TRUE; |
| } |
| |
| |
| #define UNSUPPORTED_MSG(e) \ |
| case e: \ |
| FIXME(#e ": stub\n"); \ |
| return DefWindowProcW(hWnd, msg, wParam, lParam); |
| |
| static const char * const edit_messages[] = { |
| "EM_GETSEL", |
| "EM_SETSEL", |
| "EM_GETRECT", |
| "EM_SETRECT", |
| "EM_SETRECTNP", |
| "EM_SCROLL", |
| "EM_LINESCROLL", |
| "EM_SCROLLCARET", |
| "EM_GETMODIFY", |
| "EM_SETMODIFY", |
| "EM_GETLINECOUNT", |
| "EM_LINEINDEX", |
| "EM_SETHANDLE", |
| "EM_GETHANDLE", |
| "EM_GETTHUMB", |
| "EM_UNKNOWN_BF", |
| "EM_UNKNOWN_C0", |
| "EM_LINELENGTH", |
| "EM_REPLACESEL", |
| "EM_UNKNOWN_C3", |
| "EM_GETLINE", |
| "EM_LIMITTEXT", |
| "EM_CANUNDO", |
| "EM_UNDO", |
| "EM_FMTLINES", |
| "EM_LINEFROMCHAR", |
| "EM_UNKNOWN_CA", |
| "EM_SETTABSTOPS", |
| "EM_SETPASSWORDCHAR", |
| "EM_EMPTYUNDOBUFFER", |
| "EM_GETFIRSTVISIBLELINE", |
| "EM_SETREADONLY", |
| "EM_SETWORDBREAKPROC", |
| "EM_GETWORDBREAKPROC", |
| "EM_GETPASSWORDCHAR", |
| "EM_SETMARGINS", |
| "EM_GETMARGINS", |
| "EM_GETLIMITTEXT", |
| "EM_POSFROMCHAR", |
| "EM_CHARFROMPOS" |
| }; |
| |
| static const char * const richedit_messages[] = { |
| "EM_CANPASTE", |
| "EM_DISPLAYBAND", |
| "EM_EXGETSEL", |
| "EM_EXLIMITTEXT", |
| "EM_EXLINEFROMCHAR", |
| "EM_EXSETSEL", |
| "EM_FINDTEXT", |
| "EM_FORMATRANGE", |
| "EM_GETCHARFORMAT", |
| "EM_GETEVENTMASK", |
| "EM_GETOLEINTERFACE", |
| "EM_GETPARAFORMAT", |
| "EM_GETSELTEXT", |
| "EM_HIDESELECTION", |
| "EM_PASTESPECIAL", |
| "EM_REQUESTRESIZE", |
| "EM_SELECTIONTYPE", |
| "EM_SETBKGNDCOLOR", |
| "EM_SETCHARFORMAT", |
| "EM_SETEVENTMASK", |
| "EM_SETOLECALLBACK", |
| "EM_SETPARAFORMAT", |
| "EM_SETTARGETDEVICE", |
| "EM_STREAMIN", |
| "EM_STREAMOUT", |
| "EM_GETTEXTRANGE", |
| "EM_FINDWORDBREAK", |
| "EM_SETOPTIONS", |
| "EM_GETOPTIONS", |
| "EM_FINDTEXTEX", |
| "EM_GETWORDBREAKPROCEX", |
| "EM_SETWORDBREAKPROCEX", |
| "EM_SETUNDOLIMIT", |
| "EM_UNKNOWN_USER_83", |
| "EM_REDO", |
| "EM_CANREDO", |
| "EM_GETUNDONAME", |
| "EM_GETREDONAME", |
| "EM_STOPGROUPTYPING", |
| "EM_SETTEXTMODE", |
| "EM_GETTEXTMODE", |
| "EM_AUTOURLDETECT", |
| "EM_GETAUTOURLDETECT", |
| "EM_SETPALETTE", |
| "EM_GETTEXTEX", |
| "EM_GETTEXTLENGTHEX", |
| "EM_SHOWSCROLLBAR", |
| "EM_SETTEXTEX", |
| "EM_UNKNOWN_USER_98", |
| "EM_UNKNOWN_USER_99", |
| "EM_SETPUNCTUATION", |
| "EM_GETPUNCTUATION", |
| "EM_SETWORDWRAPMODE", |
| "EM_GETWORDWRAPMODE", |
| "EM_SETIMECOLOR", |
| "EM_GETIMECOLOR", |
| "EM_SETIMEOPTIONS", |
| "EM_GETIMEOPTIONS", |
| "EM_CONVPOSITION", |
| "EM_UNKNOWN_USER_109", |
| "EM_UNKNOWN_USER_110", |
| "EM_UNKNOWN_USER_111", |
| "EM_UNKNOWN_USER_112", |
| "EM_UNKNOWN_USER_113", |
| "EM_UNKNOWN_USER_114", |
| "EM_UNKNOWN_USER_115", |
| "EM_UNKNOWN_USER_116", |
| "EM_UNKNOWN_USER_117", |
| "EM_UNKNOWN_USER_118", |
| "EM_UNKNOWN_USER_119", |
| "EM_SETLANGOPTIONS", |
| "EM_GETLANGOPTIONS", |
| "EM_GETIMECOMPMODE", |
| "EM_FINDTEXTW", |
| "EM_FINDTEXTEXW", |
| "EM_RECONVERSION", |
| "EM_SETIMEMODEBIAS", |
| "EM_GETIMEMODEBIAS" |
| }; |
| |
| static const char * |
| get_msg_name(UINT msg) |
| { |
| if (msg >= EM_GETSEL && msg <= EM_CHARFROMPOS) |
| return edit_messages[msg - EM_GETSEL]; |
| if (msg >= EM_CANPASTE && msg <= EM_GETIMEMODEBIAS) |
| return richedit_messages[msg - EM_CANPASTE]; |
| return ""; |
| } |
| |
| static LRESULT RichEditWndProc_common(HWND hWnd, UINT msg, WPARAM wParam, |
| LPARAM lParam, BOOL unicode) |
| { |
| ME_TextEditor *editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0); |
| |
| TRACE("hwnd %p msg %04x (%s) %x %lx, unicode %d\n", |
| hWnd, msg, get_msg_name(msg), wParam, lParam, unicode); |
| |
| if (!editor && msg != WM_NCCREATE && msg != WM_NCDESTROY) { |
| ERR("called with invalid hWnd %p - application bug?\n", hWnd); |
| return 0; |
| } |
| |
| switch(msg) { |
| |
| UNSUPPORTED_MSG(EM_DISPLAYBAND) |
| UNSUPPORTED_MSG(EM_FINDWORDBREAK) |
| UNSUPPORTED_MSG(EM_FMTLINES) |
| UNSUPPORTED_MSG(EM_FORMATRANGE) |
| UNSUPPORTED_MSG(EM_GETBIDIOPTIONS) |
| UNSUPPORTED_MSG(EM_GETEDITSTYLE) |
| UNSUPPORTED_MSG(EM_GETIMECOMPMODE) |
| /* UNSUPPORTED_MSG(EM_GETIMESTATUS) missing in Wine headers */ |
| UNSUPPORTED_MSG(EM_GETLANGOPTIONS) |
| /* UNSUPPORTED_MSG(EM_GETOLEINTERFACE) separate stub */ |
| UNSUPPORTED_MSG(EM_GETREDONAME) |
| UNSUPPORTED_MSG(EM_GETTEXTMODE) |
| UNSUPPORTED_MSG(EM_GETTYPOGRAPHYOPTIONS) |
| UNSUPPORTED_MSG(EM_GETUNDONAME) |
| UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX) |
| UNSUPPORTED_MSG(EM_PASTESPECIAL) |
| UNSUPPORTED_MSG(EM_SELECTIONTYPE) |
| UNSUPPORTED_MSG(EM_SETBIDIOPTIONS) |
| UNSUPPORTED_MSG(EM_SETEDITSTYLE) |
| UNSUPPORTED_MSG(EM_SETFONTSIZE) |
| UNSUPPORTED_MSG(EM_SETLANGOPTIONS) |
| UNSUPPORTED_MSG(EM_SETPALETTE) |
| UNSUPPORTED_MSG(EM_SETTABSTOPS) |
| UNSUPPORTED_MSG(EM_SETTARGETDEVICE) |
| UNSUPPORTED_MSG(EM_SETTYPOGRAPHYOPTIONS) |
| UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX) |
| UNSUPPORTED_MSG(WM_STYLECHANGING) |
| UNSUPPORTED_MSG(WM_STYLECHANGED) |
| /* UNSUPPORTED_MSG(WM_UNICHAR) FIXME missing in Wine headers */ |
| |
| /* Messages specific to Richedit controls */ |
| |
| case EM_STREAMIN: |
| return ME_StreamIn(editor, wParam, (EDITSTREAM*)lParam); |
| case EM_STREAMOUT: |
| return ME_StreamOut(editor, wParam, (EDITSTREAM *)lParam); |
| case WM_GETDLGCODE: |
| { |
| UINT code = DLGC_WANTCHARS|DLGC_WANTARROWS; |
| if(lParam && (((LPMSG)lParam)->message == WM_KEYDOWN)) |
| { |
| int vk = (int)((LPMSG)lParam)->wParam; |
| /* if style says we want return key */ |
| if((vk == VK_RETURN) && (GetWindowLongW(hWnd, GWL_STYLE) & ES_WANTRETURN)) |
| { |
| code |= DLGC_WANTMESSAGE; |
| } |
| /* we always handle ctrl-tab */ |
| if((vk == VK_TAB) && (GetKeyState(VK_CONTROL) & 0x8000)) |
| { |
| code |= DLGC_WANTMESSAGE; |
| } |
| } |
| return code; |
| } |
| case WM_NCCREATE: |
| { |
| CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam; |
| TRACE("WM_NCCREATE: style 0x%08x\n", pcs->style); |
| editor = ME_MakeEditor(hWnd); |
| SetWindowLongPtrW(hWnd, 0, (LONG_PTR)editor); |
| return TRUE; |
| } |
| case EM_EMPTYUNDOBUFFER: |
| ME_EmptyUndoStack(editor); |
| return 0; |
| case EM_GETSEL: |
| { |
| /* Note: wParam/lParam can be NULL */ |
| UINT from, to; |
| PUINT pfrom = wParam ? (PUINT)wParam : &from; |
| PUINT pto = lParam ? (PUINT)lParam : &to; |
| ME_GetSelection(editor, (int *)pfrom, (int *)pto); |
| if ((*pfrom|*pto) & 0xFFFF0000) |
| return -1; |
| return MAKELONG(*pfrom,*pto); |
| } |
| case EM_EXGETSEL: |
| { |
| CHARRANGE *pRange = (CHARRANGE *)lParam; |
| ME_GetSelection(editor, (int *)&pRange->cpMin, (int *)&pRange->cpMax); |
| TRACE("EM_EXGETSEL = (%d,%d)\n", pRange->cpMin, pRange->cpMax); |
| return 0; |
| } |
| case EM_SETUNDOLIMIT: |
| { |
| if ((int)wParam < 0) |
| editor->nUndoLimit = STACK_SIZE_DEFAULT; |
| else |
| editor->nUndoLimit = min(wParam, STACK_SIZE_MAX); |
| /* Setting a max stack size keeps wine from getting killed |
| for hogging memory. Windows allocates all this memory at once, so |
| no program would realistically set a value above our maxiumum. */ |
| return editor->nUndoLimit; |
| } |
| case EM_CANUNDO: |
| return editor->pUndoStack != NULL; |
| case EM_CANREDO: |
| return editor->pRedoStack != NULL; |
| case WM_UNDO: /* FIXME: actually not the same */ |
| case EM_UNDO: |
| ME_Undo(editor); |
| return 0; |
| case EM_REDO: |
| ME_Redo(editor); |
| return 0; |
| case EM_GETOPTIONS: |
| { |
| /* these flags are equivalent to the ES_* counterparts */ |
| DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL | |
| ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN; |
| DWORD settings = GetWindowLongW(hWnd, GWL_STYLE) & mask; |
| |
| return settings; |
| } |
| case EM_SETOPTIONS: |
| { |
| /* these flags are equivalent to ES_* counterparts |
| * ECO_READONLY is already implemented in the code, only requires |
| * setting the bit to work |
| */ |
| DWORD mask = ECO_VERTICAL | ECO_AUTOHSCROLL | ECO_AUTOVSCROLL | |
| ECO_NOHIDESEL | ECO_READONLY | ECO_WANTRETURN; |
| DWORD raw = GetWindowLongW(hWnd, GWL_STYLE); |
| DWORD settings = mask & raw; |
| |
| switch(wParam) |
| { |
| case ECOOP_SET: |
| settings = lParam; |
| break; |
| case ECOOP_OR: |
| settings |= lParam; |
| break; |
| case ECOOP_AND: |
| settings &= lParam; |
| break; |
| case ECOOP_XOR: |
| settings ^= lParam; |
| } |
| SetWindowLongW(hWnd, GWL_STYLE, (raw & ~mask) | (settings & mask)); |
| |
| if (lParam & ECO_AUTOWORDSELECTION) |
| FIXME("ECO_AUTOWORDSELECTION not implemented yet!\n"); |
| if (lParam & ECO_SELECTIONBAR) |
| FIXME("ECO_SELECTIONBAR not implemented yet!\n"); |
| if (lParam & ECO_VERTICAL) |
| FIXME("ECO_VERTICAL not implemented yet!\n"); |
| if (lParam & ECO_AUTOHSCROLL) |
| FIXME("ECO_AUTOHSCROLL not implemented yet!\n"); |
| if (lParam & ECO_AUTOVSCROLL) |
| FIXME("ECO_AUTOVSCROLL not implemented yet!\n"); |
| if (lParam & ECO_NOHIDESEL) |
| FIXME("ECO_NOHIDESEL not implemented yet!\n"); |
| if (lParam & ECO_WANTRETURN) |
| FIXME("ECO_WANTRETURN not implemented yet!\n"); |
| |
| return settings; |
| } |
| case EM_SETSEL: |
| { |
| ME_InvalidateSelection(editor); |
| ME_SetSelection(editor, wParam, lParam); |
| ME_InvalidateSelection(editor); |
| ME_SendSelChange(editor); |
| return 0; |
| } |
| case EM_SETSCROLLPOS: |
| { |
| POINT *point = (POINT *)lParam; |
| ME_ScrollAbs(editor, point->y); |
| return 0; |
| } |
| case EM_AUTOURLDETECT: |
| { |
| if (wParam==1 || wParam ==0) |
| { |
| editor->AutoURLDetect_bEnable = (BOOL)wParam; |
| return 0; |
| } |
| return E_INVALIDARG; |
| } |
| case EM_GETAUTOURLDETECT: |
| { |
| return editor->AutoURLDetect_bEnable; |
| } |
| case EM_EXSETSEL: |
| { |
| int end; |
| CHARRANGE range = *(CHARRANGE *)lParam; |
| |
| TRACE("EM_EXSETSEL (%d,%d)\n", range.cpMin, range.cpMax); |
| |
| ME_InvalidateSelection(editor); |
| end = ME_SetSelection(editor, range.cpMin, range.cpMax); |
| ME_InvalidateSelection(editor); |
| ME_SendSelChange(editor); |
| |
| return end; |
| } |
| case EM_SHOWSCROLLBAR: |
| { |
| ShowScrollBar(editor->hWnd, wParam, lParam); |
| return 0; |
| } |
| case EM_SETTEXTEX: |
| { |
| LPWSTR wszText = (LPWSTR)lParam; |
| SETTEXTEX *pStruct = (SETTEXTEX *)wParam; |
| size_t len = wszText ? lstrlenW(wszText) : 0; |
| int from, to; |
| ME_Style *style; |
| int oldModify = editor->nModifyStep; |
| TRACE("EM_SETTEXTEX - %s, flags %d, cp %d\n", debugstr_w(wszText), (int)pStruct->flags, pStruct->codepage); |
| if (pStruct->codepage != 1200) { |
| FIXME("EM_SETTEXTEX only supports unicode right now!\n"); |
| return 0; |
| } |
| /* FIXME: this should support RTF strings too, according to MSDN */ |
| if (pStruct->flags & ST_SELECTION) { |
| ME_GetSelection(editor, &from, &to); |
| style = ME_GetSelectionInsertStyle(editor); |
| ME_InternalDeleteText(editor, from, to - from); |
| ME_InsertTextFromCursor(editor, 0, wszText, len, style); |
| ME_ReleaseStyle(style); |
| } |
| else { |
| ME_InternalDeleteText(editor, 0, ME_GetTextLength(editor)); |
| ME_InsertTextFromCursor(editor, 0, wszText, len, editor->pBuffer->pDefaultStyle); |
| len = 1; |
| } |
| ME_CommitUndo(editor); |
| if (!(pStruct->flags & ST_KEEPUNDO)) |
| { |
| editor->nModifyStep = oldModify; |
| ME_EmptyUndoStack(editor); |
| } |
| ME_UpdateRepaint(editor); |
| return len; |
| } |
| case EM_SETBKGNDCOLOR: |
| { |
| LRESULT lColor = ME_GetBackColor(editor); |
| if (editor->rgbBackColor != -1) |
| DeleteObject(editor->hbrBackground); |
| if (wParam) |
| { |
| editor->rgbBackColor = -1; |
| editor->hbrBackground = GetSysColorBrush(COLOR_WINDOW); |
| } |
| else |
| { |
| editor->rgbBackColor = lParam; |
| editor->hbrBackground = CreateSolidBrush(editor->rgbBackColor); |
| } |
| if (editor->bRedraw) |
| { |
| InvalidateRect(hWnd, NULL, TRUE); |
| UpdateWindow(hWnd); |
| } |
| return lColor; |
| } |
| case EM_GETMODIFY: |
| return editor->nModifyStep == 0 ? 0 : 1; |
| case EM_SETMODIFY: |
| { |
| if (wParam) |
| editor->nModifyStep = 1; |
| else |
| editor->nModifyStep = 0; |
| |
| return 0; |
| } |
| case EM_SETREADONLY: |
| { |
| long nStyle = GetWindowLongW(hWnd, GWL_STYLE); |
| if (wParam) |
| nStyle |= ES_READONLY; |
| else |
| nStyle &= ~ES_READONLY; |
| SetWindowLongW(hWnd, GWL_STYLE, nStyle); |
| return 0; |
| } |
| case EM_SETEVENTMASK: |
| { |
| DWORD nOldMask = editor->nEventMask; |
| |
| editor->nEventMask = lParam; |
| return nOldMask; |
| } |
| case EM_GETEVENTMASK: |
| return editor->nEventMask; |
| case EM_SETCHARFORMAT: |
| { |
| CHARFORMAT2W buf, *p; |
| BOOL bRepaint = TRUE; |
| p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam); |
| if (!wParam || (editor->mode & TM_PLAINTEXT)) |
| ME_SetDefaultCharFormat(editor, p); |
| else if (wParam == (SCF_WORD | SCF_SELECTION)) |
| FIXME("EM_SETCHARFORMAT: word selection not supported\n"); |
| else if (wParam == SCF_ALL) |
| ME_SetCharFormat(editor, 0, ME_GetTextLength(editor), p); |
| else { |
| int from, to; |
| ME_GetSelection(editor, &from, &to); |
| bRepaint = (from != to); |
| ME_SetSelectionCharFormat(editor, p); |
| } |
| editor->nModifyStep = 1; |
| ME_CommitUndo(editor); |
| if (bRepaint) |
| ME_RewrapRepaint(editor); |
| return 0; |
| } |
| case EM_GETCHARFORMAT: |
| { |
| CHARFORMAT2W tmp, *dst = (CHARFORMAT2W *)lParam; |
| if (dst->cbSize != sizeof(CHARFORMATA) && |
| dst->cbSize != sizeof(CHARFORMATW) && |
| dst->cbSize != sizeof(CHARFORMAT2A) && |
| dst->cbSize != sizeof(CHARFORMAT2W)) |
| return 0; |
| tmp.cbSize = sizeof(tmp); |
| if (!wParam) |
| ME_GetDefaultCharFormat(editor, &tmp); |
| else |
| ME_GetSelectionCharFormat(editor, &tmp); |
| ME_CopyToCFAny(dst, &tmp); |
| return tmp.dwMask; |
| } |
| case EM_SETPARAFORMAT: |
| ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam); |
| ME_RewrapRepaint(editor); |
| ME_CommitUndo(editor); |
| return 0; |
| case EM_GETPARAFORMAT: |
| ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam); |
| return 0; |
| case EM_GETFIRSTVISIBLELINE: |
| { |
| ME_DisplayItem *p = editor->pBuffer->pFirst; |
| int y = ME_GetYScrollPos(editor); |
| int ypara = 0; |
| int count = 0; |
| int ystart, yend; |
| while(p) { |
| p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd); |
| if (p->type == diTextEnd) |
| break; |
| if (p->type == diParagraph) { |
| ypara = p->member.para.nYPos; |
| continue; |
| } |
| ystart = ypara + p->member.row.nYPos; |
| yend = ystart + p->member.row.nHeight; |
| if (y < yend) { |
| break; |
| } |
| count++; |
| } |
| return count; |
| } |
| case EM_HIDESELECTION: |
| { |
| editor->bHideSelection = (wParam != 0); |
| ME_InvalidateSelection(editor); |
| return 0; |
| } |
| case EM_LINESCROLL: |
| { |
| ME_ScrollDown(editor, lParam * 8); /* FIXME follow the original */ |
| return TRUE; /* Should return false if a single line richedit control */ |
| } |
| case WM_CLEAR: |
| { |
| int from, to; |
| ME_GetSelection(editor, &from, &to); |
| ME_InternalDeleteText(editor, from, to-from); |
| ME_CommitUndo(editor); |
| ME_UpdateRepaint(editor); |
| return 0; |
| } |
| case EM_REPLACESEL: |
| { |
| int from, to; |
| ME_Style *style; |
| LPWSTR wszText = ME_ToUnicode(unicode, (void *)lParam); |
| size_t len = lstrlenW(wszText); |
| TRACE("EM_REPLACESEL - %s\n", debugstr_w(wszText)); |
| |
| ME_GetSelection(editor, &from, &to); |
| style = ME_GetSelectionInsertStyle(editor); |
| ME_InternalDeleteText(editor, from, to-from); |
| ME_InsertTextFromCursor(editor, 0, wszText, len, style); |
| ME_ReleaseStyle(style); |
| /* drop temporary style if line end */ |
| /* |
| * FIXME question: does abc\n mean: put abc, |
| * clear temp style, put \n? (would require a change) |
| */ |
| if (len>0 && wszText[len-1] == '\n') |
| ME_ClearTempStyle(editor); |
| ME_EndToUnicode(unicode, wszText); |
| ME_CommitUndo(editor); |
| if (!wParam) |
| ME_EmptyUndoStack(editor); |
| ME_UpdateRepaint(editor); |
| return 0; |
| } |
| case EM_SCROLLCARET: |
| { |
| int top, bottom; /* row's edges relative to document top */ |
| int nPos; |
| ME_DisplayItem *para, *row; |
| |
| nPos = ME_GetYScrollPos(editor); |
| row = ME_RowStart(editor->pCursors[0].pRun); |
| para = ME_GetParagraph(row); |
| top = para->member.para.nYPos + row->member.row.nYPos; |
| bottom = top + row->member.row.nHeight; |
| |
| if (top < nPos) /* caret above window */ |
| ME_ScrollAbs(editor, top); |
| else if (nPos + editor->sizeWindow.cy < bottom) /*below*/ |
| ME_ScrollAbs(editor, bottom - editor->sizeWindow.cy); |
| return 0; |
| } |
| case WM_SETFONT: |
| { |
| LOGFONTW lf; |
| CHARFORMAT2W fmt; |
| HDC hDC; |
| BOOL bRepaint = LOWORD(lParam); |
| |
| if (!wParam) |
| wParam = (WPARAM)GetStockObject(SYSTEM_FONT); |
| GetObjectW((HGDIOBJ)wParam, sizeof(LOGFONTW), &lf); |
| hDC = GetDC(hWnd); |
| ME_CharFormatFromLogFont(hDC, &lf, &fmt); |
| ReleaseDC(hWnd, hDC); |
| ME_SetCharFormat(editor, 0, ME_GetTextLength(editor), &fmt); |
| ME_SetDefaultCharFormat(editor, &fmt); |
| |
| ME_CommitUndo(editor); |
| if (bRepaint) |
| ME_RewrapRepaint(editor); |
| return 0; |
| } |
| case WM_SETTEXT: |
| { |
| ME_InternalDeleteText(editor, 0, ME_GetTextLength(editor)); |
| if (lParam) |
| { |
| TRACE("WM_SETTEXT lParam==%lx\n",lParam); |
| if (!unicode && !strncmp((char *)lParam, "{\\rtf", 5)) |
| { |
| /* Undocumented: WM_SETTEXT supports RTF text */ |
| ME_StreamInRTFString(editor, 0, (char *)lParam); |
| } |
| else |
| { |
| LPWSTR wszText = ME_ToUnicode(unicode, (void *)lParam); |
| TRACE("WM_SETTEXT - %s\n", debugstr_w(wszText)); /* debugstr_w() */ |
| if (lstrlenW(wszText) > 0) |
| { |
| /* uses default style! */ |
| ME_InsertTextFromCursor(editor, 0, wszText, -1, editor->pBuffer->pDefaultStyle); |
| } |
| ME_EndToUnicode(unicode, wszText); |
| } |
| } |
| else |
| TRACE("WM_SETTEXT - NULL\n"); |
| ME_CommitUndo(editor); |
| ME_EmptyUndoStack(editor); |
| ME_SetSelection(editor, 0, 0); |
| ME_UpdateRepaint(editor); |
| return 1; |
| } |
| case EM_CANPASTE: |
| { |
| UINT nRTFFormat = RegisterClipboardFormatA("Rich Text Format"); |
| if (IsClipboardFormatAvailable(nRTFFormat)) |
| return TRUE; |
| if (IsClipboardFormatAvailable(CF_UNICODETEXT)) |
| return TRUE; |
| return FALSE; |
| } |
| case WM_PASTE: |
| { |
| DWORD dwFormat = 0; |
| EDITSTREAM es; |
| ME_GlobalDestStruct gds; |
| UINT nRTFFormat = RegisterClipboardFormatA("Rich Text Format"); |
| UINT cf = 0; |
| |
| if (IsClipboardFormatAvailable(nRTFFormat)) |
| cf = nRTFFormat, dwFormat = SF_RTF; |
| else if (IsClipboardFormatAvailable(CF_UNICODETEXT)) |
| cf = CF_UNICODETEXT, dwFormat = SF_TEXT|SF_UNICODE; |
| else |
| return 0; |
| |
| if (!OpenClipboard(hWnd)) |
| return 0; |
| gds.hData = GetClipboardData(cf); |
| gds.nLength = 0; |
| es.dwCookie = (DWORD)&gds; |
| es.pfnCallback = dwFormat == SF_RTF ? ME_ReadFromHGLOBALRTF : ME_ReadFromHGLOBALUnicode; |
| ME_StreamIn(editor, dwFormat|SFF_SELECTION, &es); |
| |
| CloseClipboard(); |
| return 0; |
| } |
| case WM_CUT: |
| case WM_COPY: |
| { |
| LPDATAOBJECT dataObj = NULL; |
| CHARRANGE range; |
| HRESULT hr = S_OK; |
| |
| if (editor->cPasswordMask) |
| return 0; /* Copying or Cutting masked text isn't allowed */ |
| |
| ME_GetSelection(editor, (int*)&range.cpMin, (int*)&range.cpMax); |
| if(editor->lpOleCallback) |
| hr = IRichEditOleCallback_GetClipboardData(editor->lpOleCallback, &range, RECO_COPY, &dataObj); |
| if(FAILED(hr) || !dataObj) |
| hr = ME_GetDataObject(editor, &range, &dataObj); |
| if(SUCCEEDED(hr)) { |
| hr = OleSetClipboard(dataObj); |
| IDataObject_Release(dataObj); |
| } |
| if (SUCCEEDED(hr) && msg == WM_CUT) |
| { |
| ME_InternalDeleteText(editor, range.cpMin, range.cpMax-range.cpMin); |
| ME_CommitUndo(editor); |
| ME_UpdateRepaint(editor); |
| } |
| return 0; |
| } |
| case WM_GETTEXTLENGTH: |
| return ME_GetTextLength(editor); |
| case EM_GETTEXTLENGTHEX: |
| return ME_GetTextLengthEx(editor, (GETTEXTLENGTHEX *)wParam); |
| case WM_GETTEXT: |
| { |
| TEXTRANGEW tr; /* W and A differ only by rng->lpstrText */ |
| tr.chrg.cpMin = 0; |
| tr.chrg.cpMax = wParam ? (wParam - 1) : 0; |
| tr.lpstrText = (WCHAR *)lParam; |
| return RichEditWndProc_common(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr, unicode); |
| } |
| case EM_GETTEXTEX: |
| { |
| GETTEXTEX *ex = (GETTEXTEX*)wParam; |
| int nStart, nCount; |
| |
| if (ex->flags & ~(GT_SELECTION | GT_USECRLF)) |
| FIXME("GETTEXTEX flags 0x%08x not supported\n", ex->flags & ~(GT_SELECTION | GT_USECRLF)); |
| |
| if (ex->flags & GT_SELECTION) |
| { |
| ME_GetSelection(editor, &nStart, &nCount); |
| nCount -= nStart; |
| nCount = min(nCount, ex->cb - 1); |
| } |
| else |
| { |
| nStart = 0; |
| nCount = ex->cb - 1; |
| } |
| if (ex->codepage == 1200 || unicode) |
| { |
| nCount = min(nCount, ex->cb / sizeof(WCHAR) - 1); |
| return ME_GetTextW(editor, (LPWSTR)lParam, nStart, nCount, ex->flags & GT_USECRLF); |
| } |
| else |
| { |
| /* potentially each char may be a CR, why calculate the exact value with O(N) when |
| we can just take a bigger buffer? :) */ |
| int crlfmul = (ex->flags & GT_USECRLF) ? 2 : 1; |
| LPWSTR buffer = richedit_alloc((crlfmul*nCount + 1) * sizeof(WCHAR)); |
| DWORD buflen = ex->cb; |
| LRESULT rc; |
| DWORD flags = 0; |
| |
| buflen = ME_GetTextW(editor, buffer, nStart, nCount, ex->flags & GT_USECRLF); |
| rc = WideCharToMultiByte(ex->codepage, flags, buffer, -1, (LPSTR)lParam, ex->cb, ex->lpDefaultChar, ex->lpUsedDefaultChar); |
| |
| richedit_free(buffer); |
| return rc; |
| } |
| } |
| case EM_GETSELTEXT: |
| { |
| int from, to; |
| TEXTRANGEW tr; /* W and A differ only by rng->lpstrText */ |
| ME_GetSelection(editor, &from, &to); |
| tr.chrg.cpMin = from; |
| tr.chrg.cpMax = to; |
| tr.lpstrText = (WCHAR *)lParam; |
| return RichEditWndProc_common(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr, unicode); |
| } |
| case EM_GETSCROLLPOS: |
| { |
| POINT *point = (POINT *)lParam; |
| point->x = 0; /* FIXME side scrolling not implemented */ |
| point->y = ME_GetYScrollPos(editor); |
| return 1; |
| } |
| case EM_GETTEXTRANGE: |
| { |
| TEXTRANGEW *rng = (TEXTRANGEW *)lParam; |
| TRACE("EM_GETTEXTRANGE min=%d max=%d unicode=%d emul1.0=%d length=%d\n", |
| rng->chrg.cpMin, rng->chrg.cpMax, unicode, |
| editor->bEmulateVersion10, ME_GetTextLength(editor)); |
| if (unicode) |
| return ME_GetTextW(editor, rng->lpstrText, rng->chrg.cpMin, rng->chrg.cpMax-rng->chrg.cpMin, editor->bEmulateVersion10); |
| else |
| { |
| int nLen = rng->chrg.cpMax-rng->chrg.cpMin; |
| WCHAR *p = ALLOC_N_OBJ(WCHAR, nLen+1); |
| int nChars = ME_GetTextW(editor, p, rng->chrg.cpMin, nLen, editor->bEmulateVersion10); |
| /* FIXME this is a potential security hole (buffer overrun) |
| if you know more about wchar->mbyte conversion please explain |
| */ |
| WideCharToMultiByte(CP_ACP, 0, p, nChars+1, (char *)rng->lpstrText, nLen+1, NULL, NULL); |
| FREE_OBJ(p); |
| return nChars; |
| } |
| } |
| case EM_GETLINE: |
| { |
| ME_DisplayItem *run; |
| const unsigned int nMaxChars = *(WORD *) lParam; |
| unsigned int nEndChars, nCharsLeft = nMaxChars; |
| char *dest = (char *) lParam; |
| /* rich text editor 1.0 uses \r\n for line end, 2.0 uses just \r; |
| we need to know how if we have the extra \n or not */ |
| int nLF = editor->bEmulateVersion10; |
| |
| TRACE("EM_GETLINE: row=%d, nMaxChars=%d (%s)\n", (int) wParam, nMaxChars, |
| unicode ? "Unicode" : "Ansi"); |
| |
| run = ME_FindRowWithNumber(editor, wParam); |
| if (run == NULL) |
| return 0; |
| |
| while (nCharsLeft && (run = ME_FindItemFwd(run, diRunOrStartRow)) |
| && !(run->member.run.nFlags & MERF_ENDPARA)) |
| { |
| unsigned int nCopy; |
| ME_String *strText; |
| if (run->type != diRun) |
| break; |
| strText = run->member.run.strText; |
| nCopy = min(nCharsLeft, strText->nLen); |
| |
| if (unicode) |
| lstrcpynW((LPWSTR) dest, strText->szData, nCopy); |
| else |
| nCopy = WideCharToMultiByte(CP_ACP, 0, strText->szData, nCopy, dest, |
| nCharsLeft, NULL, NULL); |
| dest += nCopy * (unicode ? sizeof(WCHAR) : 1); |
| nCharsLeft -= nCopy; |
| } |
| |
| /* append \r\0 (or \r\n\0 in 1.0), space allowing */ |
| nEndChars = min(nCharsLeft, 2 + nLF); |
| nCharsLeft -= nEndChars; |
| if (unicode) |
| { |
| const WCHAR src[] = {'\r', '\0'}; |
| const WCHAR src10[] = {'\r', '\n', '\0'}; |
| lstrcpynW((LPWSTR) dest, nLF ? src10 : src, nEndChars); |
| } |
| else |
| lstrcpynA(dest, nLF ? "\r\n" : "\r", nEndChars); |
| |
| TRACE("EM_GETLINE: got %u bytes\n", nMaxChars - nCharsLeft); |
| |
| if (nEndChars == 2 + nLF) |
| return nMaxChars - nCharsLeft - 1; /* don't count \0 */ |
| else |
| return nMaxChars - nCharsLeft; |
| } |
| case EM_GETLINECOUNT: |
| { |
| ME_DisplayItem *item = editor->pBuffer->pFirst->next; |
| int nRows = 0; |
| |
| while (item != editor->pBuffer->pLast) |
| { |
| assert(item->type == diParagraph); |
| nRows += item->member.para.nRows; |
| item = item->member.para.next_para; |
| } |
| TRACE("EM_GETLINECOUNT: nRows==%d\n", nRows); |
| return max(1, nRows); |
| } |
| case EM_LINEFROMCHAR: |
| { |
| if (wParam == -1) |
| return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(editor, 1)); |
| else |
| return ME_RowNumberFromCharOfs(editor, wParam); |
| } |
| case EM_EXLINEFROMCHAR: |
| { |
| if (lParam == -1) |
| return ME_RowNumberFromCharOfs(editor, ME_GetCursorOfs(editor,1)); |
| else |
| return ME_RowNumberFromCharOfs(editor, lParam); |
| } |
| case EM_LINEINDEX: |
| { |
| ME_DisplayItem *item, *para; |
| int nCharOfs; |
| |
| if (wParam == -1) |
| item = ME_FindItemBack(editor->pCursors[0].pRun, diStartRow); |
| else |
| item = ME_FindRowWithNumber(editor, wParam); |
| if (!item) |
| return -1; |
| para = ME_GetParagraph(item); |
| item = ME_FindItemFwd(item, diRun); |
| nCharOfs = para->member.para.nCharOfs + item->member.run.nCharOfs; |
| TRACE("EM_LINEINDEX: nCharOfs==%d\n", nCharOfs); |
| return nCharOfs; |
| } |
| case EM_LINELENGTH: |
| { |
| ME_DisplayItem *item, *item_end; |
| int nChars = 0, nThisLineOfs = 0, nNextLineOfs = 0; |
| |
| if (wParam > ME_GetTextLength(editor)) |
| return 0; |
| if (wParam == -1) |
| { |
| FIXME("EM_LINELENGTH: returning number of unselected characters on lines with selection unsupported.\n"); |
| return 0; |
| } |
| item = ME_FindItemAtOffset(editor, diRun, wParam, NULL); |
| item = ME_RowStart(item); |
| nThisLineOfs = ME_CharOfsFromRunOfs(editor, ME_FindItemFwd(item, diRun), 0); |
| item_end = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd); |
| if (item_end->type == diStartRow) |
| nNextLineOfs = ME_CharOfsFromRunOfs(editor, ME_FindItemFwd(item_end, diRun), 0); |
| else |
| nNextLineOfs = ME_FindItemFwd(item, diParagraphOrEnd)->member.para.nCharOfs |
| - (editor->bEmulateVersion10?2:1); |
| nChars = nNextLineOfs - nThisLineOfs; |
| TRACE("EM_LINELENGTH(%d)==%d\n",wParam, nChars); |
| return nChars; |
| } |
| case EM_EXLIMITTEXT: |
| { |
| if ((int)lParam < 0) |
| return 0; |
| if (lParam == 0) |
| editor->nTextLimit = 65536; |
| else |
| editor->nTextLimit = (int) lParam; |
| return 0; |
| } |
| case EM_LIMITTEXT: |
| { |
| if (wParam == 0) |
| editor->nTextLimit = 65536; |
| else |
| editor->nTextLimit = (int) wParam; |
| return 0; |
| } |
| case EM_GETLIMITTEXT: |
| { |
| return editor->nTextLimit; |
| } |
| case EM_FINDTEXT: |
| { |
| FINDTEXTA *ft = (FINDTEXTA *)lParam; |
| int nChars = MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, NULL, 0); |
| WCHAR *tmp; |
| LRESULT r; |
| |
| if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL) |
| MultiByteToWideChar(CP_ACP, 0, ft->lpstrText, -1, tmp, nChars); |
| r = ME_FindText(editor, wParam, &ft->chrg, tmp, NULL); |
| FREE_OBJ( tmp ); |
| return r; |
| } |
| case EM_FINDTEXTEX: |
| { |
| FINDTEXTEXA *ex = (FINDTEXTEXA *)lParam; |
| int nChars = MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, NULL, 0); |
| WCHAR *tmp; |
| LRESULT r; |
| |
| if ((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL) |
| MultiByteToWideChar(CP_ACP, 0, ex->lpstrText, -1, tmp, nChars); |
| r = ME_FindText(editor, wParam, &ex->chrg, tmp, &ex->chrgText); |
| FREE_OBJ( tmp ); |
| return r; |
| } |
| case EM_FINDTEXTW: |
| { |
| FINDTEXTW *ft = (FINDTEXTW *)lParam; |
| return ME_FindText(editor, wParam, &ft->chrg, ft->lpstrText, NULL); |
| } |
| case EM_FINDTEXTEXW: |
| { |
| FINDTEXTEXW *ex = (FINDTEXTEXW *)lParam; |
| return ME_FindText(editor, wParam, &ex->chrg, ex->lpstrText, &ex->chrgText); |
| } |
| case EM_GETZOOM: |
| if (!wParam || !lParam) |
| return FALSE; |
| *(int *)wParam = editor->nZoomNumerator; |
| *(int *)lParam = editor->nZoomDenominator; |
| return TRUE; |
| case EM_SETZOOM: |
| return ME_SetZoom(editor, wParam, lParam); |
| case EM_CHARFROMPOS: |
| return ME_CharFromPos(editor, ((POINTL *)lParam)->x, ((POINTL *)lParam)->y); |
| case EM_POSFROMCHAR: |
| { |
| ME_DisplayItem *pRun; |
| int nCharOfs, nOffset, nLength; |
| POINTL pt = {0,0}; |
| |
| nCharOfs = wParam; |
| /* detect which API version we're dealing with */ |
| if (wParam >= 0x40000) |
| nCharOfs = lParam; |
| nLength = ME_GetTextLength(editor); |
| |
| if (nCharOfs < nLength) { |
| ME_RunOfsFromCharOfs(editor, nCharOfs, &pRun, &nOffset); |
| assert(pRun->type == diRun); |
| pt.y = pRun->member.run.pt.y; |
| pt.x = pRun->member.run.pt.x + ME_PointFromChar(editor, &pRun->member.run, nOffset); |
| pt.y += ME_GetParagraph(pRun)->member.para.nYPos; |
| } else { |
| pt.x = 0; |
| pt.y = editor->pBuffer->pLast->member.para.nYPos; |
| } |
| if (wParam >= 0x40000) { |
| *(POINTL *)wParam = pt; |
| } |
| return MAKELONG( pt.x, pt.y ); |
| } |
| case WM_CREATE: |
| if (GetWindowLongW(hWnd, GWL_STYLE) & WS_HSCROLL) |
| { /* Squelch the default horizontal scrollbar it would make */ |
| ShowScrollBar(editor->hWnd, SB_HORZ, FALSE); |
| } |
| ME_CommitUndo(editor); |
| ME_WrapMarkedParagraphs(editor); |
| ME_MoveCaret(editor); |
| return 0; |
| case WM_DESTROY: |
| ME_DestroyEditor(editor); |
| SetWindowLongPtrW(hWnd, 0, 0); |
| return 0; |
| case WM_LBUTTONDOWN: |
| SetFocus(hWnd); |
| ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)); |
| SetCapture(hWnd); |
| ME_LinkNotify(editor,msg,wParam,lParam); |
| break; |
| case WM_MOUSEMOVE: |
| if (GetCapture() == hWnd) |
| ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam)); |
| ME_LinkNotify(editor,msg,wParam,lParam); |
| break; |
| case WM_LBUTTONUP: |
| if (GetCapture() == hWnd) |
| ReleaseCapture(); |
| ME_LinkNotify(editor,msg,wParam,lParam); |
| break; |
| case WM_LBUTTONDBLCLK: |
| ME_LinkNotify(editor,msg,wParam,lParam); |
| ME_SelectWord(editor); |
| break; |
| case WM_CONTEXTMENU: |
| if (!ME_ShowContextMenu(editor, (short)LOWORD(lParam), (short)HIWORD(lParam))) |
| goto do_default; |
| break; |
| case WM_PAINT: |
| if (editor->bRedraw) |
| { |
| HDC hDC; |
| PAINTSTRUCT ps; |
| |
| hDC = BeginPaint(hWnd, &ps); |
| ME_PaintContent(editor, hDC, FALSE, &ps.rcPaint); |
| EndPaint(hWnd, &ps); |
| } |
| break; |
| case WM_SETFOCUS: |
| ME_ShowCaret(editor); |
| ME_SendOldNotify(editor, EN_SETFOCUS); |
| return 0; |
| case WM_KILLFOCUS: |
| ME_HideCaret(editor); |
| ME_SendOldNotify(editor, EN_KILLFOCUS); |
| return 0; |
| case WM_ERASEBKGND: |
| { |
| if (editor->bRedraw) |
| { |
| HDC hDC = (HDC)wParam; |
| RECT rc; |
| if (GetUpdateRect(hWnd,&rc,TRUE)) |
| { |
| FillRect(hDC, &rc, editor->hbrBackground); |
| } |
| } |
| return 1; |
| } |
| case WM_COMMAND: |
| TRACE("editor wnd command = %d\n", LOWORD(wParam)); |
| return 0; |
| case WM_KEYDOWN: |
| if (ME_KeyDown(editor, LOWORD(wParam))) |
| return 0; |
| goto do_default; |
| case WM_CHAR: |
| { |
| WCHAR wstr; |
| |
| if (unicode) |
| wstr = (WCHAR)wParam; |
| else |
| { |
| CHAR charA = wParam; |
| MultiByteToWideChar(CP_ACP, 0, &charA, 1, &wstr, 1); |
| } |
| if (editor->AutoURLDetect_bEnable) |
| ME_AutoURLDetect(editor, wstr); |
| |
| switch (wstr) |
| { |
| case 1: /* Ctrl-A */ |
| ME_SetSelection(editor, 0, -1); |
| return 0; |
| case 3: /* Ctrl-C */ |
| SendMessageW(editor->hWnd, WM_COPY, 0, 0); |
| return 0; |
| } |
| |
| if (GetWindowLongW(editor->hWnd, GWL_STYLE) & ES_READONLY) { |
| MessageBeep(MB_ICONERROR); |
| return 0; /* FIXME really 0 ? */ |
| } |
| |
| switch (wstr) |
| { |
| case 22: /* Ctrl-V */ |
| SendMessageW(editor->hWnd, WM_PASTE, 0, 0); |
| return 0; |
| case 24: /* Ctrl-X */ |
| SendMessageW(editor->hWnd, WM_CUT, 0, 0); |
| return 0; |
| case 25: /* Ctrl-Y */ |
| SendMessageW(editor->hWnd, EM_REDO, 0, 0); |
| return 0; |
| case 26: /* Ctrl-Z */ |
| SendMessageW(editor->hWnd, EM_UNDO, 0, 0); |
| return 0; |
| } |
| if (((unsigned)wstr)>=' ' || wstr=='\r' || wstr=='\t') { |
| /* FIXME maybe it would make sense to call EM_REPLACESEL instead ? */ |
| ME_Style *style = ME_GetInsertStyle(editor, 0); |
| ME_SaveTempStyle(editor); |
| ME_InsertTextFromCursor(editor, 0, &wstr, 1, style); |
| ME_ReleaseStyle(style); |
| ME_CommitUndo(editor); |
| ME_UpdateRepaint(editor); |
| } |
| return 0; |
| } |
| case EM_SCROLL: /* fall through */ |
| case WM_VSCROLL: |
| { |
| int origNPos; |
| int lineHeight; |
| |
| origNPos = ME_GetYScrollPos(editor); |
| lineHeight = 24; |
| |
| if (editor && editor->pBuffer && editor->pBuffer->pDefaultStyle) |
| lineHeight = editor->pBuffer->pDefaultStyle->tm.tmHeight; |
| if (lineHeight <= 0) lineHeight = 24; |
| |
| switch(LOWORD(wParam)) |
| { |
| case SB_LINEUP: |
| ME_ScrollUp(editor,lineHeight); |
| break; |
| case SB_LINEDOWN: |
| ME_ScrollDown(editor,lineHeight); |
| break; |
| case SB_PAGEUP: |
| ME_ScrollUp(editor,editor->sizeWindow.cy); |
| break; |
| case SB_PAGEDOWN: |
| ME_ScrollDown(editor,editor->sizeWindow.cy); |
| break; |
| case SB_THUMBTRACK: |
| case SB_THUMBPOSITION: |
| ME_ScrollAbs(editor,HIWORD(wParam)); |
| break; |
| } |
| if (msg == EM_SCROLL) |
| return 0x00010000 | (((ME_GetYScrollPos(editor) - origNPos)/lineHeight) & 0xffff); |
| break; |
| } |
| case WM_MOUSEWHEEL: |
| { |
| int gcWheelDelta; |
| UINT pulScrollLines; |
| |
| SystemParametersInfoW(SPI_GETWHEELSCROLLLINES,0, &pulScrollLines, 0); |
| gcWheelDelta = -GET_WHEEL_DELTA_WPARAM(wParam); |
| |
| if (abs(gcWheelDelta) >= WHEEL_DELTA && pulScrollLines) |
| { |
| /* FIXME follow the original */ |
| ME_ScrollDown(editor,pulScrollLines * (gcWheelDelta / WHEEL_DELTA) * 8); |
| } |
| break; |
| } |
| case EM_GETRECT: |
| { |
| *((RECT *)lParam) = editor->rcFormat; |
| return 0; |
| } |
| case EM_SETRECT: |
| case EM_SETRECTNP: |
| { |
| if (lParam) |
| { |
| RECT *rc = (RECT *)lParam; |
| |
| if (wParam) |
| { |
| editor->rcFormat.left += rc->left; |
| editor->rcFormat.top += rc->top; |
| editor->rcFormat.right += rc->right; |
| editor->rcFormat.bottom += rc->bottom; |
| } |
| else |
| { |
| editor->rcFormat = *rc; |
| } |
| } |
| else |
| { |
| GetClientRect(hWnd, &editor->rcFormat); |
| } |
| if (msg != EM_SETRECTNP) |
| ME_RewrapRepaint(editor); |
| return 0; |
| } |
| case EM_REQUESTRESIZE: |
| ME_SendRequestResize(editor, TRUE); |
| return 0; |
| case WM_SETREDRAW: |
| editor->bRedraw = wParam; |
| return 0; |
| case WM_SIZE: |
| { |
| GetClientRect(hWnd, &editor->rcFormat); |
| ME_RewrapRepaint(editor); |
| return DefWindowProcW(hWnd, msg, wParam, lParam); |
| } |
| case EM_GETOLEINTERFACE: |
| { |
| LPVOID *ppvObj = (LPVOID*) lParam; |
| return CreateIRichEditOle(editor, ppvObj); |
| } |
| case EM_GETPASSWORDCHAR: |
| { |
| return editor->cPasswordMask; |
| } |
| case EM_SETOLECALLBACK: |
| if(editor->lpOleCallback) |
| IUnknown_Release(editor->lpOleCallback); |
| editor->lpOleCallback = (LPRICHEDITOLECALLBACK)lParam; |
| if(editor->lpOleCallback) |
| IUnknown_AddRef(editor->lpOleCallback); |
| return TRUE; |
| case EM_GETWORDBREAKPROC: |
| return (LRESULT)editor->pfnWordBreak; |
| case EM_SETWORDBREAKPROC: |
| { |
| EDITWORDBREAKPROCW pfnOld = editor->pfnWordBreak; |
| |
| editor->pfnWordBreak = (EDITWORDBREAKPROCW)lParam; |
| return (LRESULT)pfnOld; |
| } |
| case EM_SETTEXTMODE: |
| { |
| LRESULT ret; |
| int mask = 0; |
| int changes = 0; |
| ret = RichEditWndProc_common(hWnd, WM_GETTEXTLENGTH, 0, 0, unicode); |
| if (!ret) |
| { |
| /*Check for valid wParam*/ |
| if ((((wParam & TM_RICHTEXT) && ((wParam & TM_PLAINTEXT) << 1))) || |
| (((wParam & TM_MULTILEVELUNDO) && ((wParam & TM_SINGLELEVELUNDO) << 1))) || |
| (((wParam & TM_MULTICODEPAGE) && ((wParam & TM_SINGLECODEPAGE) << 1)))) |
| return 1; |
| else |
| { |
| if (wParam & (TM_RICHTEXT | TM_PLAINTEXT)) |
| { |
| mask |= (TM_RICHTEXT | TM_PLAINTEXT); |
| changes |= (wParam & (TM_RICHTEXT | TM_PLAINTEXT)); |
| } |
| /*FIXME: Currently no support for undo level and code page options*/ |
| editor->mode = (editor->mode & (~mask)) | changes; |
| return 0; |
| } |
| } |
| return ret; |
| } |
| case EM_SETPASSWORDCHAR: |
| { |
| editor->cPasswordMask = wParam; |
| ME_RewrapRepaint(editor); |
| return 0; |
| } |
| default: |
| do_default: |
| return DefWindowProcW(hWnd, msg, wParam, lParam); |
| } |
| return 0L; |
| } |
| |
| static LRESULT WINAPI RichEditWndProcW(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| BOOL unicode = TRUE; |
| |
| /* Under Win9x RichEdit20W returns ANSI strings, see the tests. */ |
| if (msg == WM_GETTEXT && (GetVersion() & 0x80000000)) |
| unicode = FALSE; |
| |
| return RichEditWndProc_common(hWnd, msg, wParam, lParam, unicode); |
| } |
| |
| static LRESULT WINAPI RichEditWndProcA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| return RichEditWndProc_common(hWnd, msg, wParam, lParam, FALSE); |
| } |
| |
| /****************************************************************** |
| * RichEditANSIWndProc (RICHED20.10) |
| */ |
| LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| return RichEditWndProcA(hWnd, msg, wParam, lParam); |
| } |
| |
| /****************************************************************** |
| * RichEdit10ANSIWndProc (RICHED20.9) |
| */ |
| LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| LRESULT result; |
| |
| /* FIXME: this is NOT the same as 2.0 version */ |
| result = RichEditANSIWndProc(hWnd, msg, wParam, lParam); |
| if (msg == WM_NCCREATE) |
| { |
| ME_TextEditor *editor = (ME_TextEditor *)GetWindowLongPtrW(hWnd, 0); |
| |
| editor->bEmulateVersion10 = TRUE; |
| editor->pBuffer->pLast->member.para.nCharOfs = 2; |
| } |
| return result; |
| } |
| |
| void ME_SendOldNotify(ME_TextEditor *editor, int nCode) |
| { |
| HWND hWnd = editor->hWnd; |
| SendMessageA(GetParent(hWnd), WM_COMMAND, (nCode<<16)|GetWindowLongW(hWnd, GWLP_ID), (LPARAM)hWnd); |
| } |
| |
| void ME_LinkNotify(ME_TextEditor *editor, UINT msg, WPARAM wParam, LPARAM lParam) |
| { |
| int x,y; |
| ME_Cursor tmpCursor; |
| int nCharOfs; /* The start of the clicked text. Absolute character offset */ |
| |
| ME_Run *tmpRun; |
| |
| ENLINK info; |
| x = (short)LOWORD(lParam); |
| y = (short)HIWORD(lParam); |
| nCharOfs = ME_CharFromPos(editor, x, y); |
| if (nCharOfs < 0) return; |
| |
| ME_CursorFromCharOfs(editor, nCharOfs, &tmpCursor); |
| tmpRun = &tmpCursor.pRun->member.run; |
| |
| if ((tmpRun->style->fmt.dwMask & CFM_LINK) |
| && (tmpRun->style->fmt.dwEffects & CFE_LINK)) |
| { /* The clicked run has CFE_LINK set */ |
| info.nmhdr.hwndFrom = editor->hWnd; |
| info.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID); |
| info.nmhdr.code = EN_LINK; |
| info.msg = msg; |
| info.wParam = wParam; |
| info.lParam = lParam; |
| info.chrg.cpMin = ME_CharOfsFromRunOfs(editor,tmpCursor.pRun,0); |
| info.chrg.cpMax = info.chrg.cpMin + ME_StrVLen(tmpRun->strText); |
| SendMessageW(GetParent(editor->hWnd), WM_NOTIFY,info.nmhdr.idFrom, (LPARAM)&info); |
| } |
| } |
| |
| int ME_CountParagraphsBetween(ME_TextEditor *editor, int from, int to) |
| { |
| ME_DisplayItem *item = ME_FindItemFwd(editor->pBuffer->pFirst, diParagraph); |
| int i = 0; |
| |
| while(item && item->member.para.next_para->member.para.nCharOfs <= from) |
| item = item->member.para.next_para; |
| if (!item) |
| return 0; |
| while(item && item->member.para.next_para->member.para.nCharOfs <= to) { |
| item = item->member.para.next_para; |
| i++; |
| } |
| return i; |
| } |
| |
| |
| int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int nStart, int nChars, int bCRLF) |
| { |
| ME_DisplayItem *item = ME_FindItemAtOffset(editor, diRun, nStart, &nStart); |
| int nWritten = 0; |
| WCHAR *pStart = buffer; |
| |
| if (!item) { |
| *buffer = 0; |
| return 0; |
| } |
| |
| if (nStart) |
| { |
| int nLen = ME_StrLen(item->member.run.strText) - nStart; |
| if (nLen > nChars) |
| nLen = nChars; |
| CopyMemory(buffer, item->member.run.strText->szData + nStart, sizeof(WCHAR)*nLen); |
| nChars -= nLen; |
| nWritten += nLen; |
| if (!nChars) |
| return nWritten; |
| buffer += nLen; |
| nStart = 0; |
| item = ME_FindItemFwd(item, diRun); |
| } |
| |
| while(nChars && item) |
| { |
| int nLen = ME_StrLen(item->member.run.strText); |
| if (nLen > nChars) |
| nLen = nChars; |
| |
| if (item->member.run.nFlags & MERF_ENDPARA) |
| { |
| if (!ME_FindItemFwd(item, diRun)) |
| /* No '\r' is appended to the last paragraph. */ |
| nLen = 0; |
| else { |
| *buffer = '\r'; |
| if (bCRLF) |
| { |
| *(++buffer) = '\n'; |
| nWritten++; |
| } |
| assert(nLen == 1); |
| /* our end paragraph consists of 2 characters now */ |
| if (editor->bEmulateVersion10) |
| nChars--; |
| } |
| } |
| else |
| CopyMemory(buffer, item->member.run.strText->szData, sizeof(WCHAR)*nLen); |
| nChars -= nLen; |
| nWritten += nLen; |
| buffer += nLen; |
| |
| if (!nChars) |
| { |
| TRACE("nWritten=%d, actual=%d\n", nWritten, buffer-pStart); |
| *buffer = 0; |
| return nWritten; |
| } |
| item = ME_FindItemFwd(item, diRun); |
| } |
| *buffer = 0; |
| TRACE("nWritten=%d, actual=%d\n", nWritten, buffer-pStart); |
| return nWritten; |
| } |
| |
| static BOOL ME_RegisterEditorClass(HINSTANCE hInstance) |
| { |
| WNDCLASSW wcW; |
| WNDCLASSA wcA; |
| |
| wcW.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS; |
| wcW.lpfnWndProc = RichEditWndProcW; |
| wcW.cbClsExtra = 0; |
| wcW.cbWndExtra = sizeof(ME_TextEditor *); |
| wcW.hInstance = NULL; /* hInstance would register DLL-local class */ |
| wcW.hIcon = NULL; |
| wcW.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_IBEAM)); |
| wcW.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); |
| wcW.lpszMenuName = NULL; |
| |
| if (is_version_nt()) |
| { |
| wcW.lpszClassName = RichEdit20W; |
| if (!RegisterClassW(&wcW)) return FALSE; |
| wcW.lpszClassName = RichEdit50W; |
| if (!RegisterClassW(&wcW)) return FALSE; |
| } |
| else |
| { |
| /* WNDCLASSA/W have the same layout */ |
| wcW.lpszClassName = (LPCWSTR)"RichEdit20W"; |
| if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE; |
| wcW.lpszClassName = (LPCWSTR)"RichEdit50W"; |
| if (!RegisterClassA((WNDCLASSA *)&wcW)) return FALSE; |
| } |
| |
| wcA.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW | CS_GLOBALCLASS; |
| wcA.lpfnWndProc = RichEditWndProcA; |
| wcA.cbClsExtra = 0; |
| wcA.cbWndExtra = sizeof(ME_TextEditor *); |
| wcA.hInstance = NULL; /* hInstance would register DLL-local class */ |
| wcA.hIcon = NULL; |
| wcA.hCursor = LoadCursorW(NULL, MAKEINTRESOURCEW(IDC_IBEAM)); |
| wcA.hbrBackground = (HBRUSH)GetStockObject(NULL_BRUSH); |
| wcA.lpszMenuName = NULL; |
| wcA.lpszClassName = "RichEdit20A"; |
| if (!RegisterClassA(&wcA)) return FALSE; |
| wcA.lpszClassName = "RichEdit50A"; |
| if (!RegisterClassA(&wcA)) return FALSE; |
| |
| return TRUE; |
| } |
| |
| LRESULT WINAPI REComboWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { |
| /* FIXME: Not implemented */ |
| TRACE("hWnd %p msg %04x (%s) %08x %08lx\n", |
| hWnd, msg, get_msg_name(msg), wParam, lParam); |
| return DefWindowProcW(hWnd, msg, wParam, lParam); |
| } |
| |
| LRESULT WINAPI REListWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { |
| /* FIXME: Not implemented */ |
| TRACE("hWnd %p msg %04x (%s) %08x %08lx\n", |
| hWnd, msg, get_msg_name(msg), wParam, lParam); |
| return DefWindowProcW(hWnd, msg, wParam, lParam); |
| } |
| |
| /****************************************************************** |
| * REExtendedRegisterClass (RICHED20.8) |
| * |
| * FIXME undocumented |
| * Need to check for errors and implement controls and callbacks |
| */ |
| LRESULT WINAPI REExtendedRegisterClass(void) |
| { |
| WNDCLASSW wcW; |
| UINT result; |
| |
| FIXME("semi stub\n"); |
| |
| wcW.cbClsExtra = 0; |
| wcW.cbWndExtra = 4; |
| wcW.hInstance = NULL; |
| wcW.hIcon = NULL; |
| wcW.hCursor = NULL; |
| wcW.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); |
| wcW.lpszMenuName = NULL; |
| |
| if (!ME_ListBoxRegistered) |
| { |
| wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS; |
| wcW.lpfnWndProc = REListWndProc; |
| wcW.lpszClassName = REListBox20W; |
| if (RegisterClassW(&wcW)) ME_ListBoxRegistered = TRUE; |
| } |
| |
| if (!ME_ComboBoxRegistered) |
| { |
| wcW.style = CS_PARENTDC | CS_DBLCLKS | CS_GLOBALCLASS | CS_VREDRAW | CS_HREDRAW; |
| wcW.lpfnWndProc = REComboWndProc; |
| wcW.lpszClassName = REComboBox20W; |
| if (RegisterClassW(&wcW)) ME_ComboBoxRegistered = TRUE; |
| } |
| |
| result = 0; |
| if (ME_ListBoxRegistered) |
| result += 1; |
| if (ME_ComboBoxRegistered) |
| result += 2; |
| |
| return result; |
| } |
| |
| int ME_AutoURLDetect(ME_TextEditor *editor, WCHAR curChar) |
| { |
| struct prefix_s { |
| const char *text; |
| int length; |
| } prefixes[12] = { |
| {"http:", 5}, |
| {"file:", 6}, |
| {"mailto:", 8}, |
| {"ftp:", 5}, |
| {"https:", 7}, |
| {"gopher:", 8}, |
| {"nntp:", 6}, |
| {"prospero:", 10}, |
| {"telnet:", 8}, |
| {"news:", 6}, |
| {"wais:", 6}, |
| {"www.", 5} |
| }; |
| CHARRANGE ins_pt; |
| int curf_ef, link_ef, def_ef; |
| int cur_prefx, prefx_cnt; |
| int sel_min, sel_max; |
| int car_pos = 0; |
| int text_pos=-1; |
| int URLmin, URLmax = 0; |
| CHARRANGE url; |
| FINDTEXTA ft; |
| CHARFORMAT2W cur_format; |
| CHARFORMAT2W default_format; |
| CHARFORMAT2W link; |
| RichEditANSIWndProc(editor->hWnd, EM_EXGETSEL, (WPARAM) 0, (LPARAM) &ins_pt); |
| sel_min = ins_pt.cpMin; |
| sel_max = ins_pt.cpMax; |
| if (sel_min==sel_max) |
| car_pos = sel_min; |
| if (sel_min!=sel_max) |
| car_pos = ME_GetTextLength(editor)+1; |
| cur_format.cbSize = sizeof(cur_format); |
| default_format.cbSize = sizeof(default_format); |
| RichEditANSIWndProc(editor->hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &cur_format); |
| RichEditANSIWndProc(editor->hWnd, EM_GETCHARFORMAT, SCF_DEFAULT, (LPARAM) &default_format); |
| link.cbSize = sizeof(link); |
| link.dwMask = CFM_LINK; |
| link.dwEffects = CFE_LINK; |
| curf_ef = cur_format.dwEffects & link.dwEffects; |
| def_ef = default_format.dwEffects & link.dwEffects; |
| link_ef = link.dwEffects & link.dwEffects; |
| if (curf_ef == link_ef) |
| { |
| if( curChar == '\n' || curChar=='\r' || curChar==' ') |
| { |
| ME_SetSelection(editor, car_pos, car_pos); |
| RichEditANSIWndProc(editor->hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &default_format); |
| text_pos=-1; |
| return 0; |
| } |
| } |
| if (curf_ef == def_ef) |
| { |
| cur_prefx = 0; |
| prefx_cnt = (sizeof(prefixes)/sizeof(struct prefix_s))-1; |
| while (cur_prefx<=prefx_cnt) |
| { |
| if (text_pos == -1) |
| { |
| ft.lpstrText = prefixes[cur_prefx].text; |
| URLmin=max(0,(car_pos-prefixes[cur_prefx].length)); |
| URLmax=max(0, car_pos); |
| if ((car_pos == 0) && (ME_GetTextLength(editor) != 0)) |
| { |
| URLmax = ME_GetTextLength(editor)+1; |
| } |
| ft.chrg.cpMin = URLmin; |
| ft.chrg.cpMax = URLmax; |
| text_pos=RichEditANSIWndProc(editor->hWnd, EM_FINDTEXT, FR_DOWN, (LPARAM)&ft); |
| cur_prefx++; |
| } |
| if (text_pos != -1) |
| { |
| url.cpMin=text_pos; |
| url.cpMax=car_pos-1; |
| ME_SetCharFormat(editor, text_pos, (URLmax-text_pos), &link); |
| ME_RewrapRepaint(editor); |
| break; |
| } |
| } |
| } |
| return 0; |
| } |