Commit d488f3f1 authored by Krzysztof Foltman's avatar Krzysztof Foltman Committed by Alexandre Julliard

Initial implementation of riched20.

parent aa35787d
......@@ -19988,7 +19988,7 @@ MAKE_LIB_RULES=libs/Makelib.rules
MAKE_PROG_RULES=programs/Makeprog.rules
ac_config_files="$ac_config_files Make.rules dlls/Makedll.rules dlls/Maketest.rules libs/Makelib.rules programs/Makeprog.rules Makefile dlls/Makefile dlls/advapi32/Makefile dlls/advapi32/tests/Makefile dlls/advpack/Makefile dlls/advpack/tests/Makefile dlls/amstream/Makefile dlls/atl/Makefile dlls/avicap32/Makefile dlls/avifil32/Makefile dlls/cabinet/Makefile dlls/capi2032/Makefile dlls/cards/Makefile dlls/cfgmgr32/Makefile dlls/comcat/Makefile dlls/comctl32/Makefile dlls/comctl32/tests/Makefile dlls/commdlg/Makefile dlls/crtdll/Makefile dlls/crypt32/Makefile dlls/cryptdll/Makefile dlls/ctl3d/Makefile dlls/d3d8/Makefile dlls/d3d9/Makefile dlls/d3dim/Makefile dlls/d3drm/Makefile dlls/d3dx8/Makefile dlls/d3dxof/Makefile dlls/dbghelp/Makefile dlls/dciman32/Makefile dlls/ddraw/Makefile dlls/ddraw/tests/Makefile dlls/devenum/Makefile dlls/dinput/Makefile dlls/dinput8/Makefile dlls/dmband/Makefile dlls/dmcompos/Makefile dlls/dmime/Makefile dlls/dmloader/Makefile dlls/dmscript/Makefile dlls/dmstyle/Makefile dlls/dmsynth/Makefile dlls/dmusic/Makefile dlls/dmusic32/Makefile dlls/dplay/Makefile dlls/dplayx/Makefile dlls/dpnet/Makefile dlls/dpnhpast/Makefile dlls/dsound/Makefile dlls/dsound/tests/Makefile dlls/dswave/Makefile dlls/dxdiagn/Makefile dlls/dxerr8/Makefile dlls/dxerr9/Makefile dlls/dxguid/Makefile dlls/gdi/Makefile dlls/gdi/tests/Makefile dlls/glu32/Makefile dlls/glut32/Makefile dlls/hhctrl.ocx/Makefile dlls/iccvid/Makefile dlls/icmp/Makefile dlls/ifsmgr.vxd/Makefile dlls/imagehlp/Makefile dlls/imm32/Makefile dlls/iphlpapi/Makefile dlls/iphlpapi/tests/Makefile dlls/itss/Makefile dlls/kernel/Makefile dlls/kernel/tests/Makefile dlls/lzexpand/Makefile dlls/lzexpand/tests/Makefile dlls/mapi32/Makefile dlls/mapi32/tests/Makefile dlls/mlang/Makefile dlls/mlang/tests/Makefile dlls/mmdevldr.vxd/Makefile dlls/monodebg.vxd/Makefile dlls/mpr/Makefile dlls/msacm/Makefile dlls/msacm/imaadp32/Makefile dlls/msacm/msadp32/Makefile dlls/msacm/msg711/Makefile dlls/msacm/winemp3/Makefile dlls/msacm/tests/Makefile dlls/mscms/Makefile dlls/mscms/tests/Makefile dlls/msdmo/Makefile dlls/mshtml/Makefile dlls/msi/Makefile dlls/msi/tests/Makefile dlls/msimg32/Makefile dlls/msisys/Makefile dlls/msnet32/Makefile dlls/msrle32/Makefile dlls/msvcrt/Makefile dlls/msvcrt/tests/Makefile dlls/msvcrt20/Makefile dlls/msvcrt40/Makefile dlls/msvcrtd/Makefile dlls/msvcrtd/tests/Makefile dlls/msvidc32/Makefile dlls/msvideo/Makefile dlls/mswsock/Makefile dlls/netapi32/Makefile dlls/netapi32/tests/Makefile dlls/newdev/Makefile dlls/ntdll/Makefile dlls/ntdll/tests/Makefile dlls/odbc32/Makefile dlls/ole32/Makefile dlls/ole32/tests/Makefile dlls/oleacc/Makefile dlls/oleaut32/Makefile dlls/oleaut32/tests/Makefile dlls/olecli/Makefile dlls/oledlg/Makefile dlls/olepro32/Makefile dlls/olesvr/Makefile dlls/opengl32/Makefile dlls/psapi/Makefile dlls/psapi/tests/Makefile dlls/qcap/Makefile dlls/quartz/Makefile dlls/quartz/tests/Makefile dlls/rasapi32/Makefile dlls/richedit/Makefile dlls/rpcrt4/Makefile dlls/rpcrt4/tests/Makefile dlls/rsabase/Makefile dlls/rsabase/tests/Makefile dlls/rsaenh/Makefile dlls/rsaenh/tests/Makefile dlls/secur32/Makefile dlls/sensapi/Makefile dlls/serialui/Makefile dlls/setupapi/Makefile dlls/shdocvw/Makefile dlls/shell32/Makefile dlls/shell32/tests/Makefile dlls/shfolder/Makefile dlls/shlwapi/Makefile dlls/shlwapi/tests/Makefile dlls/snmpapi/Makefile dlls/stdole32.tlb/Makefile dlls/sti/Makefile dlls/strmiids/Makefile dlls/tapi32/Makefile dlls/ttydrv/Makefile dlls/twain/Makefile dlls/unicows/Makefile dlls/url/Makefile dlls/urlmon/Makefile dlls/urlmon/tests/Makefile dlls/user/Makefile dlls/user/tests/Makefile dlls/uuid/Makefile dlls/uxtheme/Makefile dlls/vdhcp.vxd/Makefile dlls/vdmdbg/Makefile dlls/version/Makefile dlls/version/tests/Makefile dlls/vmm.vxd/Makefile dlls/vnbt.vxd/Makefile dlls/vnetbios.vxd/Makefile dlls/vtdapi.vxd/Makefile dlls/vwin32.vxd/Makefile dlls/win32s/Makefile dlls/winaspi/Makefile dlls/wined3d/Makefile dlls/winedos/Makefile dlls/wineps/Makefile dlls/wininet/Makefile dlls/wininet/tests/Makefile dlls/winmm/Makefile dlls/winmm/joystick/Makefile dlls/winmm/mcianim/Makefile dlls/winmm/mciavi/Makefile dlls/winmm/mcicda/Makefile dlls/winmm/mciseq/Makefile dlls/winmm/mciwave/Makefile dlls/winmm/midimap/Makefile dlls/winmm/tests/Makefile dlls/winmm/wavemap/Makefile dlls/winmm/winealsa/Makefile dlls/winmm/winearts/Makefile dlls/winmm/wineaudioio/Makefile dlls/winmm/winejack/Makefile dlls/winmm/winenas/Makefile dlls/winmm/wineoss/Makefile dlls/winnls/Makefile dlls/winsock/Makefile dlls/winsock/tests/Makefile dlls/winspool/Makefile dlls/winspool/tests/Makefile dlls/wintab32/Makefile dlls/wintrust/Makefile dlls/wow32/Makefile dlls/wsock32/Makefile dlls/wtsapi32/Makefile dlls/x11drv/Makefile documentation/Makefile fonts/Makefile include/Makefile libs/Makefile libs/port/Makefile libs/unicode/Makefile libs/wine/Makefile libs/wpp/Makefile loader/Makefile programs/Makefile programs/avitools/Makefile programs/clock/Makefile programs/cmdlgtst/Makefile programs/control/Makefile programs/expand/Makefile programs/msiexec/Makefile programs/notepad/Makefile programs/progman/Makefile programs/regedit/Makefile programs/regsvr32/Makefile programs/rpcss/Makefile programs/rundll32/Makefile programs/start/Makefile programs/taskmgr/Makefile programs/uninstaller/Makefile programs/view/Makefile programs/wcmd/Makefile programs/wineboot/Makefile programs/winebrowser/Makefile programs/winecfg/Makefile programs/wineconsole/Makefile programs/winedbg/Makefile programs/winefile/Makefile programs/winemenubuilder/Makefile programs/winemine/Makefile programs/winepath/Makefile programs/winetest/Makefile programs/winevdm/Makefile programs/winhelp/Makefile programs/winver/Makefile server/Makefile tools/Makefile tools/widl/Makefile tools/winapi/Makefile tools/winebuild/Makefile tools/winedump/Makefile tools/winegcc/Makefile tools/wmc/Makefile tools/wrc/Makefile"
ac_config_files="$ac_config_files Make.rules dlls/Makedll.rules dlls/Maketest.rules libs/Makelib.rules programs/Makeprog.rules Makefile dlls/Makefile dlls/advapi32/Makefile dlls/advapi32/tests/Makefile dlls/advpack/Makefile dlls/advpack/tests/Makefile dlls/amstream/Makefile dlls/atl/Makefile dlls/avicap32/Makefile dlls/avifil32/Makefile dlls/cabinet/Makefile dlls/capi2032/Makefile dlls/cards/Makefile dlls/cfgmgr32/Makefile dlls/comcat/Makefile dlls/comctl32/Makefile dlls/comctl32/tests/Makefile dlls/commdlg/Makefile dlls/crtdll/Makefile dlls/crypt32/Makefile dlls/cryptdll/Makefile dlls/ctl3d/Makefile dlls/d3d8/Makefile dlls/d3d9/Makefile dlls/d3dim/Makefile dlls/d3drm/Makefile dlls/d3dx8/Makefile dlls/d3dxof/Makefile dlls/dbghelp/Makefile dlls/dciman32/Makefile dlls/ddraw/Makefile dlls/ddraw/tests/Makefile dlls/devenum/Makefile dlls/dinput/Makefile dlls/dinput8/Makefile dlls/dmband/Makefile dlls/dmcompos/Makefile dlls/dmime/Makefile dlls/dmloader/Makefile dlls/dmscript/Makefile dlls/dmstyle/Makefile dlls/dmsynth/Makefile dlls/dmusic/Makefile dlls/dmusic32/Makefile dlls/dplay/Makefile dlls/dplayx/Makefile dlls/dpnet/Makefile dlls/dpnhpast/Makefile dlls/dsound/Makefile dlls/dsound/tests/Makefile dlls/dswave/Makefile dlls/dxdiagn/Makefile dlls/dxerr8/Makefile dlls/dxerr9/Makefile dlls/dxguid/Makefile dlls/gdi/Makefile dlls/gdi/tests/Makefile dlls/glu32/Makefile dlls/glut32/Makefile dlls/hhctrl.ocx/Makefile dlls/iccvid/Makefile dlls/icmp/Makefile dlls/ifsmgr.vxd/Makefile dlls/imagehlp/Makefile dlls/imm32/Makefile dlls/iphlpapi/Makefile dlls/iphlpapi/tests/Makefile dlls/itss/Makefile dlls/kernel/Makefile dlls/kernel/tests/Makefile dlls/lzexpand/Makefile dlls/lzexpand/tests/Makefile dlls/mapi32/Makefile dlls/mapi32/tests/Makefile dlls/mlang/Makefile dlls/mlang/tests/Makefile dlls/mmdevldr.vxd/Makefile dlls/monodebg.vxd/Makefile dlls/mpr/Makefile dlls/msacm/Makefile dlls/msacm/imaadp32/Makefile dlls/msacm/msadp32/Makefile dlls/msacm/msg711/Makefile dlls/msacm/winemp3/Makefile dlls/msacm/tests/Makefile dlls/mscms/Makefile dlls/mscms/tests/Makefile dlls/msdmo/Makefile dlls/mshtml/Makefile dlls/msi/Makefile dlls/msi/tests/Makefile dlls/msimg32/Makefile dlls/msisys/Makefile dlls/msnet32/Makefile dlls/msrle32/Makefile dlls/msvcrt/Makefile dlls/msvcrt/tests/Makefile dlls/msvcrt20/Makefile dlls/msvcrt40/Makefile dlls/msvcrtd/Makefile dlls/msvcrtd/tests/Makefile dlls/msvidc32/Makefile dlls/msvideo/Makefile dlls/mswsock/Makefile dlls/netapi32/Makefile dlls/netapi32/tests/Makefile dlls/newdev/Makefile dlls/ntdll/Makefile dlls/ntdll/tests/Makefile dlls/odbc32/Makefile dlls/ole32/Makefile dlls/ole32/tests/Makefile dlls/oleacc/Makefile dlls/oleaut32/Makefile dlls/oleaut32/tests/Makefile dlls/olecli/Makefile dlls/oledlg/Makefile dlls/olepro32/Makefile dlls/olesvr/Makefile dlls/opengl32/Makefile dlls/psapi/Makefile dlls/psapi/tests/Makefile dlls/qcap/Makefile dlls/quartz/Makefile dlls/quartz/tests/Makefile dlls/rasapi32/Makefile dlls/riched20/Makefile dlls/richedit/Makefile dlls/rpcrt4/Makefile dlls/rpcrt4/tests/Makefile dlls/rsabase/Makefile dlls/rsabase/tests/Makefile dlls/rsaenh/Makefile dlls/rsaenh/tests/Makefile dlls/secur32/Makefile dlls/sensapi/Makefile dlls/serialui/Makefile dlls/setupapi/Makefile dlls/shdocvw/Makefile dlls/shell32/Makefile dlls/shell32/tests/Makefile dlls/shfolder/Makefile dlls/shlwapi/Makefile dlls/shlwapi/tests/Makefile dlls/snmpapi/Makefile dlls/stdole32.tlb/Makefile dlls/sti/Makefile dlls/strmiids/Makefile dlls/tapi32/Makefile dlls/ttydrv/Makefile dlls/twain/Makefile dlls/unicows/Makefile dlls/url/Makefile dlls/urlmon/Makefile dlls/urlmon/tests/Makefile dlls/user/Makefile dlls/user/tests/Makefile dlls/uuid/Makefile dlls/uxtheme/Makefile dlls/vdhcp.vxd/Makefile dlls/vdmdbg/Makefile dlls/version/Makefile dlls/version/tests/Makefile dlls/vmm.vxd/Makefile dlls/vnbt.vxd/Makefile dlls/vnetbios.vxd/Makefile dlls/vtdapi.vxd/Makefile dlls/vwin32.vxd/Makefile dlls/win32s/Makefile dlls/winaspi/Makefile dlls/wined3d/Makefile dlls/winedos/Makefile dlls/wineps/Makefile dlls/wininet/Makefile dlls/wininet/tests/Makefile dlls/winmm/Makefile dlls/winmm/joystick/Makefile dlls/winmm/mcianim/Makefile dlls/winmm/mciavi/Makefile dlls/winmm/mcicda/Makefile dlls/winmm/mciseq/Makefile dlls/winmm/mciwave/Makefile dlls/winmm/midimap/Makefile dlls/winmm/tests/Makefile dlls/winmm/wavemap/Makefile dlls/winmm/winealsa/Makefile dlls/winmm/winearts/Makefile dlls/winmm/wineaudioio/Makefile dlls/winmm/winejack/Makefile dlls/winmm/winenas/Makefile dlls/winmm/wineoss/Makefile dlls/winnls/Makefile dlls/winsock/Makefile dlls/winsock/tests/Makefile dlls/winspool/Makefile dlls/winspool/tests/Makefile dlls/wintab32/Makefile dlls/wintrust/Makefile dlls/wow32/Makefile dlls/wsock32/Makefile dlls/wtsapi32/Makefile dlls/x11drv/Makefile documentation/Makefile fonts/Makefile include/Makefile libs/Makefile libs/port/Makefile libs/unicode/Makefile libs/wine/Makefile libs/wpp/Makefile loader/Makefile programs/Makefile programs/avitools/Makefile programs/clock/Makefile programs/cmdlgtst/Makefile programs/control/Makefile programs/expand/Makefile programs/msiexec/Makefile programs/notepad/Makefile programs/progman/Makefile programs/regedit/Makefile programs/regsvr32/Makefile programs/rpcss/Makefile programs/rundll32/Makefile programs/start/Makefile programs/taskmgr/Makefile programs/uninstaller/Makefile programs/view/Makefile programs/wcmd/Makefile programs/wineboot/Makefile programs/winebrowser/Makefile programs/winecfg/Makefile programs/wineconsole/Makefile programs/winedbg/Makefile programs/winefile/Makefile programs/winemenubuilder/Makefile programs/winemine/Makefile programs/winepath/Makefile programs/winetest/Makefile programs/winevdm/Makefile programs/winhelp/Makefile programs/winver/Makefile server/Makefile tools/Makefile tools/widl/Makefile tools/winapi/Makefile tools/winebuild/Makefile tools/winedump/Makefile tools/winegcc/Makefile tools/wmc/Makefile tools/wrc/Makefile"
cat >confcache <<\_ACEOF
......@@ -20650,6 +20650,7 @@ do
"dlls/quartz/Makefile" ) CONFIG_FILES="$CONFIG_FILES dlls/quartz/Makefile" ;;
"dlls/quartz/tests/Makefile" ) CONFIG_FILES="$CONFIG_FILES dlls/quartz/tests/Makefile" ;;
"dlls/rasapi32/Makefile" ) CONFIG_FILES="$CONFIG_FILES dlls/rasapi32/Makefile" ;;
"dlls/riched20/Makefile" ) CONFIG_FILES="$CONFIG_FILES dlls/riched20/Makefile" ;;
"dlls/richedit/Makefile" ) CONFIG_FILES="$CONFIG_FILES dlls/richedit/Makefile" ;;
"dlls/rpcrt4/Makefile" ) CONFIG_FILES="$CONFIG_FILES dlls/rpcrt4/Makefile" ;;
"dlls/rpcrt4/tests/Makefile" ) CONFIG_FILES="$CONFIG_FILES dlls/rpcrt4/tests/Makefile" ;;
......
......@@ -1634,6 +1634,7 @@ dlls/qcap/Makefile
dlls/quartz/Makefile
dlls/quartz/tests/Makefile
dlls/rasapi32/Makefile
dlls/riched20/Makefile
dlls/richedit/Makefile
dlls/rpcrt4/Makefile
dlls/rpcrt4/tests/Makefile
......
......@@ -108,6 +108,7 @@ BASEDIRS = \
qcap \
quartz \
rasapi32 \
riched20 \
richedit \
rpcrt4 \
rsabase \
......@@ -351,6 +352,7 @@ SYMLINKS_SO = \
qcap.dll.so \
quartz.dll.so \
rasapi32.dll.so \
riched20.dll.so \
riched32.dll.so \
rpcrt4.dll.so \
rsabase.dll.so \
......@@ -781,6 +783,9 @@ rasapi32.dll.so: rasapi32/rasapi32.dll.so
rasapi16.dll.so : rasapi32.dll.so
$(RM) $@ && $(LN_S) rasapi32.dll.so $@
riched20.dll.so: riched20/riched20.dll.so
$(RM) $@ && $(LN_S) riched20/riched20.dll.so $@
riched32.dll.so: richedit/riched32.dll.so
$(RM) $@ && $(LN_S) richedit/riched32.dll.so $@
......@@ -1092,6 +1097,7 @@ IMPORT_LIBS = \
libqcap.$(IMPLIBEXT) \
libquartz.$(IMPLIBEXT) \
librasapi32.$(IMPLIBEXT) \
libriched20.$(IMPLIBEXT) \
libriched32.$(IMPLIBEXT) \
librpcrt4.$(IMPLIBEXT) \
librsabase.$(IMPLIBEXT) \
......@@ -1590,6 +1596,11 @@ librasapi32.def: rasapi32/rasapi32.spec.def
librasapi32.a: rasapi32/rasapi32.spec.def
$(DLLTOOL) -k -l $@ -d rasapi32/rasapi32.spec.def
libriched20.def: riched20/riched20.spec.def
$(RM) $@ && $(LN_S) riched20/riched20.spec.def $@
libriched20.a: riched20/riched20.spec.def
$(DLLTOOL) -k -l $@ -d riched20/riched20.spec.def
libriched32.def: richedit/riched32.spec.def
$(RM) $@ && $(LN_S) richedit/riched32.spec.def $@
libriched32.a: richedit/riched32.spec.def
......@@ -1880,6 +1891,7 @@ psapi/psapi.spec.def: $(WINEBUILD)
qcap/qcap.spec.def: $(WINEBUILD)
quartz/quartz.spec.def: $(WINEBUILD)
rasapi32/rasapi32.spec.def: $(WINEBUILD)
riched20/riched20.spec.def: $(WINEBUILD)
richedit/riched32.spec.def: $(WINEBUILD)
rpcrt4/rpcrt4.spec.def: $(WINEBUILD)
rsabase/rsabase.spec.def: $(WINEBUILD)
......@@ -2032,6 +2044,7 @@ psapi/psapi.dll.so: psapi
qcap/qcap.dll.so: qcap
quartz/quartz.dll.so: quartz
rasapi32/rasapi32.dll.so: rasapi32
riched20/riched20.dll.so: riched20
richedit/riched32.dll.so: richedit
rpcrt4/rpcrt4.dll.so: rpcrt4
rsabase/rsabase.dll.so: rsabase
......
Makefile
riched20.dll.dbg.c
riched20.spec.def
TOPSRCDIR = @top_srcdir@
TOPOBJDIR = ../..
SRCDIR = @srcdir@
VPATH = @srcdir@
MODULE = riched20.dll
IMPORTS = user32 gdi32 kernel32
C_SRCS = \
caret.c \
context.c \
editor.c \
list.c \
paint.c \
para.c \
richole.c \
row.c \
run.c \
string.c \
style.c \
undo.c \
wrap.c
@MAKE_DLL_RULES@
### Dependencies:
/*
* RichEdit - Caret and selection functions.
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
void ME_GetSelection(ME_TextEditor *editor, int *from, int *to)
{
*from = ME_GetCursorOfs(editor, 0);
*to = ME_GetCursorOfs(editor, 1);
if (*from > *to)
{
int tmp = *from;
*from = *to;
*to = tmp;
}
}
int ME_GetTextLength(ME_TextEditor *editor)
{
return ME_CharOfsFromRunOfs(editor, ME_FindItemBack(editor->pBuffer->pLast, diRun), 0);
}
void ME_SetSelection(ME_TextEditor *editor, int from, int to)
{
if (from == 0 && to == -1)
{
editor->pCursors[1].pRun = ME_FindItemFwd(editor->pBuffer->pFirst, diRun);
editor->pCursors[1].nOffset = 0;
editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
editor->pCursors[0].nOffset = 0;
ME_Repaint(editor);
ME_ClearTempStyle(editor);
return;
}
if (from == -1)
{
editor->pCursors[1] = editor->pCursors[0];
ME_Repaint(editor);
ME_ClearTempStyle(editor);
return;
}
if (from>to)
{
int tmp = from;
from = to;
to = tmp;
}
ME_RunOfsFromCharOfs(editor, from, &editor->pCursors[1].pRun, &editor->pCursors[1].nOffset);
ME_RunOfsFromCharOfs(editor, to, &editor->pCursors[0].pRun, &editor->pCursors[0].nOffset);
}
void ME_MoveCaret(ME_TextEditor *editor)
{
HDC hDC = GetDC(editor->hWnd);
ME_Context c;
ME_Cursor *pCursor = &editor->pCursors[0];
ME_DisplayItem *pCursorRun = pCursor->pRun;
ME_DisplayItem *pSizeRun = pCursor->pRun;
ME_InitContext(&c, editor, hDC);
assert(!pCursor->nOffset || !editor->bCaretAtEnd);
if (pCursorRun->type == diRun) {
ME_DisplayItem *row = ME_FindItemBack(pCursorRun, diStartRowOrParagraph);
if (row) {
ME_DisplayItem *run = pCursorRun;
ME_DisplayItem *para;
SIZE sz = {0, 0};
if (!pCursor->nOffset && !editor->bCaretAtEnd)
{
ME_DisplayItem *prev = ME_FindItemBack(pCursorRun, diRunOrStartRow);
if (prev->type == diRun)
pSizeRun = prev;
}
assert(row->type == diStartRow); /* paragraph -> run without start row ?*/
para = ME_FindItemBack(row, diParagraph);
if (editor->bCaretAtEnd && !pCursor->nOffset &&
run == ME_FindItemFwd(row, diRun))
{
ME_DisplayItem *tmp = ME_FindItemBack(row, diRunOrParagraph);
if (tmp->type == diRun)
{
row = ME_FindItemBack(tmp, diStartRow);
pSizeRun = run = tmp;
sz = ME_GetRunSize(&c, &run->member.run, ME_StrLen(run->member.run.strText));
}
}
if (pCursor->nOffset && !(run->member.run.nFlags & MERF_SKIPPED)) {
sz = ME_GetRunSize(&c, &run->member.run, pCursor->nOffset);
}
CreateCaret(editor->hWnd, NULL, 0, pSizeRun->member.run.nAscent+pSizeRun->member.run.nDescent);
SetCaretPos(run->member.run.pt.x+sz.cx,
para->member.para.nYPos+row->member.row.nBaseline+pSizeRun->member.run.pt.y-pSizeRun->member.run.nAscent-GetScrollPos(editor->hWnd, SB_VERT));
} else {
assert(0 == "Wrapped paragraph run without a row?");
CreateCaret(editor->hWnd, NULL, 0, 10);
SetCaretPos(0,0);
}
}
else {
assert(0 == "Cursor not on a run");
CreateCaret(editor->hWnd, NULL, 0, 10); /* FIXME use global font */
SetCaretPos(0,0);
}
ME_DestroyContext(&c);
ReleaseDC(editor->hWnd, hDC);
}
void ME_ShowCaret(ME_TextEditor *ed)
{
ME_MoveCaret(ed);
ShowCaret(ed->hWnd);
}
void ME_HideCaret(ME_TextEditor *ed)
{
HideCaret(ed->hWnd);
DestroyCaret();
}
void ME_InternalDeleteText(ME_TextEditor *editor, int nOfs,
int nChars)
{
ME_Cursor c;
int shift = 0;
while(nChars > 0)
{
ME_Run *run;
ME_CursorFromCharOfs(editor, nOfs, &c);
run = &c.pRun->member.run;
if (run->nFlags & MERF_ENDPARA) {
if (!ME_FindItemFwd(c.pRun, diParagraph))
{
return;
}
ME_JoinParagraphs(editor, ME_GetParagraph(c.pRun));
/* ME_SkipAndPropagateCharOffset(p->pRun, shift); */
ME_CheckCharOffsets(editor);
nChars--;
continue;
}
else
{
ME_Cursor cursor;
int nIntendedChars = nChars;
int nCharsToDelete = nChars;
int i;
int loc = c.nOffset;
ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags &= ~MEPF_WRAPPED;
cursor = c;
ME_StrRelPos(run->strText, loc, &nChars);
/* nChars is the number of characters that should be deleted from the
FOLLOWING runs (these AFTER cursor.pRun)
nCharsToDelete is a number of chars to delete from THIS run */
nCharsToDelete -= nChars;
shift -= nCharsToDelete;
TRACE("Deleting %d (intended %d-remaning %d) chars at %d in '%s' (%d)\n",
nCharsToDelete, nIntendedChars, nChars, c.nOffset,
debugstr_w(run->strText->szData), run->strText->nLen);
if (!c.nOffset && ME_StrVLen(run->strText) == nCharsToDelete)
{
/* undo = reinsert whole run */
/* nOfs is a character offset (from the start of the document
to the current (deleted) run */
ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
if (pUndo)
pUndo->di.member.run.nCharOfs = nOfs;
}
else
{
/* undo = reinsert partial run */
ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
if (pUndo) {
ME_DestroyString(pUndo->di.member.run.strText);
pUndo->di.member.run.nCharOfs = nOfs;
pUndo->di.member.run.strText = ME_MakeStringN(run->strText->szData+c.nOffset, nCharsToDelete);
}
}
TRACE("Post deletion string: %s (%d)\n", debugstr_w(run->strText->szData), run->strText->nLen);
TRACE("Shift value: %d\n", shift);
ME_StrDeleteV(run->strText, c.nOffset, nCharsToDelete);
/* update cursors (including c) */
for (i=-1; i<editor->nCursors; i++) {
ME_Cursor *pThisCur = editor->pCursors + i;
if (i == -1) pThisCur = &c;
if (pThisCur->pRun == cursor.pRun) {
if (pThisCur->nOffset > cursor.nOffset) {
if (pThisCur->nOffset-cursor.nOffset < nCharsToDelete)
pThisCur->nOffset = cursor.nOffset;
else
pThisCur->nOffset -= nCharsToDelete;
assert(pThisCur->nOffset >= 0);
assert(pThisCur->nOffset <= ME_StrVLen(run->strText));
}
if (pThisCur->nOffset == ME_StrVLen(run->strText))
{
pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd);
assert(pThisCur->pRun->type == diRun);
pThisCur->nOffset = 0;
}
}
}
/* c = updated data now */
if (c.pRun == cursor.pRun)
ME_SkipAndPropagateCharOffset(c.pRun, shift);
else
ME_PropagateCharOffset(c.pRun, shift);
if (!ME_StrVLen(cursor.pRun->member.run.strText))
{
TRACE("Removing useless run\n");
ME_Remove(cursor.pRun);
ME_DestroyDisplayItem(cursor.pRun);
}
shift = 0;
/*
ME_CheckCharOffsets(editor);
*/
continue;
}
}
}
void ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor,
int nChars)
{
assert(nCursor>=0 && nCursor<editor->nCursors);
ME_InternalDeleteText(editor, ME_GetCursorOfs(editor, nCursor), nChars);
}
static WCHAR wszSpace[] = {' ', 0};
/* FIXME this is temporary, just to have something to test how bad graphics handler is */
void ME_InsertGraphicsFromCursor(ME_TextEditor *editor, int nCursor)
{
ME_Cursor *pCursor = &editor->pCursors[nCursor];
ME_DisplayItem *pItem = NULL;
ME_DisplayItem *pNewRun = NULL;
ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor);
ME_UndoItem *pUndo;
/* FIXME no no no */
if (ME_IsSelection(editor))
ME_DeleteSelection(editor);
pUndo = ME_AddUndoItem(editor, diUndoDeleteRun, NULL);
if (pUndo) {
pUndo->nStart = pCursor->nOffset + pCursor->pRun->member.run.nCharOfs + ME_GetParagraph(pCursor->pRun)->member.para.nCharOfs;
pUndo->nLen = 1;
}
if (pCursor->nOffset)
{
ME_SplitRunSimple(editor, pCursor->pRun, pCursor->nOffset);
}
pItem = pCursor->pRun;
pNewRun = ME_MakeRun(pStyle, ME_MakeStringN(wszSpace, 1), MERF_GRAPHICS);
pNewRun->member.run.nCharOfs = pCursor->pRun->member.run.nCharOfs;
ME_InsertBefore(pCursor->pRun, pNewRun);
ME_PropagateCharOffset(pItem, 1);
ME_CheckCharOffsets(editor);
ME_SendSelChange(editor);
}
void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor,
const WCHAR *str, int len, ME_Style *style)
{
const WCHAR *pos;
ME_Cursor *p = NULL;
assert(style);
editor->bCaretAtEnd = FALSE;
/*
if (!style)
style = ME_GetInsertStyle(editor, nCursor);
else
ME_AddRefStyle(style);
*/
ME_AddRefStyle(style);
/* FIXME really HERE ? */
if (ME_IsSelection(editor))
ME_DeleteSelection(editor);
assert(nCursor>=0 && nCursor<editor->nCursors);
if (len == -1)
len = lstrlenW(str);
pos = str;
/* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */
while(pos-str < len && *pos != '\r' && *pos != '\n')
pos++;
/* handle EOLs */
if (pos-str < len) {
ME_DisplayItem *tp, *end_run;
ME_Paragraph *para;
ME_Style *tmp_style;
if (pos!=str)
ME_InsertTextFromCursor(editor, nCursor, str, pos-str, style);
p = &editor->pCursors[nCursor];
tp = ME_FindItemBack(p->pRun, diParagraph);
para = &tp->member.para;
assert(tp);
if (p->nOffset) {
ME_SplitRunSimple(editor, p->pRun, p->nOffset);
p = &editor->pCursors[nCursor];
}
tmp_style = ME_GetInsertStyle(editor, nCursor);
/* ME_SplitParagraph increases style refcount */
tp = ME_SplitParagraph(editor, p->pRun, p->pRun->member.run.style);
p->pRun = ME_FindItemFwd(tp, diRun);
end_run = ME_FindItemBack(tp, diRun);
ME_ReleaseStyle(end_run->member.run.style);
end_run->member.run.style = tmp_style;
p->nOffset = 0;
if(pos-str < len && *pos =='\r')
pos++;
if(pos-str < len && *pos =='\n')
pos++;
if(pos-str < len) {
ME_InsertTextFromCursor(editor, nCursor, pos, len-(pos-str), style);
}
ME_ReleaseStyle(style);
return;
}
p = &editor->pCursors[nCursor];
if (style) {
ME_DisplayItem *pNewRun = NULL;
assert(p->pRun->type == diRun);
pNewRun = ME_MakeRun(style, ME_MakeStringN(str, len), 0); /* addrefs style */
ME_InsertRun(editor, ME_CharOfsFromRunOfs(editor, p->pRun, p->nOffset), pNewRun);
ME_DestroyDisplayItem(pNewRun);
ME_ReleaseStyle(style);
return;
} else {
assert(0);
}
}
BOOL ME_ArrowLeft(ME_TextEditor *editor, ME_Cursor *p)
{
if (p->nOffset) {
p->nOffset = ME_StrRelPos2(p->pRun->member.run.strText, p->nOffset, -1);
return TRUE;
}
else
{
ME_DisplayItem *pRun = ME_FindItemBack(p->pRun, diRunOrParagraph);
assert(pRun);
if (pRun->type == diRun) {
p->pRun = pRun;
assert(p->pRun->type == diRun);
p->nOffset = pRun->member.run.strText->nLen;
if (p->nOffset) {
p->nOffset = ME_StrRelPos2(pRun->member.run.strText, p->nOffset, -1);
return TRUE;
}
else
assert(0);
}
if (pRun->type == diParagraph)
{
if (pRun->member.para.prev_para->type == diTextStart)
return FALSE;
assert(pRun->member.para.prev_para->type == diParagraph);
pRun = ME_FindItemBack(pRun, diRunOrParagraph);
/* every paragraph ought to have at least one run */
assert(pRun && pRun->type == diRun);
assert(pRun->member.run.nFlags & MERF_ENDPARA);
p->pRun = pRun;
p->nOffset = 0;
return TRUE;
}
assert(0);
}
}
BOOL ME_ArrowRight(ME_TextEditor *editor, ME_Cursor *p)
{
int new_ofs = ME_StrRelPos2(p->pRun->member.run.strText, p->nOffset, 1);
if (new_ofs<p->pRun->member.run.strText->nLen) {
p->nOffset = new_ofs;
}
else
{
ME_DisplayItem *pRun = ME_FindItemFwd(p->pRun, diRun);
if (pRun) {
p->pRun = pRun;
assert(p->pRun->type == diRun);
p->nOffset = 0;
}
}
return TRUE;
}
int ME_GetCursorOfs(ME_TextEditor *editor, int nCursor)
{
ME_Cursor *pCursor = &editor->pCursors[nCursor];
return ME_GetParagraph(pCursor->pRun)->member.para.nCharOfs
+ pCursor->pRun->member.run.nCharOfs + pCursor->nOffset;
}
int ME_FindPixelPos(ME_TextEditor *editor, int x, int y, ME_Cursor *result, BOOL *is_eol)
{
ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para;
int rx = 0;
if (is_eol)
*is_eol = 0;
while(p != editor->pBuffer->pLast)
{
if (p->type == diParagraph)
{
int ry = y - p->member.para.nYPos;
if (ry < 0)
{
result->pRun = ME_FindItemFwd(p, diRun);
result->nOffset = 0;
return 0;
}
if (ry >= p->member.para.nHeight)
{
p = p->member.para.next_para;
continue;
}
p = ME_FindItemFwd(p, diStartRow);
y = ry;
continue;
}
if (p->type == diStartRow)
{
int ry = y - p->member.row.nYPos;
if (ry < 0)
return 0;
if (ry >= p->member.row.nHeight)
{
p = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
if (p->type != diStartRow)
return 0;
continue;
}
p = ME_FindItemFwd(p, diRun);
continue;
}
if (p->type == diRun)
{
ME_DisplayItem *pp;
rx = x - p->member.run.pt.x;
if (rx < 0)
rx = 0;
if (rx >= p->member.run.nWidth) /* not this run yet... find next item */
{
pp = p;
do {
p = p->next;
if (p->type == diRun)
{
rx = x - p->member.run.pt.x;
goto continue_search;
}
if (p->type == diStartRow)
{
p = ME_FindItemFwd(p, diRun);
if (is_eol)
*is_eol = 1;
rx = 0; /* FIXME not sure */
goto found_here;
}
if (p->type == diParagraph || p->type == diTextEnd)
{
rx = 0; /* FIXME not sure */
p = pp;
goto found_here;
}
} while(1);
continue;
}
found_here:
if (p->member.run.nFlags & MERF_ENDPARA)
rx = 0;
result->pRun = p;
result->nOffset = ME_CharFromPointCursor(editor, rx, &p->member.run);
if (editor->pCursors[0].nOffset == p->member.run.strText->nLen && rx)
{
result->pRun = ME_FindItemFwd(editor->pCursors[0].pRun, diRun);
result->nOffset = 0;
}
return 1;
}
assert(0);
continue_search:
;
}
result->pRun = ME_FindItemBack(p, diRun);
result->nOffset = 0;
assert(result->pRun->member.run.nFlags & MERF_ENDPARA);
return 0;
}
void ME_LButtonDown(ME_TextEditor *editor, int x, int y)
{
ME_Cursor tmp_cursor;
int is_selection = 0;
editor->nUDArrowX = -1;
y += GetScrollPos(editor->hWnd, SB_VERT);
tmp_cursor = editor->pCursors[0];
is_selection = ME_IsSelection(editor);
ME_FindPixelPos(editor, x, y, &editor->pCursors[0], &editor->bCaretAtEnd);
if (GetKeyState(VK_SHIFT)>=0)
{
editor->pCursors[1] = editor->pCursors[0];
}
else
{
if (!is_selection) {
editor->pCursors[1] = tmp_cursor;
is_selection = 1;
}
}
HideCaret(editor->hWnd);
ME_MoveCaret(editor);
if (is_selection)
ME_Repaint(editor);
ShowCaret(editor->hWnd);
ME_ClearTempStyle(editor);
ME_SendSelChange(editor);
}
void ME_MouseMove(ME_TextEditor *editor, int x, int y)
{
ME_Cursor tmp_cursor;
y += GetScrollPos(editor->hWnd, SB_VERT);
tmp_cursor = editor->pCursors[0];
if (!ME_FindPixelPos(editor, x, y, &editor->pCursors[0], &editor->bCaretAtEnd))
/* return */;
if (tmp_cursor.pRun == editor->pCursors[0].pRun &&
tmp_cursor.nOffset == editor->pCursors[0].nOffset)
return;
HideCaret(editor->hWnd);
ME_MoveCaret(editor);
ME_Repaint(editor);
ShowCaret(editor->hWnd);
ME_SendSelChange(editor);
}
static ME_DisplayItem *ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow,
int x, int *pOffset, int *pbCaretAtEnd)
{
ME_DisplayItem *pNext, *pLastRun;
pNext = ME_FindItemFwd(pRow, diRunOrStartRow);
assert(pNext->type == diRun);
pLastRun = pNext;
*pbCaretAtEnd = FALSE;
do {
int run_x = pNext->member.run.pt.x;
int width = pNext->member.run.nWidth;
if (x < run_x)
{
if (pOffset) *pOffset = 0;
return pNext;
}
if (x >= run_x && x < run_x+width)
{
int ch = ME_CharFromPointCursor(editor, x-run_x, &pNext->member.run);
ME_String *s = pNext->member.run.strText;
if (ch < s->nLen) {
if (pOffset)
*pOffset = ch;
return pNext;
}
}
pLastRun = pNext;
pNext = ME_FindItemFwd(pNext, diRunOrStartRow);
} while(pNext && pNext->type == diRun);
if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0)
{
pNext = ME_FindItemFwd(pNext, diRun);
if (pbCaretAtEnd) *pbCaretAtEnd = 1;
if (pOffset) *pOffset = 0;
return pNext;
} else {
if (pbCaretAtEnd) *pbCaretAtEnd = 0;
if (pOffset) *pOffset = 0;
return pLastRun;
}
}
static int ME_GetXForArrow(ME_TextEditor *editor, ME_Cursor *pCursor)
{
ME_DisplayItem *pRun = pCursor->pRun;
int x;
if (editor->nUDArrowX != -1)
x = editor->nUDArrowX;
else {
if (editor->bCaretAtEnd)
{
pRun = ME_FindItemBack(pRun, diRun);
assert(pRun);
x = pRun->member.run.pt.x + pRun->member.run.nWidth;
}
else {
x = pRun->member.run.pt.x;
x += ME_PointFromChar(editor, &pRun->member.run, pCursor->nOffset);
}
editor->nUDArrowX = x;
}
return x;
}
void ME_ArrowUp(ME_TextEditor *editor, ME_Cursor *pCursor)
{
ME_DisplayItem *pRun = pCursor->pRun;
ME_DisplayItem *pItem, *pItem2;
int x = ME_GetXForArrow(editor, pCursor);
if (editor->bCaretAtEnd && !pCursor->nOffset)
{
pRun = ME_FindItemBack(pRun, diRun);
if (!pRun)
return;
}
/* start of this row */
pItem = ME_FindItemBack(pRun, diStartRow);
assert(pItem);
/* start of the previous row */
pItem2 = ME_FindItemBack(pItem, diStartRow);
/* no previous row = the first line of the first paragraph */
if (!pItem2) /* can't go up - don't go BOL (as in MS richedit) */
return;
/* FIXME
ME_WrapTextParagraph(editor, ME_FindItemBack(pItem2, diParagraph));
*/
pCursor->pRun = ME_FindRunInRow(editor, pItem2, x, &pCursor->nOffset, &editor->bCaretAtEnd);
}
void ME_ArrowDown(ME_TextEditor *editor, ME_Cursor *pCursor)
{
ME_DisplayItem *pRun = pCursor->pRun;
ME_DisplayItem *pItem;
int x = ME_GetXForArrow(editor, pCursor);
if (!pCursor->nOffset && editor->bCaretAtEnd)
{
pRun = ME_FindItemBack(pRun, diRun);
/* x = pRun->member.run.pt.x + pRun->member.run.nWidth; */
}
/* start of the next row */
pItem = ME_FindItemFwd(pRun, diStartRow);
/* FIXME If diParagraph is before diStartRow, wrap the next paragraph?
*/
if (!pItem)
{
/* next row not found - ignore */
return;
}
pCursor->pRun = ME_FindRunInRow(editor, pItem, x, &pCursor->nOffset, &editor->bCaretAtEnd);
assert(pCursor->pRun);
assert(pCursor->pRun->type == diRun);
}
void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor)
{
ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow);
if (pRow) {
ME_DisplayItem *pRun;
if (editor->bCaretAtEnd && !pCursor->nOffset) {
pRow = ME_FindItemBack(pRow, diStartRow);
if (!pRow)
return;
}
pRun = ME_FindItemFwd(pRow, diRun);
if (pRun) {
pCursor->pRun = pRun;
pCursor->nOffset = 0;
}
}
editor->bCaretAtEnd = FALSE;
}
void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor)
{
ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diTextStart);
if (pRow) {
ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
if (pRun) {
pCursor->pRun = pRun;
pCursor->nOffset = 0;
}
}
}
void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
{
ME_DisplayItem *pRow;
if (editor->bCaretAtEnd && !pCursor->nOffset)
return;
pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd);
assert(pRow);
if (pRow->type == diStartRow) {
/* FIXME WTF was I thinking about here ? */
ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
assert(pRun);
pCursor->pRun = pRun;
pCursor->nOffset = 0;
editor->bCaretAtEnd = 1;
return;
}
pCursor->pRun = ME_FindItemBack(pRow, diRun);
assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA);
pCursor->nOffset = 0;
editor->bCaretAtEnd = FALSE;
}
void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
{
ME_DisplayItem *p = ME_FindItemFwd(pCursor->pRun, diTextEnd);
assert(p);
p = ME_FindItemBack(p, diRun);
assert(p);
assert(p->member.run.nFlags & MERF_ENDPARA);
pCursor->pRun = p;
pCursor->nOffset = 0;
editor->bCaretAtEnd = FALSE;
}
BOOL ME_IsSelection(ME_TextEditor *editor)
{
return memcmp(&editor->pCursors[0], &editor->pCursors[1], sizeof(ME_Cursor))!=0;
}
int ME_GetSelCursor(ME_TextEditor *editor, int dir)
{
int cdir = ME_GetCursorOfs(editor, 0) - ME_GetCursorOfs(editor, 1);
if (cdir*dir>0)
return 0;
else
return 1;
}
BOOL ME_CancelSelection(ME_TextEditor *editor, int dir)
{
int cdir;
if (GetKeyState(VK_SHIFT)<0)
return FALSE;
if (!memcmp(&editor->pCursors[0], &editor->pCursors[1], sizeof(ME_Cursor)))
return FALSE;
cdir = ME_GetCursorOfs(editor, 0) - ME_GetCursorOfs(editor, 1);
if (cdir*dir>0)
editor->pCursors[1] = editor->pCursors[0];
else
editor->pCursors[0] = editor->pCursors[1];
/* FIXME optimize */
ME_MarkAllForWrapping(editor);
ME_Repaint(editor);
return TRUE;
}
void ME_RepaintSelection(ME_TextEditor *editor, ME_Cursor *pTempCursor)
{
ME_Cursor old_anchor = editor->pCursors[1];
BOOL bRedraw = FALSE;
bRedraw = memcmp(&editor->pCursors[0], &editor->pCursors[1], sizeof(ME_Cursor));
if (bRedraw)
{
/* FIXME optimize */
ME_MarkAllForWrapping(editor);
}
if (GetKeyState(VK_SHIFT)>=0) /* cancelling selection */
{
/* any selection was present ? if so, it's no more, repaint ! */
editor->pCursors[1] = editor->pCursors[0];
if (memcmp(pTempCursor, &old_anchor, sizeof(ME_Cursor))) {
ME_Repaint(editor);
return;
}
return;
}
else
{
if (!memcmp(pTempCursor, &editor->pCursors[1], sizeof(ME_Cursor))) /* starting selection */
{
editor->pCursors[1] = *pTempCursor;
}
}
ME_Repaint(editor);
}
void ME_DeleteSelection(ME_TextEditor *editor)
{
int from, to;
ME_GetSelection(editor, &from, &to);
ME_DeleteTextAtCursor(editor, ME_GetSelCursor(editor,-1), to-from);
}
void ME_SendSelChange(ME_TextEditor *editor)
{
SELCHANGE sc;
if (!(editor->nEventMask & ENM_SELCHANGE))
return;
sc.nmhdr.hwndFrom = editor->hWnd;
sc.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
sc.nmhdr.code = EN_SELCHANGE;
SendMessageW(editor->hWnd, EM_EXGETSEL, 0, (LPARAM)&sc.chrg);
sc.seltyp = SEL_EMPTY;
if (sc.chrg.cpMin != sc.chrg.cpMax)
sc.seltyp |= SEL_TEXT;
if (sc.chrg.cpMin < sc.chrg.cpMax+1) /* wth were RICHEDIT authors thinking ? */
sc.seltyp |= SEL_MULTICHAR;
SendMessageW(GetParent(editor->hWnd), WM_NOTIFY, sc.nmhdr.idFrom, (LPARAM)&sc);
}
BOOL ME_ArrowKey(ME_TextEditor *editor, int nVKey, int nCtrl)
{
int nCursor = 0;
ME_Cursor *p = &editor->pCursors[nCursor];
ME_Cursor tmp_curs = *p;
switch(nVKey) {
case VK_UP:
ME_ArrowUp(editor, p);
ME_ClearTempStyle(editor);
ME_RepaintSelection(editor, &tmp_curs);
ME_SendSelChange(editor);
return TRUE;
case VK_DOWN:
ME_ArrowDown(editor, p);
ME_ClearTempStyle(editor);
ME_RepaintSelection(editor, &tmp_curs);
ME_SendSelChange(editor);
return TRUE;
}
editor->nUDArrowX = -1;
switch(nVKey) {
case VK_BACK: { /* 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))
{
editor->bCaretAtEnd = FALSE; /* FIXME or maybe not */
ME_DeleteSelection(editor);
ME_UpdateRepaint(editor);
return TRUE;
}
if (ME_ArrowLeft(editor, p)) {
editor->bCaretAtEnd = FALSE; /* FIXME or maybe not */
ME_MoveCaret(editor);
ME_DeleteTextAtCursor(editor, nCursor, 1);
ME_UpdateRepaint(editor);
}
return TRUE;
}
case VK_DELETE: {
if (GetWindowLongW(editor->hWnd, GWL_STYLE) & ES_READONLY)
return FALSE;
/* editor->bCaretAtEnd = 0; FIXME or maybe not */
if (ME_IsSelection(editor))
{
ME_DeleteSelection(editor);
ME_UpdateRepaint(editor);
return TRUE;
}
ME_DeleteTextAtCursor(editor, nCursor, 1);
ME_UpdateRepaint(editor);
return TRUE;
}
case VK_HOME: {
if (GetKeyState(VK_CONTROL)<0)
ME_ArrowCtrlHome(editor, p);
else
ME_ArrowHome(editor, p);
editor->bCaretAtEnd = 0;
ME_ClearTempStyle(editor);
ME_RepaintSelection(editor, &tmp_curs);
ME_SendSelChange(editor);
return TRUE;
}
case VK_END:
if (GetKeyState(VK_CONTROL)<0)
ME_ArrowCtrlEnd(editor, p);
else
ME_ArrowEnd(editor, p);
ME_ClearTempStyle(editor);
ME_RepaintSelection(editor, &tmp_curs);
ME_SendSelChange(editor);
return TRUE;
case VK_LEFT:
editor->bCaretAtEnd = 0;
if (ME_CancelSelection(editor, -1))
return TRUE;
ME_ArrowLeft(editor, p);
ME_RepaintSelection(editor, &tmp_curs);
ME_ClearTempStyle(editor);
ME_SendSelChange(editor);
return TRUE;
case VK_RIGHT:
editor->bCaretAtEnd = 0;
if (ME_CancelSelection(editor, +1))
return TRUE;
ME_ArrowRight(editor, p);
ME_RepaintSelection(editor, &tmp_curs);
ME_ClearTempStyle(editor);
ME_SendSelChange(editor);
return TRUE;
}
return FALSE;
}
/*
* RichEdit - Operation context functions
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
void ME_InitContext(ME_Context *c, ME_TextEditor *editor, HDC hDC)
{
c->nSequence = editor->nSequence++;
c->hDC = hDC;
c->editor = editor;
c->pt.x = 0;
c->pt.y = 0;
c->hbrMargin = CreateSolidBrush(RGB(224,224,224));
GetClientRect(editor->hWnd, &c->rcView);
}
void ME_DestroyContext(ME_Context *c)
{
DeleteObject(c->hbrMargin);
}
/*
* RichEdit - functions dealing with editor object
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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
- EM_FINDTEXTEX
- EM_FINDWORDBREAK
- EM_FMTLINES
- EM_FORMATRANGE
- EM_GETCHARFORMAT (partly done)
+ EM_GETEVENTMASK
- EM_GETFIRSTVISIBLELINE
- 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_GETPUNCTUATION 1.0asian
- EM_GETRECT
- EM_GETREDONAME 2.0
+ EM_GETSEL
+ EM_GETSELTEXT (ANSI&Unicode)
! - EM_GETTHUMB
- EM_GETTEXTMODE 2.0
? + EM_GETTEXTRANGE (ANSI&Unicode)
- EM_GETUNDONAME
- EM_GETWORDBREAKPROC
- EM_GETWORDBREAKPROCEX
- EM_GETWORDWRAPMODE 1.0asian
- EM_HIDESELECTION
- EM_LIMITTEXT
- EM_LINEFROMCHAR
- EM_LINEINDEX
- EM_LINELENGTH
- EM_LINESCROLL
- EM_PASTESPECIAL
- EM_POSFROMCHARS
- EM_REDO 2.0
- EM_REQUESTRESIZE
+ EM_REPLACESEL (proper style?) ANSI&Unicode
- EM_SCROLL
- EM_SCROLLCARET
- EM_SELECTIONTYPE
+ EM_SETBKGNDCOLOR
- EM_SETCHARFORMAT (partly done, no ANSI)
+ EM_SETEVENTMASK (few notifications supported)
- 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
+ EM_SETPARAFORMAT
- EM_SETPUNCTUATION 1.0asian
+ EM_SETREADONLY no beep on modification attempt
- EM_SETRECT
- EM_SETRECTNP (EM_SETRECT without repainting) - not supported in RICHEDIT
+ EM_SETSEL
- EM_SETTARGETDEVICE
- EM_SETTEXTMODE 2.0
- EM_SETUNDOLIMIT 2.0
- EM_SETWORDBREAKPROC
- EM_SETWORDBREAKPROCEX
- EM_SETWORDWRAPMODE 1.0asian
- EM_STOPGROUPTYPING 2.0
- EM_STREAMIN
- EM_STREAMOUT
- EM_UNDO
+ WM_CHAR
+ WM_CLEAR
- WM_COPY (lame implementation, no RTF support)
- WM_CUT (lame implementation, no RTF support)
+ 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):
*
* - font caching
* - add remaining CHARFORMAT/PARAFORMAT fields
* - right/center align should strip spaces from the beginning
* - more advanced navigation (Ctrl-arrows, PageUp/PageDn)
* - tabs
* - pictures (not just smiling faces that lack API support ;-) )
* - OLE objects
* - calculate heights of pictures (half-done)
* - EM_STREAMIN/EM_STREAMOUT
* - horizontal scrolling (not even started)
* - fix scrollbars and refresh (it sucks bigtime)
* - hysteresis during wrapping (related to scrollbars appearing/disappearing)
* - should remember maximum row width for wrap hysteresis
* - find/replace
* - how to implement EM_FORMATRANGE and EM_DISPLAYBAND ? (Mission Impossible)
* - italic cursor 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)
* - bugs in end-of-text handling (the gray bar) could get me in jail ;-)
* - determination of row size
* - end-of-paragraph marks should be of reasonable size
*
* 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 <ole2.h>
#include <richole.h>
#include <winreg.h>
#define NO_SHLWAPI_STREAM
#include <shlwapi.h>
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
int me_debug = 0;
HANDLE me_heap = NULL;
void DoWrap(ME_TextEditor *editor) {
HDC hDC = GetDC(editor->hWnd);
ME_DisplayItem *item;
ME_Context c;
HWND hWnd = editor->hWnd;
int yLength = editor->nTotalLength;
int nSelFrom, nSelTo;
int nMinSel, nMaxSel;
ME_GetSelection(editor, &nSelFrom, &nSelTo);
nMinSel = nSelFrom < editor->nOldSelFrom ? nSelFrom : editor->nOldSelFrom;
nMaxSel = nSelTo > editor->nOldSelTo ? nSelTo : editor->nOldSelTo;
ME_InitContext(&c, editor, hDC);
c.pt.x = 0;
c.pt.y = 0;
item = editor->pBuffer->pFirst->next;
while(item != editor->pBuffer->pLast) {
int para_from, para_to;
BOOL bRedraw = FALSE;
para_from = item->member.para.nCharOfs;
para_to = item->member.para.next_para->member.para.nCharOfs;
if (para_from <= nMaxSel && para_to >= nMinSel && nMinSel != nMaxSel)
bRedraw = TRUE;
assert(item->type == diParagraph);
if (!(item->member.para.nFlags & MEPF_WRAPPED)
|| (item->member.para.nYPos != c.pt.y))
bRedraw = TRUE;
item->member.para.nYPos = c.pt.y;
ME_WrapTextParagraph(&c, item);
if (bRedraw) {
item->member.para.nFlags |= MEPF_REDRAW;
}
c.pt.y = item->member.para.nYPos + item->member.para.nHeight;
item = item->member.para.next_para;
}
editor->sizeWindow.cx = c.rcView.right-c.rcView.left;
editor->sizeWindow.cy = c.rcView.bottom-c.rcView.top;
editor->nTotalLength = c.pt.y-c.rcView.top;
ME_UpdateScrollBar(editor, -1);
ME_EnsureVisible(editor, editor->pCursors[0].pRun);
/* FIXME this should be marked for update too somehow, so that painting happens in ME_PaintContent */
if (yLength != c.pt.y-c.rcView.top) {
RECT rc;
rc.left = c.rcView.left;
rc.right = c.rcView.right;
rc.top = c.pt.y;
rc.bottom = c.rcView.bottom;
InvalidateRect(editor->hWnd, &rc, FALSE);
UpdateWindow(editor->hWnd);
}
editor->nOldSelFrom = nSelFrom;
editor->nOldSelTo = nSelTo;
/* PatBlt(hDC, 0, c.pt.y, c.rcView.right, c.rcView.bottom, BLACKNESS);*/
ME_DestroyContext(&c);
ReleaseDC(hWnd, hDC);
}
ME_TextBuffer *ME_MakeText() {
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;
}
ME_TextEditor *ME_MakeEditor(HWND hWnd) {
ME_TextEditor *ed = ALLOC_OBJ(ME_TextEditor);
HDC hDC;
ed->hWnd = hWnd;
ed->pBuffer = ME_MakeText();
hDC = GetDC(hWnd);
ME_MakeFirstParagraph(hDC, ed->pBuffer);
ReleaseDC(hWnd, hDC);
ed->bCaretShown = FALSE;
ed->nCursors = 3;
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->nTotalLength = 0;
ed->nScrollPos = 0;
ed->nUDArrowX = -1;
ed->nSequence = 0;
ed->rgbBackColor = -1;
ed->bCaretAtEnd = FALSE;
ed->nEventMask = 0;
ed->nModifyStep = 0;
ed->pUndoStack = ed->pRedoStack = NULL;
ed->nUndoMode = umAddToUndo;
ed->nParagraphs = 1;
ME_CheckCharOffsets(ed);
return ed;
}
void ME_DestroyEditor(ME_TextEditor *editor)
{
ME_DisplayItem *pFirst = editor->pBuffer->pFirst;
ME_DisplayItem *p = pFirst, *pNext = NULL;
ME_ClearTempStyle(editor);
ME_EmptyUndoStack(editor);
while(p) {
pNext = p->next;
ME_DestroyDisplayItem(p);
p = pNext;
}
ME_ReleaseStyle(editor->pBuffer->pDefaultStyle);
FREE_OBJ(editor);
}
#define UNSUPPORTED_MSG(e) \
case e: \
FIXME(#e ": stub\n"); \
return DefWindowProcW(hWnd, msg, wParam, lParam);
/******************************************************************
* RichEditANSIWndProc (RICHED20.10)
*/
LRESULT WINAPI RichEditANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
HDC hDC;
PAINTSTRUCT ps;
SCROLLINFO si;
ME_TextEditor *editor = (ME_TextEditor *)GetWindowLongW(hWnd, 0);
switch(msg) {
UNSUPPORTED_MSG(EM_AUTOURLDETECT)
UNSUPPORTED_MSG(EM_CANPASTE)
UNSUPPORTED_MSG(EM_CHARFROMPOS)
UNSUPPORTED_MSG(EM_DISPLAYBAND)
UNSUPPORTED_MSG(EM_EXLIMITTEXT)
UNSUPPORTED_MSG(EM_EXLINEFROMCHAR)
UNSUPPORTED_MSG(EM_FINDTEXT)
UNSUPPORTED_MSG(EM_FINDTEXTEX)
UNSUPPORTED_MSG(EM_FINDWORDBREAK)
UNSUPPORTED_MSG(EM_FMTLINES)
UNSUPPORTED_MSG(EM_FORMATRANGE)
UNSUPPORTED_MSG(EM_GETFIRSTVISIBLELINE)
UNSUPPORTED_MSG(EM_GETIMECOMPMODE)
/* UNSUPPORTED_MSG(EM_GETIMESTATUS) missing in Wine headers */
UNSUPPORTED_MSG(EM_GETLANGOPTIONS)
UNSUPPORTED_MSG(EM_GETLIMITTEXT)
UNSUPPORTED_MSG(EM_GETLINE)
UNSUPPORTED_MSG(EM_GETLINECOUNT)
/* UNSUPPORTED_MSG(EM_GETOLEINTERFACE) separate stub */
UNSUPPORTED_MSG(EM_GETOPTIONS)
UNSUPPORTED_MSG(EM_GETRECT)
UNSUPPORTED_MSG(EM_GETREDONAME)
UNSUPPORTED_MSG(EM_GETTEXTMODE)
UNSUPPORTED_MSG(EM_GETUNDONAME)
UNSUPPORTED_MSG(EM_GETWORDBREAKPROC)
UNSUPPORTED_MSG(EM_GETWORDBREAKPROCEX)
UNSUPPORTED_MSG(EM_HIDESELECTION)
UNSUPPORTED_MSG(EM_LIMITTEXT) /* also known as EM_SETLIMITTEXT */
UNSUPPORTED_MSG(EM_LINEFROMCHAR)
UNSUPPORTED_MSG(EM_LINEINDEX)
UNSUPPORTED_MSG(EM_LINELENGTH)
UNSUPPORTED_MSG(EM_LINESCROLL)
UNSUPPORTED_MSG(EM_PASTESPECIAL)
/* UNSUPPORTED_MSG(EM_POSFROMCHARS) missing in Wine headers */
UNSUPPORTED_MSG(EM_REQUESTRESIZE)
UNSUPPORTED_MSG(EM_SCROLL)
UNSUPPORTED_MSG(EM_SCROLLCARET)
UNSUPPORTED_MSG(EM_SELECTIONTYPE)
UNSUPPORTED_MSG(EM_SETLANGOPTIONS)
UNSUPPORTED_MSG(EM_SETOLECALLBACK)
UNSUPPORTED_MSG(EM_SETOPTIONS)
UNSUPPORTED_MSG(EM_SETRECT)
UNSUPPORTED_MSG(EM_SETRECTNP)
UNSUPPORTED_MSG(EM_SETTARGETDEVICE)
UNSUPPORTED_MSG(EM_SETTEXTMODE)
UNSUPPORTED_MSG(EM_SETUNDOLIMIT)
UNSUPPORTED_MSG(EM_SETWORDBREAKPROC)
UNSUPPORTED_MSG(EM_SETWORDBREAKPROCEX)
UNSUPPORTED_MSG(EM_STREAMIN)
UNSUPPORTED_MSG(EM_STREAMOUT)
UNSUPPORTED_MSG(WM_SETFONT)
UNSUPPORTED_MSG(WM_PASTE)
UNSUPPORTED_MSG(WM_STYLECHANGING)
UNSUPPORTED_MSG(WM_STYLECHANGED)
/* UNSUPPORTED_MSG(WM_UNICHAR) FIXME missing in Wine headers */
case WM_GETDLGCODE:
{
UINT code = DLGC_WANTCHARS|DLGC_WANTARROWS;
if (GetWindowLongW(hWnd, GWL_STYLE)&ES_WANTRETURN)
code |= 0; /* FIXME what can we do here ? ask for messages and censor them ? */
return code;
}
case WM_NCCREATE:
{
CREATESTRUCTW *pcs = (CREATESTRUCTW *)lParam;
editor = ME_MakeEditor(hWnd);
SetWindowLongW(hWnd, 0, (long)editor);
pcs = 0; /* ignore */
return TRUE;
}
case EM_EMPTYUNDOBUFFER:
ME_EmptyUndoStack(editor);
return 0;
case EM_GETSEL:
{
ME_GetSelection(editor, (int *)wParam, (int *)lParam);
if (!((wParam|lParam) & 0xFFFF0000))
return (lParam<<16)|wParam;
return -1;
}
case EM_EXGETSEL:
{
CHARRANGE *pRange = (CHARRANGE *)lParam;
ME_GetSelection(editor, (int *)&pRange->cpMin, (int *)&pRange->cpMax);
return 0;
}
case EM_CANUNDO:
return editor->pUndoStack != NULL;
case EM_CANREDO:
return editor->pRedoStack != NULL;
case EM_UNDO:
ME_Undo(editor);
return 0;
case EM_REDO:
ME_Redo(editor);
return 0;
case EM_SETSEL:
{
ME_SetSelection(editor, wParam, lParam);
ME_Repaint(editor);
ME_SendSelChange(editor);
return 0;
}
case EM_EXSETSEL:
{
CHARRANGE *pRange = (CHARRANGE *)lParam;
ME_SetSelection(editor, pRange->cpMin, pRange->cpMax);
/* FIXME optimize */
ME_Repaint(editor);
ME_SendSelChange(editor);
return 0;
}
case EM_SETBKGNDCOLOR:
{
LRESULT lColor = ME_GetBackColor(editor);
if (wParam)
editor->rgbBackColor = -1;
else
editor->rgbBackColor = lParam;
InvalidateRect(hWnd, NULL, TRUE);
UpdateWindow(hWnd);
return lColor;
}
case EM_GETMODIFY:
return editor->nModifyStep == 0 ? 0 : 1;
case EM_SETMODIFY:
{
if (wParam)
editor->nModifyStep = 0x80000000;
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);
ME_Repaint(editor);
return 0;
}
case EM_SETEVENTMASK:
editor->nEventMask = lParam;
return 0;
case EM_GETEVENTMASK:
return editor->nEventMask;
case EM_SETCHARFORMAT:
{
CHARFORMAT2W buf, *p;
p = ME_ToCF2W(&buf, (CHARFORMAT2W *)lParam);
if (!wParam)
ME_SetDefaultCharFormat(editor, p);
else if (wParam == (SCF_WORD | SCF_SELECTION))
FIXME("word selection not supported\n");
else if (wParam == SCF_ALL)
ME_SetCharFormat(editor, 0, ME_GetTextLength(editor), p);
else
ME_SetSelectionCharFormat(editor, p);
ME_CommitUndo(editor);
ME_UpdateRepaint(editor);
return 0;
}
case EM_GETCHARFORMAT:
{
CHARFORMAT2W tmp;
tmp.cbSize = sizeof(tmp);
if (!wParam)
ME_GetDefaultCharFormat(editor, &tmp);
else
ME_GetSelectionCharFormat(editor, &tmp);
ME_CopyToCFAny((CHARFORMAT2W *)lParam, &tmp);
return 0;
}
case EM_SETPARAFORMAT:
ME_SetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
ME_CommitUndo(editor);
return 0;
case EM_GETPARAFORMAT:
ME_GetSelectionParaFormat(editor, (PARAFORMAT2 *)lParam);
return 0;
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_Cursor c;
ME_Style *style;
LPWSTR wszText = ME_ToUnicode(hWnd, (void *)lParam);
size_t len = lstrlenW(wszText);
ME_GetSelection(editor, &from, &to);
ME_CursorFromCharOfs(editor, from, &c);
if (from != to) {
style = c.pRun->member.run.style;
ME_AddRefStyle(style); /* ME_GetInsertStyle has already done that */
}
else
style = ME_GetInsertStyle(editor, 0);
ME_InternalDeleteText(editor, from, to-from);
ME_InsertTextFromCursor(editor, 0, wszText, len, style);
ME_ReleaseStyle(style);
ME_EndToUnicode(hWnd, wszText);
/* 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_CommitUndo(editor);
if (!wParam)
ME_EmptyUndoStack(editor);
ME_UpdateRepaint(editor);
return 0;
}
case WM_SETTEXT:
{
LPWSTR wszText = ME_ToUnicode(hWnd, (void *)lParam);
ME_InternalDeleteText(editor, 0, ME_GetTextLength(editor));
/* uses default style! */
ME_InsertTextFromCursor(editor, 0, wszText, -1, editor->pBuffer->pDefaultStyle);
ME_EndToUnicode(hWnd, wszText);
ME_CommitUndo(editor);
ME_EmptyUndoStack(editor);
ME_UpdateRepaint(editor);
return 0;
}
case WM_CUT:
case WM_COPY:
{
int from, to, pars;
WCHAR *data;
HANDLE hData;
if (!OpenClipboard(hWnd))
return 0;
EmptyClipboard();
ME_GetSelection(editor, &from, &to);
pars = ME_CountParagraphsBetween(editor, from, to);
hData = GlobalAlloc(GMEM_MOVEABLE, sizeof(WCHAR)*(to-from+pars+1));
data = (WCHAR *)GlobalLock(hData);
ME_GetTextW(editor, data, from, to-from, TRUE);
GlobalUnlock(hData);
SetClipboardData(CF_UNICODETEXT, hData);
CloseClipboard();
if (msg == WM_CUT)
{
ME_InternalDeleteText(editor, from, to-from);
ME_CommitUndo(editor);
ME_UpdateRepaint(editor);
}
return 0;
}
case WM_GETTEXTLENGTH:
return ME_GetTextLength(editor);
case WM_GETTEXT:
{
TEXTRANGEW tr; /* W and A differ only by rng->lpstrText */
tr.chrg.cpMin = 0;
tr.chrg.cpMax = wParam-1;
tr.lpstrText = (WCHAR *)lParam;
return RichEditANSIWndProc(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
}
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 RichEditANSIWndProc(hWnd, EM_GETTEXTRANGE, 0, (LPARAM)&tr);
}
case EM_GETTEXTRANGE:
{
TEXTRANGEW *rng = (TEXTRANGEW *)lParam;
if (IsWindowUnicode(hWnd))
return ME_GetTextW(editor, rng->lpstrText, rng->chrg.cpMin, rng->chrg.cpMax-rng->chrg.cpMin, FALSE);
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, FALSE);
/* 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;
}
return ME_GetTextW(editor, rng->lpstrText, rng->chrg.cpMin, rng->chrg.cpMax-rng->chrg.cpMin, FALSE);
}
case WM_CREATE:
ME_CommitUndo(editor);
/* ME_InsertTextFromCursor(editor, 0, (WCHAR *)L"x", 1, editor->pBuffer->pDefaultStyle); */
DoWrap(editor);
ME_MoveCaret(editor);
return 0;
case WM_DESTROY:
ME_DestroyEditor(editor);
SetWindowLongW(hWnd, 0, 0);
PostQuitMessage(0);
break;
case WM_LBUTTONDOWN:
SetFocus(hWnd);
ME_LButtonDown(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
SetCapture(hWnd);
break;
case WM_MOUSEMOVE:
if (GetCapture() == hWnd)
ME_MouseMove(editor, (short)LOWORD(lParam), (short)HIWORD(lParam));
break;
case WM_LBUTTONUP:
if (GetCapture() == hWnd)
ReleaseCapture();
break;
case WM_PAINT:
hDC = BeginPaint(hWnd, &ps);
ME_PaintContent(editor, hDC, FALSE);
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:
{
HDC hDC = (HDC)wParam;
RECT rc;
COLORREF rgbBG = ME_GetBackColor(editor);
if (GetUpdateRect(hWnd,&rc,TRUE))
{
HBRUSH hbr = CreateSolidBrush(rgbBG);
FillRect(hDC, &rc, hbr);
DeleteObject(hbr);
}
return 1;
}
case WM_COMMAND:
TRACE("editor wnd command = %d\n", LOWORD(wParam));
return 0;
case WM_KEYDOWN:
if (ME_ArrowKey(editor, LOWORD(wParam), GetKeyState(VK_CONTROL)<0)) {
ME_CommitUndo(editor);
ME_EnsureVisible(editor, editor->pCursors[0].pRun);
HideCaret(hWnd);
ME_MoveCaret(editor);
ShowCaret(hWnd);
return 0;
}
if (GetKeyState(VK_CONTROL)<0)
{
if (LOWORD(wParam)=='W')
{
CHARFORMAT2W chf;
char buf[2048];
ME_GetSelectionCharFormat(editor, &chf);
ME_DumpStyleToBuf(&chf, buf);
MessageBoxA(NULL, buf, "Style dump", MB_OK);
}
if (LOWORD(wParam)=='Q')
{
ME_CheckCharOffsets(editor);
}
}
goto do_default;
case WM_CHAR:
{
WCHAR wstr;
if (GetWindowLongW(editor->hWnd, GWL_STYLE) & ES_READONLY) {
MessageBeep(MB_ICONERROR);
return 0; /* FIXME really 0 ? */
}
wstr = LOWORD(wParam);
if (((unsigned)wstr)>=' ' || wstr=='\r') {
/* 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 WM_VSCROLL:
{
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE|SIF_POS|SIF_RANGE|SIF_TRACKPOS;
GetScrollInfo(hWnd, SB_VERT, &si);
switch(LOWORD(wParam)) {
case SB_THUMBTRACK:
SetScrollPos(hWnd, SB_VERT, si.nTrackPos, FALSE);
ScrollWindow(hWnd, 0, si.nPos-si.nTrackPos, NULL, NULL);
/* InvalidateRect(hWnd, NULL, TRUE); */
UpdateWindow(hWnd);
break;
}
break;
}
case WM_SIZE:
{
ME_DisplayItem *tp = editor->pBuffer->pFirst;
while(tp)
{
if (tp->type == diParagraph)
{
tp->member.para.nFlags &= ~MEPF_WRAPPED;
tp = tp->member.para.next_para;
}
else
tp = tp->next;
}
ME_Repaint(editor);
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
case EM_GETOLEINTERFACE:
FIXME("EM_GETOLEINTERFACE: stub\n");
/* return 0x12345678; to find out if the missing interface is
related to app crash */
return 0;
default:
do_default:
return DefWindowProcW(hWnd, msg, wParam, lParam);
}
return 0L;
}
/******************************************************************
* RichEdit10ANSIWndProc (RICHED20.9)
*/
LRESULT WINAPI RichEdit10ANSIWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
/* FIXME: this is NOT the same as 2.0 version */
return RichEditANSIWndProc(hWnd, msg, wParam, lParam);
}
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);
}
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_FindItemFwd(editor->pBuffer->pFirst, diParagraph);
int nWritten = 0;
while(item && item->member.para.next_para->member.para.nCharOfs <= nStart)
item = ME_FindItemFwd(item, diParagraph);
if (!item) {
*buffer = L'\0';
return 0;
}
nStart -= item->member.para.nCharOfs;
do {
item = ME_FindItemFwd(item, diRun);
} while(item && (item->member.run.nCharOfs + ME_StrLen(item->member.run.strText) <= nStart));
assert(item);
nStart -= item->member.run.nCharOfs;
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 (bCRLF) {
*buffer++ = '\r';
nWritten++;
}
*buffer = '\n';
assert(nLen == 1);
}
else
CopyMemory(buffer, item->member.run.strText->szData, sizeof(WCHAR)*nLen);
nChars -= nLen;
nWritten += nLen;
buffer += nLen;
if (!nChars)
{
*buffer = L'\0';
return nWritten;
}
item = ME_FindItemFwd(item, diRun);
}
*buffer = L'\0';
return nWritten;
}
static WCHAR wszClassName[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '2', '0', 'W', 0};
static WCHAR wszClassName50[] = {'R', 'i', 'c', 'h', 'E', 'd', 'i', 't', '5', '0', 'W', 0};
void ME_RegisterEditorClass(HINSTANCE hInstance)
{
BOOL bResult;
WNDCLASSW wcW;
WNDCLASSA wcA;
wcW.style = CS_HREDRAW | CS_VREDRAW;
wcW.lpfnWndProc = RichEditANSIWndProc;
wcW.cbClsExtra = 0;
wcW.cbWndExtra = 4;
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;
wcW.lpszClassName = wszClassName;
bResult = RegisterClassW(&wcW);
assert(bResult);
wcW.lpszClassName = wszClassName50;
bResult = RegisterClassW(&wcW);
assert(bResult);
wcA.style = CS_HREDRAW | CS_VREDRAW;
wcA.lpfnWndProc = RichEditANSIWndProc;
wcA.cbClsExtra = 0;
wcA.cbWndExtra = 4;
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";
bResult = RegisterClassA(&wcA);
assert(bResult);
wcA.lpszClassName = "RichEdit50A";
bResult = RegisterClassA(&wcA);
assert(bResult);
}
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);
ME_RegisterEditorClass(hinstDLL);
break;
case DLL_PROCESS_DETACH:
UnregisterClassW(wszClassName, hinstDLL);
UnregisterClassA("RichEdit20A", hinstDLL);
HeapDestroy (me_heap);
me_heap = NULL;
break;
}
return TRUE;
}
/******************************************************************
* CreateTextServices (RICHED20.4)
*
* FIXME should be ITextHost instead of void*
*/
HRESULT WINAPI CreateTextServices(IUnknown *punkOuter, void *pITextHost,
IUnknown **ppUnk)
{
FIXME("stub\n");
/* FIXME should support aggregation */
if (punkOuter)
return CLASS_E_NOAGGREGATION;
return E_FAIL; /* E_NOTIMPL isn't allowed by MSDN */
}
/******************************************************************
* REExtendedRegisterClass (RICHED20.8)
*
* FIXME undocumented
*/
void WINAPI REExtendedRegisterClass(void)
{
FIXME("stub\n");
}
/*
* RichEdit - prototypes for functions and macro definitions
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editstr.h"
#define ALLOC_OBJ(type) (type *)HeapAlloc(me_heap, 0, sizeof(type))
#define ALLOC_N_OBJ(type, count) (type *)HeapAlloc(me_heap, 0, count*sizeof(type))
#define FREE_OBJ(ptr) HeapFree(me_heap, 0, ptr)
/* style.c */
ME_Style *ME_MakeStyle(CHARFORMAT2W *style);
void ME_AddRefStyle(ME_Style *item);
void ME_ReleaseStyle(ME_Style *item);
ME_Style *ME_GetInsertStyle(ME_TextEditor *editor, int nCursor);
ME_Style *ME_ApplyStyle(ME_Style *sSrc, CHARFORMAT2W *style);
void ME_PrepareStyle(ME_Context *c, ME_Style *s);
void ME_PrepareStyleFromDC(ME_Style *s, HDC hDC, int nSequence);
void ME_UnprepareStyle(ME_Style *s);
HFONT ME_SelectStyleFont(HDC hDC, ME_Style *s);
void ME_InitCharFormat2W(CHARFORMAT2W *pFmt);
void ME_SaveTempStyle(ME_TextEditor *editor);
void ME_ClearTempStyle(ME_TextEditor *editor);
void ME_DumpStyleToBuf(CHARFORMAT2W *pFmt, char buf[2048]);
void ME_DumpStyle(ME_Style *s);
CHARFORMAT2W *ME_ToCF2W(CHARFORMAT2W *to, CHARFORMAT2W *from);
void ME_CopyToCF2W(CHARFORMAT2W *to, CHARFORMAT2W *from);
CHARFORMAT2W *ME_ToCFAny(CHARFORMAT2W *to, CHARFORMAT2W *from);
void ME_CopyToCFAny(CHARFORMAT2W *to, CHARFORMAT2W *from);
void ME_CopyCharFormat(CHARFORMAT2W *pDest, CHARFORMAT2W *pSrc); /* only works with 2W structs */
/* list.c */
void ME_InsertBefore(ME_DisplayItem *diWhere, ME_DisplayItem *diWhat);
void ME_Remove(ME_DisplayItem *diWhere);
ME_DisplayItem *ME_FindItemBack(ME_DisplayItem *di, ME_DIType nTypeOrClass);
ME_DisplayItem *ME_FindItemFwd(ME_DisplayItem *di, ME_DIType nTypeOrClass);
ME_DisplayItem *ME_FindItemBackOrHere(ME_DisplayItem *di, ME_DIType nTypeOrClass);
ME_DisplayItem *ME_FindItemFwdOrHere(ME_DisplayItem *di, ME_DIType nTypeOrClass);
BOOL ME_DITypesEqual(ME_DIType type, ME_DIType nTypeOrClass);
ME_DisplayItem *ME_MakeDI(ME_DIType type);
void ME_DestroyDisplayItem(ME_DisplayItem *item);
void ME_DumpDocument(ME_TextBuffer *buffer);
const char *ME_GetDITypeName(ME_DIType type);
/* string.c */
int ME_GetOptimalBuffer(int nLen);
ME_String *ME_MakeString(LPCWSTR szText);
ME_String *ME_MakeStringN(LPCWSTR szText, int nMaxChars);
ME_String *ME_StrDup(ME_String *s);
void ME_DestroyString(ME_String *s);
void ME_AppendString(ME_String *s1, ME_String *s2);
ME_String *ME_ConcatString(ME_String *s1, ME_String *s2);
ME_String *ME_VSplitString(ME_String *orig, int nVPos);
int ME_IsWhitespaces(ME_String *s);
int ME_IsSplitable(ME_String *s);
/* int ME_CalcSkipChars(ME_String *s); */
int ME_StrLen(ME_String *s);
int ME_StrVLen(ME_String *s);
int ME_FindNonWhitespaceV(ME_String *s, int nVChar);
int ME_FindWhitespaceV(ME_String *s, int nVChar);
int ME_GetCharFwd(ME_String *s, int nPos); /* get char starting from start */
int ME_GetCharBack(ME_String *s, int nPos); /* get char starting from \0 */
int ME_StrRelPos(ME_String *s, int nVChar, int *pRelChars);
int ME_StrRelPos2(ME_String *s, int nVChar, int nRelChars);
int ME_VPosToPos(ME_String *s, int nVPos);
int ME_PosToVPos(ME_String *s, int nPos);
void ME_StrDeleteV(ME_String *s, int nVChar, int nChars);
/* smart helpers for A<->W conversions, they reserve/free memory and call MultiByte<->WideChar functions */
LPWSTR ME_ToUnicode(HWND hWnd, LPVOID psz);
void ME_EndToUnicode(HWND hWnd, LPVOID psz);
LPSTR ME_ToAnsi(HWND hWnd, LPVOID psz);
void ME_EndToAnsi(HWND hWnd, LPVOID psz);
/* note: those two really return the first matching offset (starting from EOS)+1
* in other words, an offset of the first trailing white/black */
int ME_ReverseFindNonWhitespaceV(ME_String *s, int nVChar);
int ME_ReverseFindWhitespaceV(ME_String *s, int nVChar);
/* row.c */
ME_DisplayItem *ME_FindRowStart(ME_Context *c, ME_DisplayItem *run, int nRelPos);
ME_DisplayItem *ME_RowStart(ME_DisplayItem *item);
ME_DisplayItem *ME_RowEnd(ME_DisplayItem *item);
void ME_RenumberParagraphs(ME_DisplayItem *item); /* TODO */
/* run.c */
ME_DisplayItem *ME_MakeRun(ME_Style *s, ME_String *strData, int nFlags);
/* note: ME_InsertRun inserts a copy of the specified run - so you need to destroy the original */
ME_DisplayItem *ME_InsertRun(ME_TextEditor *editor, int nCharOfs, ME_DisplayItem *pItem);
void ME_CheckCharOffsets(ME_TextEditor *editor);
void ME_PropagateCharOffset(ME_DisplayItem *p, int shift);
void ME_GetGraphicsSize(ME_TextEditor *editor, ME_Run *run, SIZE *pSize);
int ME_CharFromPoint(ME_TextEditor *editor, int cx, ME_Run *run);
/* this one accounts for 1/2 char tolerance */
int ME_CharFromPointCursor(ME_TextEditor *editor, int cx, ME_Run *run);
int ME_PointFromChar(ME_TextEditor *editor, ME_Run *pRun, int nOffset);
int ME_GetLastSplittablePlace(ME_Context *c, ME_Run *run);
int ME_CanJoinRuns(ME_Run *run1, ME_Run *run2);
void ME_JoinRuns(ME_TextEditor *editor, ME_DisplayItem *p);
ME_DisplayItem *ME_SplitRun(ME_Context *c, ME_DisplayItem *item, int nChar);
ME_DisplayItem *ME_SplitRunSimple(ME_TextEditor *editor, ME_DisplayItem *item, int nChar);
int ME_FindSplitPoint(ME_Context *c, POINT *pt, ME_Run *run, int desperate);
void ME_UpdateRunFlags(ME_TextEditor *editor, ME_Run *run);
ME_DisplayItem *ME_SplitFurther(ME_TextEditor *editor, ME_DisplayItem *run);
void ME_CalcRunExtent(ME_Context *c, ME_Run *run);
SIZE ME_GetRunSize(ME_Context *c, ME_Run *run, int nLen);
void ME_CursorFromCharOfs(ME_TextEditor *editor, int nCharOfs, ME_Cursor *pCursor);
void ME_RunOfsFromCharOfs(ME_TextEditor *editor, int nCharOfs, ME_DisplayItem **ppRun, int *pOfs);
int ME_CharOfsFromRunOfs(ME_TextEditor *editor, ME_DisplayItem *pRun, int nOfs);
void ME_SkipAndPropagateCharOffset(ME_DisplayItem *p, int shift);
void ME_SetCharFormat(ME_TextEditor *editor, int nFrom, int nLen, CHARFORMAT2W *pFmt);
void ME_SetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt);
void ME_GetCharFormat(ME_TextEditor *editor, int nFrom, int nLen, CHARFORMAT2W *pFmt);
void ME_GetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt);
void ME_GetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt);
void ME_SetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *mod);
/* caret.c */
void ME_SetSelection(ME_TextEditor *editor, int from, int to);
void ME_HideCaret(ME_TextEditor *ed);
void ME_ShowCaret(ME_TextEditor *ed);
void ME_MoveCaret(ME_TextEditor *ed);
int ME_FindPixelPos(ME_TextEditor *editor, int x, int y, ME_Cursor *result, BOOL *is_eol);
void ME_LButtonDown(ME_TextEditor *editor, int x, int y);
void ME_MouseMove(ME_TextEditor *editor, int x, int y);
void ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor, int nChars);
void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor,
const WCHAR *str, int len, ME_Style *style);
void ME_SetCharFormat(ME_TextEditor *editor, int nOfs, int nChars, CHARFORMAT2W *pFmt);
BOOL ME_ArrowKey(ME_TextEditor *ed, int nVKey, int nCtrl);
void ME_InitContext(ME_Context *c, ME_TextEditor *editor, HDC hDC);
void ME_DestroyContext(ME_Context *c);
ME_Style *GetInsertStyle(ME_TextEditor *editor, int nCursor);
void ME_MustBeWrapped(ME_Context *c, ME_DisplayItem *para);
int ME_GetCursorOfs(ME_TextEditor *editor, int nCursor);
void ME_GetSelection(ME_TextEditor *editor, int *from, int *to);
int ME_CountParagraphsBetween(ME_TextEditor *editor, int from, int to);
BOOL ME_IsSelection(ME_TextEditor *editor);
void ME_DeleteSelection(ME_TextEditor *editor);
void ME_SendSelChange(ME_TextEditor *editor);
void ME_InsertGraphicsFromCursor(ME_TextEditor *editor, int nCursor);
void ME_InternalDeleteText(ME_TextEditor *editor, int nOfs, int nChars);
int ME_GetTextLength(ME_TextEditor *editor);
/* wrap.c */
void ME_PrepareParagraphForWrapping(ME_Context *c, ME_DisplayItem *tp);
ME_DisplayItem *ME_MakeRow(int height, int baseline, int width);
void ME_InsertRowStart(ME_WrapContext *wc, ME_DisplayItem *pEnd);
void ME_WrapTextParagraph(ME_Context *c, ME_DisplayItem *tp);
/* para.c */
ME_DisplayItem *ME_GetParagraph(ME_DisplayItem *run);
void ME_MakeFirstParagraph(HDC hDC, ME_TextBuffer *editor);
ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *rp, ME_Style *style);
ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp);
void ME_DumpParaStyle(ME_Paragraph *s);
void ME_DumpParaStyleToBuf(PARAFORMAT2 *pFmt, char buf[2048]);
void ME_SetParaFormat(ME_TextEditor *editor, ME_DisplayItem *para, PARAFORMAT2 *pFmt);
void ME_SetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt);
void ME_GetParaFormat(ME_TextEditor *editor, ME_DisplayItem *para, PARAFORMAT2 *pFmt);
void ME_GetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt);
/* marks from first up to (but not including) last */
void ME_MarkForWrapping(ME_TextEditor *editor, ME_DisplayItem *first, ME_DisplayItem *last);
void ME_MarkAllForWrapping(ME_TextEditor *editor);
/* paint.c */
void ME_PaintContent(ME_TextEditor *editor, HDC hDC, BOOL bOnlyNew);
void ME_Repaint(ME_TextEditor *editor);
void ME_UpdateRepaint(ME_TextEditor *editor);
void ME_DrawParagraph(ME_Context *c, ME_DisplayItem *paragraph);
void ME_UpdateScrollBar(ME_TextEditor *editor, int ypos);
int ME_GetScrollPos(ME_TextEditor *editor);
void ME_EnsureVisible(ME_TextEditor *editor, ME_DisplayItem *pRun);
COLORREF ME_GetBackColor(ME_TextEditor *editor);
/* wintest.c */
/* editor.c */
void ME_RegisterEditorClass();
ME_TextEditor *ME_MakeEditor(HWND hWnd);
void ME_DestroyEditor(ME_TextEditor *editor);
void ME_SendOldNotify(ME_TextEditor *editor, int nCode);
ME_UndoItem *ME_AddUndoItem(ME_TextEditor *editor, ME_DIType type, ME_DisplayItem *di);
void ME_CommitUndo(ME_TextEditor *editor);
void ME_Undo(ME_TextEditor *editor);
void ME_Redo(ME_TextEditor *editor);
void ME_EmptyUndoStack(ME_TextEditor *editor);
int ME_GetTextW(ME_TextEditor *editor, WCHAR *buffer, int nStart, int nChars, BOOL bCRLF);
extern int me_debug;
extern HANDLE me_heap;
extern void DoWrap(ME_TextEditor *editor);
/*
* RichEdit - structures and constant
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#ifndef __EDITSTR_H
#define __EDITSTR_H
#ifndef _WIN32_IE
#define _WIN32_IE 0x0400
#endif
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <windef.h>
#include <winbase.h>
#include <winnls.h>
#include <winnt.h>
#include <wingdi.h>
#include <winuser.h>
#include <richedit.h>
#include <commctrl.h>
#include "wine/debug.h"
typedef struct tagME_String
{
WCHAR *szData;
int nLen, nBuffer;
} ME_String;
typedef struct tagME_Style
{
CHARFORMAT2W fmt;
HFONT hFont; /* cached font for the style */
TEXTMETRICW tm; /* cached font metrics for the style */
int nRefs; /* reference count */
int nSequence; /* incremented when cache needs to be rebuilt, ie. every screen redraw */
} ME_Style;
typedef enum {
diTextStart, /* start of the text buffer */
diParagraph, /* paragraph start */
diRun, /* run (sequence of chars with the same character format) */
diStartRow, /* start of the row (line of text on the screen) */
diTextEnd, /* end of the text buffer */
/********************* these below are meant for finding only *********************/
diStartRowOrParagraph, /* 5 */
diStartRowOrParagraphOrEnd,
diRunOrParagraph,
diRunOrStartRow,
diParagraphOrEnd,
diRunOrParagraphOrEnd, /* 10 */
diUndoInsertRun, /* 11 */
diUndoDeleteRun, /* 12 */
diUndoJoinParagraphs, /* 13 */
diUndoSplitParagraph, /* 14 */
diUndoSetParagraphFormat, /* 15 */
diUndoSetCharFormat, /* 16 */
diUndoEndTransaction, /* 17 */
diUndoSetDefaultCharFormat, /* 18 */
} ME_DIType;
/******************************** run flags *************************/
#define MERF_STYLEFLAGS 0x0FFF
/* run contains non-text content, which has its own rules for wrapping, sizing etc */
#define MERF_GRAPHICS 1
/* run is splittable (contains white spaces in the middle or end) */
#define MERF_SPLITTABLE 0x001000
/* run starts with whitespaces */
#define MERF_STARTWHITE 0x002000
/* run ends with whitespaces */
#define MERF_ENDWHITE 0x004000
/* run is completely made of whitespaces */
#define MERF_WHITESPACE 0x008000
/* run is a last (dummy) run in the paragraph */
#define MERF_SKIPPED 0x010000
/* flags that are calculated during text wrapping */
#define MERF_CALCBYWRAP 0x0F0000
/* the "end of paragraph" run, contains 1 character */
#define MERF_ENDPARA 0x100000
/* those flags are kept when the row is split */
#define MERF_SPLITMASK (~(0))
/******************************** para flags *************************/
/* this paragraph was already wrapped and hasn't changed, every change resets that flag */
#define MEPF_WRAPPED 1
#define MEPF_REDRAW 2
/******************************** structures *************************/
struct tagME_DisplayItem;
typedef struct tagME_Run
{
ME_String *strText;
ME_Style *style;
int nCharOfs; /* relative to para's offset */
int nWidth; /* width of full run, width of leading&trailing ws */
int nFlags;
int nAscent, nDescent; /* pixels above/below baseline */
POINT pt; /* relative to para's position */
} ME_Run;
typedef struct tagME_Document {
struct tagME_DisplayItem *def_char_style;
struct tagME_DisplayItem *def_para_style;
int last_wrapped_line;
} ME_Document;
typedef struct tagME_Paragraph
{
PARAFORMAT2 *pFmt;
int nLeftMargin, nRightMargin, nFirstMargin;
int nCharOfs;
int nFlags;
int nYPos, nHeight;
struct tagME_DisplayItem *prev_para, *next_para, *document;
} ME_Paragraph;
typedef struct tagME_Row
{
int nHeight;
int nBaseline;
int nWidth;
int nLMargin;
int nRMargin;
int nYPos;
} ME_Row;
typedef struct tagME_DisplayItem
{
ME_DIType type;
struct tagME_DisplayItem *prev, *next;
union {
ME_Run run;
ME_Row row;
ME_Paragraph para;
ME_Document doc; /* not used */
ME_Style *ustyle; /* used by diUndoSetCharFormat */
} member;
} ME_DisplayItem;
typedef struct tagME_UndoItem
{
ME_DisplayItem di;
int nStart, nLen;
} ME_UndoItem;
typedef struct tagME_TextBuffer
{
ME_DisplayItem *pFirst, *pLast;
ME_Style *pCharStyle;
ME_Style *pDefaultStyle;
} ME_TextBuffer;
typedef struct tagME_Cursor
{
ME_DisplayItem *pRun;
int nOffset;
} ME_Cursor;
typedef enum {
umAddToUndo,
umAddToRedo,
umIgnore,
umAddBackToUndo
} ME_UndoMode;
typedef struct tagME_TextEditor
{
HWND hWnd;
BOOL bCaretShown;
ME_TextBuffer *pBuffer;
ME_Cursor *pCursors;
int nCursors;
SIZE sizeWindow;
int nScrollPos;
int nTotalLength;
int nUDArrowX;
int nSequence;
int nOldSelFrom, nOldSelTo;
COLORREF rgbBackColor;
BOOL bCaretAtEnd;
int nEventMask;
int nModifyStep;
ME_DisplayItem *pUndoStack, *pRedoStack;
ME_UndoMode nUndoMode;
int nParagraphs;
} ME_TextEditor;
typedef struct tagME_Context
{
HDC hDC;
POINT pt;
POINT ptRowOffset;
RECT rcView;
HBRUSH hbrMargin;
/* those are valid inside ME_WrapTextParagraph and related */
POINT ptFirstRun;
ME_TextEditor *editor;
int nSequence;
} ME_Context;
typedef struct tagME_WrapContext
{
ME_Style *style;
ME_Context *context;
int nLeftMargin, nRightMargin, nFirstMargin;
int nTotalWidth, nAvailWidth;
int nRow;
POINT pt;
BOOL bOverflown;
ME_DisplayItem *pRowStart;
ME_DisplayItem *pLastSplittableRun;
POINT ptLastSplittableRun;
} ME_WrapContext;
#endif
/*
* RichEdit - Basic operations on double linked lists.
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(riched20);
void ME_InsertBefore(ME_DisplayItem *diWhere, ME_DisplayItem *diWhat)
{
diWhat->next = diWhere;
diWhat->prev = diWhere->prev;
diWhere->prev->next = diWhat;
diWhat->next->prev = diWhat;
}
void ME_Remove(ME_DisplayItem *diWhere)
{
ME_DisplayItem *diNext = diWhere->next;
ME_DisplayItem *diPrev = diWhere->prev;
assert(diNext);
assert(diPrev);
diPrev->next = diNext;
diNext->prev = diPrev;
}
ME_DisplayItem *ME_FindItemBack(ME_DisplayItem *di, ME_DIType nTypeOrClass)
{
if (!di)
return NULL;
di = di->prev;
while(di!=NULL) {
if (ME_DITypesEqual(di->type, nTypeOrClass))
return di;
di = di->prev;
}
return NULL;
}
ME_DisplayItem *ME_FindItemBackOrHere(ME_DisplayItem *di, ME_DIType nTypeOrClass)
{
while(di!=NULL) {
if (ME_DITypesEqual(di->type, nTypeOrClass))
return di;
di = di->prev;
}
return NULL;
}
ME_DisplayItem *ME_FindItemFwd(ME_DisplayItem *di, ME_DIType nTypeOrClass)
{
if (!di) return NULL;
di = di->next;
while(di!=NULL) {
if (ME_DITypesEqual(di->type, nTypeOrClass))
return di;
di = di->next;
}
return NULL;
}
ME_DisplayItem *ME_FindItemFwdOrHere(ME_DisplayItem *di, ME_DIType nTypeOrClass)
{
while(di!=NULL) {
if (ME_DITypesEqual(di->type, nTypeOrClass))
return di;
di = di->next;
}
return NULL;
}
BOOL ME_DITypesEqual(ME_DIType type, ME_DIType nTypeOrClass)
{
if (type==nTypeOrClass)
return TRUE;
if (nTypeOrClass==diRunOrParagraph && (type==diRun || type==diParagraph))
return TRUE;
if (nTypeOrClass==diRunOrStartRow && (type==diRun || type==diStartRow))
return TRUE;
if (nTypeOrClass==diParagraphOrEnd && (type==diTextEnd || type==diParagraph))
return TRUE;
if (nTypeOrClass==diStartRowOrParagraph && (type==diStartRow || type==diParagraph))
return TRUE;
if (nTypeOrClass==diStartRowOrParagraphOrEnd
&& (type==diStartRow || type==diParagraph || type==diTextEnd))
return TRUE;
if (nTypeOrClass==diRunOrParagraphOrEnd
&& (type==diRun || type==diParagraph || type==diTextEnd))
return TRUE;
return FALSE;
}
void ME_DestroyDisplayItem(ME_DisplayItem *item) {
/* TRACE("type=%s\n", ME_GetDITypeName(item->type)); */
if (item->type==diParagraph || item->type == diUndoSetParagraphFormat) {
FREE_OBJ(item->member.para.pFmt);
}
if (item->type==diRun || item->type == diUndoInsertRun) {
ME_ReleaseStyle(item->member.run.style);
ME_DestroyString(item->member.run.strText);
}
if (item->type==diUndoSetCharFormat || item->type==diUndoSetDefaultCharFormat) {
ME_ReleaseStyle(item->member.ustyle);
}
FREE_OBJ(item);
}
ME_DisplayItem *ME_MakeDI(ME_DIType type) {
ME_DisplayItem *item = ALLOC_OBJ(ME_DisplayItem);
ZeroMemory(item, sizeof(ME_DisplayItem));
item->type = type;
item->prev = item->next = NULL;
if (type == diParagraph || type == diUndoSplitParagraph) {
item->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
item->member.para.pFmt->cbSize = sizeof(PARAFORMAT2);
item->member.para.pFmt->dwMask = 0;
}
return item;
}
const char *ME_GetDITypeName(ME_DIType type)
{
switch(type)
{
case diParagraph: return "diParagraph";
case diRun: return "diRun";
case diTextStart: return "diTextStart";
case diTextEnd: return "diTextEnd";
case diStartRow: return "diStartRow";
case diUndoEndTransaction: return "diUndoEndTransaction";
case diUndoSetParagraphFormat: return "diUndoSetParagraphFormat";
case diUndoSetCharFormat: return "diUndoSetCharFormat";
case diUndoInsertRun: return "diUndoInsertRun";
case diUndoDeleteRun: return "diUndoDeleteRun";
case diUndoJoinParagraphs: return "diJoinParagraphs";
case diUndoSplitParagraph: return "diSplitParagraph";
case diUndoSetDefaultCharFormat: return "diUndoSetDefaultCharFormat";
default: return "?";
}
}
void ME_DumpDocument(ME_TextBuffer *buffer)
{
/* FIXME this is useless, */
ME_DisplayItem *pItem = buffer->pFirst;
TRACE("DOCUMENT DUMP START\n");
while(pItem) {
switch(pItem->type)
{
case diTextStart:
TRACE("Start");
break;
case diParagraph:
TRACE("\nParagraph(ofs=%d)", pItem->member.para.nCharOfs);
break;
case diStartRow:
TRACE(" - StartRow");
break;
case diRun:
TRACE(" - Run(\"%s\", %d)", debugstr_w(pItem->member.run.strText->szData),
pItem->member.run.nCharOfs);
break;
case diTextEnd:
TRACE("\nEnd\n");
break;
default:
break;
}
pItem = pItem->next;
}
TRACE("DOCUMENT DUMP END\n");
}
/*
* RichEdit - painting functions
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
void ME_PaintContent(ME_TextEditor *editor, HDC hDC, BOOL bOnlyNew) {
ME_DisplayItem *item;
ME_Context c;
editor->nSequence++;
ME_InitContext(&c, editor, hDC);
SetBkMode(hDC, TRANSPARENT);
ME_MoveCaret(editor);
item = editor->pBuffer->pFirst->next;
c.pt.y=-GetScrollPos(editor->hWnd, SB_VERT);
while(item != editor->pBuffer->pLast) {
assert(item->type == diParagraph);
if (!bOnlyNew || (item->member.para.nFlags & MEPF_REDRAW))
{
ME_DrawParagraph(&c, item);
item->member.para.nFlags &= ~MEPF_REDRAW;
}
c.pt.y += item->member.para.nHeight;
item = item->member.para.next_para;
}
/* FIXME this code just sucks, it should try to redraw incrementally */
if (c.pt.y<c.rcView.bottom) {
RECT rc;
rc.left = c.rcView.left;
rc.top = c.pt.y;
rc.right = c.rcView.right;
rc.bottom = c.pt.y+1;
FillRect(hDC, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
rc.left = c.rcView.left;
rc.top = c.pt.y+1;
rc.right = c.rcView.right;
rc.bottom = c.rcView.bottom;
FillRect(hDC, &rc, (HBRUSH)GetStockObject(LTGRAY_BRUSH));
}
ME_DestroyContext(&c);
item = editor->pBuffer->pFirst->next;
while(item != editor->pBuffer->pLast) {
if (item->type == diRun)
ME_UnprepareStyle(item->member.run.style);
item = item->next;
}
}
void ME_Repaint(ME_TextEditor *editor)
{
ME_Cursor *pCursor = &editor->pCursors[0];
ME_DisplayItem *pRun = NULL;
int nOffset = -1;
HDC hDC;
int nCharOfs = ME_CharOfsFromRunOfs(editor, pCursor->pRun, pCursor->nOffset);
ME_RunOfsFromCharOfs(editor, nCharOfs, &pRun, &nOffset);
assert(pRun == pCursor->pRun);
assert(nOffset == pCursor->nOffset);
DoWrap(editor);
hDC = GetDC(editor->hWnd);
ME_HideCaret(editor);
ME_PaintContent(editor, hDC, TRUE);
ReleaseDC(editor->hWnd, hDC);
ME_ShowCaret(editor);
}
void ME_UpdateRepaint(ME_TextEditor *editor)
{
InvalidateRect(editor->hWnd, NULL, TRUE);
ME_SendOldNotify(editor, EN_CHANGE);
ME_Repaint(editor);
ME_SendOldNotify(editor, EN_UPDATE);
ME_SendSelChange(editor);
}
void ME_DrawTextWithStyle(ME_Context *c, int x, int y, LPCWSTR szText, int nChars,
ME_Style *s, int *width, int nSelFrom, int nSelTo, int ymin, int cy) {
HDC hDC = c->hDC;
HGDIOBJ hOldFont;
COLORREF rgbOld, rgbBack;
ME_PrepareStyle(c, s);
hOldFont = SelectObject(hDC, s->hFont);
rgbBack = ME_GetBackColor(c->editor);
if ((s->fmt.dwMask & CFM_COLOR) && (s->fmt.dwEffects & CFE_AUTOCOLOR))
rgbOld = SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
else
rgbOld = SetTextColor(hDC, s->fmt.crTextColor);
ExtTextOutW(hDC, x, y, 0, NULL, szText, nChars, NULL);
if (width) {
SIZE sz;
GetTextExtentPoint32W(hDC, szText, nChars, &sz);
*width = sz.cx;
}
if (nSelFrom < nChars && nSelTo >= 0 && nSelFrom<nSelTo)
{
SIZE sz;
if (nSelFrom < 0) nSelFrom = 0;
if (nSelTo > nChars) nSelTo = nChars;
GetTextExtentPoint32W(hDC, szText, nSelFrom, &sz);
x += sz.cx;
GetTextExtentPoint32W(hDC, szText+nSelFrom, nSelTo-nSelFrom, &sz);
PatBlt(hDC, x, ymin, sz.cx, cy, DSTINVERT);
}
SetTextColor(hDC, rgbOld);
SelectObject(hDC, hOldFont);
}
void ME_DrawSelection(ME_Context *c)
{
}
void ME_DebugWrite(HDC hDC, POINT *pt, WCHAR *szText) {
int align = SetTextAlign(hDC, TA_LEFT|TA_TOP);
HGDIOBJ hFont = SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
COLORREF color = SetTextColor(hDC, RGB(128,128,128));
TextOutW(hDC, pt->x, pt->y, szText, lstrlenW(szText));
SelectObject(hDC, hFont);
SetTextAlign(hDC, align);
SetTextColor(hDC, color);
}
void ME_DrawGraphics(ME_Context *c, int x, int y, ME_Run *run,
ME_Paragraph *para, BOOL selected) {
SIZE sz;
int xs, ys, xe, ye, h, ym, width, eyes;
ME_GetGraphicsSize(c->editor, run, &sz);
xs = run->pt.x;
ys = y-sz.cy;
xe = xs+sz.cx;
ye = y;
h = ye-ys;
ym = ys+h/4;
width = sz.cx;
eyes = width/8;
/* draw a smiling face :) */
Ellipse(c->hDC, xs, ys, xe, ye);
Ellipse(c->hDC, xs+width/8, ym, x+width/8+eyes, ym+eyes);
Ellipse(c->hDC, xs+7*width/8-eyes, ym, xs+7*width/8, ym+eyes);
MoveToEx(c->hDC, xs+width/8, ys+3*h/4-eyes, NULL);
LineTo(c->hDC, xs+width/8, ys+3*h/4);
LineTo(c->hDC, xs+7*width/8, ys+3*h/4);
LineTo(c->hDC, xs+7*width/8, ys+3*h/4-eyes);
if (selected)
{
/* descent is usually (always?) 0 for graphics */
PatBlt(c->hDC, x, y-run->nAscent, sz.cx, run->nAscent+run->nDescent, DSTINVERT);
}
}
void ME_DrawRun(ME_Context *c, int x, int y, ME_DisplayItem *rundi, ME_Paragraph *para) {
ME_Run *run = &rundi->member.run;
int runofs = run->nCharOfs+para->nCharOfs;
if (run->nFlags & MERF_GRAPHICS) {
int blfrom, blto;
ME_GetSelection(c->editor, &blfrom, &blto);
ME_DrawGraphics(c, x, y, run, para, (runofs >= blfrom) && (runofs < blto));
} else
{
int blfrom, blto;
ME_DisplayItem *start = ME_FindItemBack(rundi, diStartRow);
ME_GetSelection(c->editor, &blfrom, &blto);
ME_DrawTextWithStyle(c, x, y,
run->strText->szData, ME_StrVLen(run->strText), run->style, NULL,
blfrom-runofs, blto-runofs, c->pt.y+start->member.row.nYPos, start->member.row.nHeight);
}
}
COLORREF ME_GetBackColor(ME_TextEditor *editor)
{
/* Looks like I was seriously confused
return GetSysColor((GetWindowLong(editor->hWnd, GWL_STYLE) & ES_READONLY) ? COLOR_3DFACE: COLOR_WINDOW);
*/
if (editor->rgbBackColor == -1)
return GetSysColor(COLOR_WINDOW);
else
return editor->rgbBackColor;
}
void ME_DrawParagraph(ME_Context *c, ME_DisplayItem *paragraph) {
int align = SetTextAlign(c->hDC, TA_BASELINE);
ME_DisplayItem *p;
ME_Run *run;
ME_Paragraph *para = NULL;
RECT rc, rcPara;
int y = c->pt.y;
int height = 0, baseline = 0, no=0, pno = 0;
int xs, xe;
int visible = 0;
int nMargWidth = 0;
c->pt.x = c->rcView.left;
rcPara.left = c->rcView.left;
rcPara.right = c->rcView.right;
for (p = paragraph; p!=paragraph->member.para.next_para; p = p->next) {
switch(p->type) {
case diParagraph:
para = &p->member.para;
break;
case diStartRow:
assert(para);
nMargWidth = (pno==0?para->nFirstMargin:para->nLeftMargin);
xs = c->rcView.left+nMargWidth;
xe = c->rcView.right-para->nRightMargin;
y += height;
rcPara.top = y;
rcPara.bottom = y+p->member.row.nHeight;
visible = RectVisible(c->hDC, &rcPara);
if (visible) {
HBRUSH hbr;
/* left margin */
rc.left = c->rcView.left;
rc.right = c->rcView.left+nMargWidth;
rc.top = y;
rc.bottom = y+p->member.row.nHeight;
FillRect(c->hDC, &rc, c->hbrMargin);
/* right margin */
rc.left = xe;
rc.right = c->rcView.right;
FillRect(c->hDC, &rc, c->hbrMargin);
rc.left = c->rcView.left+para->nLeftMargin;
rc.right = xe;
hbr = CreateSolidBrush(ME_GetBackColor(c->editor));
FillRect(c->hDC, &rc, hbr);
DeleteObject(hbr);
}
if (me_debug)
{
const WCHAR wszRowDebug[] = {'r','o','w','[','%','d',']',0};
WCHAR buf[128];
POINT pt = c->pt;
wsprintfW(buf, wszRowDebug, no);
pt.y = 12+y;
ME_DebugWrite(c->hDC, &pt, buf);
}
height = p->member.row.nHeight;
baseline = p->member.row.nBaseline;
pno++;
break;
case diRun:
assert(para);
run = &p->member.run;
if (visible && me_debug) {
rc.left = c->rcView.left+run->pt.x;
rc.right = c->rcView.left+run->pt.x+run->nWidth;
rc.top = c->pt.y+run->pt.y;
rc.bottom = c->pt.y+run->pt.y+height;
TRACE("rc = (%ld, %ld, %ld, %ld)\n", rc.left, rc.top, rc.right, rc.bottom);
if (run->nFlags & MERF_SKIPPED)
DrawFocusRect(c->hDC, &rc);
else
FrameRect(c->hDC, &rc, GetSysColorBrush(COLOR_GRAYTEXT));
}
if (visible)
ME_DrawRun(c, run->pt.x, c->pt.y+run->pt.y+baseline, p, &paragraph->member.para);
if (me_debug)
{
/* I'm using %ls, hope wsprintfW is not going to use wrong (4-byte) WCHAR version */
const WCHAR wszRunDebug[] = {'[','%','d',':','%','x',']',' ','%','l','s',0};
WCHAR buf[2560];
POINT pt;
pt.x = run->pt.x;
pt.y = c->pt.y + run->pt.y;
wsprintfW(buf, wszRunDebug, no, p->member.run.nFlags, p->member.run.strText->szData);
ME_DebugWrite(c->hDC, &pt, buf);
}
/* c->pt.x += p->member.run.nWidth; */
break;
default:
break;
}
no++;
}
SetTextAlign(c->hDC, align);
}
void ME_UpdateScrollBar(ME_TextEditor *editor, int ypos)
{
float perc = 0.0;
SCROLLINFO si;
HWND hWnd = editor->hWnd;
int overflow = editor->nTotalLength - editor->sizeWindow.cy;
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE|SIF_POS|SIF_RANGE|SIF_TRACKPOS;
GetScrollInfo(hWnd, SB_VERT, &si);
if (ypos < 0) {
perc = 1.0*si.nPos/si.nMax;
ypos = perc*overflow;
}
if (ypos >= overflow && overflow > 0)
ypos = overflow - 1;
if (overflow > 0) {
EnableScrollBar(hWnd, SB_VERT, ESB_ENABLE_BOTH);
SetScrollRange(hWnd, SB_VERT, 0, overflow, FALSE);
SetScrollPos(hWnd, SB_VERT, ypos, TRUE);
} else {
EnableScrollBar(hWnd, SB_VERT, ESB_DISABLE_BOTH);
SetScrollRange(hWnd, SB_VERT, 0, 0, FALSE);
SetScrollPos(hWnd, SB_VERT, 0, TRUE);
}
if (ypos != si.nPos)
{
TRACE("ScrollWindow(%d, %d, %d, %0.4f)\n", si.nPos, si.nMax, ypos, perc);
ScrollWindow(hWnd, 0, si.nPos - ypos, NULL, NULL);
UpdateWindow(hWnd);
}
}
int ME_GetScrollPos(ME_TextEditor *editor)
{
return GetScrollPos(editor->hWnd, SB_VERT);
}
void ME_EnsureVisible(ME_TextEditor *editor, ME_DisplayItem *pRun)
{
ME_DisplayItem *pRow = ME_FindItemBack(pRun, diStartRow);
ME_DisplayItem *pPara = ME_FindItemBack(pRun, diParagraph);
int y, yrel, yheight;
assert(pRow);
assert(pPara);
y = pPara->member.para.nYPos+pRow->member.row.nYPos;
yheight = pRow->member.row.nHeight;
yrel = y - ME_GetScrollPos(editor);
if (yrel < 0)
ME_UpdateScrollBar(editor, y);
else if (yrel + yheight > editor->sizeWindow.cy)
{
ME_UpdateScrollBar(editor, y + yheight - editor->sizeWindow.cy);
}
}
/*
* RichEdit - functions working on paragraphs of text (diParagraph).
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
static WCHAR wszParagraphSign[] = {0xB6, 0};
void ME_MakeFirstParagraph(HDC hDC, ME_TextBuffer *text)
{
PARAFORMAT2 fmt;
CHARFORMAT2W cf;
LOGFONTW lf;
HFONT hf;
ME_DisplayItem *para = ME_MakeDI(diParagraph);
ME_DisplayItem *run;
ME_Style *style;
hf = (HFONT)GetStockObject(SYSTEM_FONT);
assert(hf);
GetObjectW(hf, sizeof(LOGFONTW), &lf);
ZeroMemory(&cf, sizeof(cf));
cf.cbSize = sizeof(cf);
cf.dwMask = CFM_BACKCOLOR|CFM_COLOR|CFM_FACE|CFM_SIZE|CFM_CHARSET;
cf.dwMask |= CFM_ALLCAPS|CFM_BOLD|CFM_DISABLED|CFM_EMBOSS|CFM_HIDDEN;
cf.dwMask |= CFM_IMPRINT|CFM_ITALIC|CFM_LINK|CFM_OUTLINE|CFM_PROTECTED;
cf.dwMask |= CFM_REVISED|CFM_SHADOW|CFM_SMALLCAPS|CFM_STRIKEOUT;
cf.dwMask |= CFM_SUBSCRIPT|CFM_UNDERLINE;
cf.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
lstrcpyW(cf.szFaceName, lf.lfFaceName);
cf.yHeight=lf.lfHeight*1440/GetDeviceCaps(hDC, LOGPIXELSY);
if (lf.lfWeight>=700) /* FIXME correct weight ? */
cf.dwEffects |= CFE_BOLD;
cf.wWeight = lf.lfWeight;
if (lf.lfItalic) cf.dwEffects |= CFE_ITALIC;
if (lf.lfUnderline) cf.dwEffects |= CFE_UNDERLINE;
if (lf.lfStrikeOut) cf.dwEffects |= CFE_STRIKEOUT;
ZeroMemory(&fmt, sizeof(fmt));
fmt.cbSize = sizeof(fmt);
fmt.dwMask = PFM_ALIGNMENT | PFM_OFFSET | PFM_STARTINDENT | PFM_RIGHTINDENT;
CopyMemory(para->member.para.pFmt, &fmt, sizeof(PARAFORMAT2));
style = ME_MakeStyle(&cf);
text->pDefaultStyle = style;
run = ME_MakeRun(style, ME_MakeString(wszParagraphSign), MERF_ENDPARA);
run->member.run.nCharOfs = 0;
ME_InsertBefore(text->pLast, para);
ME_InsertBefore(text->pLast, run);
para->member.para.prev_para = text->pFirst;
para->member.para.next_para = text->pLast;
text->pFirst->member.para.next_para = para;
text->pLast->member.para.prev_para = para;
text->pLast->member.para.nCharOfs = 1;
}
void ME_MarkAllForWrapping(ME_TextEditor *editor)
{
ME_MarkForWrapping(editor, editor->pBuffer->pFirst->member.para.next_para, editor->pBuffer->pLast);
}
void ME_MarkForWrapping(ME_TextEditor *editor, ME_DisplayItem *first, ME_DisplayItem *last)
{
while(first != last)
{
first->member.para.nFlags &= ~MEPF_WRAPPED;
first = first->member.para.next_para;
}
}
/* split paragraph at the beginning of the run */
ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *run, ME_Style *style)
{
ME_DisplayItem *next_para = NULL;
ME_DisplayItem *run_para = NULL;
ME_DisplayItem *new_para = ME_MakeDI(diParagraph);
ME_DisplayItem *end_run = ME_MakeRun(style,ME_MakeString(wszParagraphSign), MERF_ENDPARA);
ME_UndoItem *undo = NULL;
int ofs;
ME_DisplayItem *pp;
assert(run->type == diRun);
run_para = ME_GetParagraph(run);
assert(run_para->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
ofs = end_run->member.run.nCharOfs = run->member.run.nCharOfs;
next_para = run_para->member.para.next_para;
assert(next_para == ME_FindItemFwd(run_para, diParagraphOrEnd));
undo = ME_AddUndoItem(editor, diUndoJoinParagraphs, NULL);
if (undo)
undo->nStart = run_para->member.para.nCharOfs + ofs;
/* the new paragraph will have a different starting offset, so let's update its runs */
pp = run;
while(pp->type == diRun) {
pp->member.run.nCharOfs -= ofs;
pp = ME_FindItemFwd(pp, diRunOrParagraphOrEnd);
}
new_para->member.para.nCharOfs = ME_GetParagraph(run)->member.para.nCharOfs+ofs;
new_para->member.para.nCharOfs += 1;
new_para->member.para.nFlags = 0; /* FIXME copy flags (if applicable) */
/* FIXME initialize format style and call ME_SetParaFormat blah blah */
CopyMemory(new_para->member.para.pFmt, run_para->member.para.pFmt, sizeof(PARAFORMAT2));
/* FIXME remove this as soon as nLeftMargin etc are replaced with proper fields of PARAFORMAT2 */
new_para->member.para.nLeftMargin = run_para->member.para.nLeftMargin;
new_para->member.para.nRightMargin = run_para->member.para.nRightMargin;
new_para->member.para.nFirstMargin = run_para->member.para.nFirstMargin;
/* insert paragraph into paragraph double linked list */
new_para->member.para.prev_para = run_para;
new_para->member.para.next_para = next_para;
run_para->member.para.next_para = new_para;
next_para->member.para.prev_para = new_para;
/* insert end run of the old paragraph, and new paragraph, into DI double linked list */
ME_InsertBefore(run, new_para);
ME_InsertBefore(new_para, end_run);
/* force rewrap of the */
run_para->member.para.prev_para->member.para.nFlags &= ~MEPF_WRAPPED;
new_para->member.para.prev_para->member.para.nFlags &= ~MEPF_WRAPPED;
/* we've added the end run, so we need to modify nCharOfs in the next paragraphs */
ME_PropagateCharOffset(next_para, 1);
editor->nParagraphs++;
return new_para;
}
/* join tp with tp->member.para.next_para, keeping tp's style; this
* is consistent with the original */
ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp)
{
ME_DisplayItem *pNext, *pFirstRunInNext, *pRun, *pTmp;
int i, shift;
ME_UndoItem *undo = NULL;
assert(tp->type == diParagraph);
assert(tp->member.para.next_para);
assert(tp->member.para.next_para->type == diParagraph);
pNext = tp->member.para.next_para;
{
/* null char format operation to store the original char format for the ENDPARA run */
CHARFORMAT2W fmt;
ME_InitCharFormat2W(&fmt);
ME_SetCharFormat(editor, pNext->member.para.nCharOfs-1, 1, &fmt);
}
undo = ME_AddUndoItem(editor, diUndoSplitParagraph, NULL);
if (undo)
{
undo->nStart = pNext->member.para.nCharOfs-1;
assert(pNext->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
CopyMemory(undo->di.member.para.pFmt, pNext->member.para.pFmt, sizeof(PARAFORMAT2));
}
shift = pNext->member.para.nCharOfs - tp->member.para.nCharOfs - 1;
pRun = ME_FindItemBack(pNext, diRunOrParagraph);
pFirstRunInNext = ME_FindItemFwd(pNext, diRunOrParagraph);
assert(pRun);
assert(pRun->type == diRun);
assert(pRun->member.run.nFlags & MERF_ENDPARA);
assert(pFirstRunInNext->type == diRun);
/* if some cursor points at end of paragraph, make it point to the first
run of the next joined paragraph */
for (i=0; i<editor->nCursors; i++) {
if (editor->pCursors[i].pRun == pRun) {
editor->pCursors[i].pRun = pFirstRunInNext;
editor->pCursors[i].nOffset = 0;
}
}
pTmp = pNext;
do {
pTmp = ME_FindItemFwd(pTmp, diRunOrParagraphOrEnd);
if (pTmp->type != diRun)
break;
TRACE("shifting \"%s\" by %d (previous %d)\n", debugstr_w(pTmp->member.run.strText->szData), shift, pTmp->member.run.nCharOfs);
pTmp->member.run.nCharOfs += shift;
} while(1);
ME_Remove(pRun);
ME_DestroyDisplayItem(pRun);
tp->member.para.next_para = pNext->member.para.next_para;
pNext->member.para.next_para->member.para.prev_para = tp;
ME_Remove(pNext);
ME_DestroyDisplayItem(pNext);
ME_PropagateCharOffset(tp->member.para.next_para, -1);
ME_CheckCharOffsets(editor);
editor->nParagraphs--;
tp->member.para.nFlags &= ~MEPF_WRAPPED;
return tp;
}
ME_DisplayItem *ME_GetParagraph(ME_DisplayItem *item) {
return ME_FindItemBackOrHere(item, diParagraph);
}
static void ME_DumpStyleEffect(char **p, const char *name, PARAFORMAT2 *fmt, int mask)
{
*p += sprintf(*p, "%-22s%s\n", name, (fmt->dwMask & mask) ? ((fmt->wEffects & mask) ? "yes" : "no") : "N/A");
}
void ME_DumpParaStyleToBuf(PARAFORMAT2 *pFmt, char buf[2048])
{
/* FIXME only PARAFORMAT styles implemented */
char *p;
p = buf;
p += sprintf(p, "Alignment: %s\n",
!(pFmt->dwMask & PFM_ALIGNMENT) ? "N/A" :
((pFmt->wAlignment == PFA_LEFT) ? "left" :
((pFmt->wAlignment == PFA_RIGHT) ? "right" :
((pFmt->wAlignment == PFA_CENTER) ? "center" :
/*((pFmt->wAlignment == PFA_JUSTIFY) ? "justify" : "incorrect")*/
"incorrect"))));
if (pFmt->dwMask & PFM_OFFSET)
p += sprintf(p, "Offset: %d\n", (int)pFmt->dxOffset);
else
p += sprintf(p, "Offset: N/A\n");
if (pFmt->dwMask & PFM_OFFSETINDENT)
p += sprintf(p, "Offset indent: %d\n", (int)pFmt->dxStartIndent);
else
p += sprintf(p, "Offset indent: N/A\n");
if (pFmt->dwMask & PFM_STARTINDENT)
p += sprintf(p, "Start indent: %d\n", (int)pFmt->dxStartIndent);
else
p += sprintf(p, "Start indent: N/A\n");
if (pFmt->dwMask & PFM_RIGHTINDENT)
p += sprintf(p, "Right indent: %d\n", (int)pFmt->dxRightIndent);
else
p += sprintf(p, "Right indent: N/A\n");
ME_DumpStyleEffect(&p, "Page break before:", pFmt, PFM_PAGEBREAKBEFORE);
}
void ME_SetParaFormat(ME_TextEditor *editor, ME_DisplayItem *para, PARAFORMAT2 *pFmt)
{
PARAFORMAT2 copy;
assert(sizeof(*para->member.para.pFmt) == sizeof(PARAFORMAT2));
ME_AddUndoItem(editor, diUndoSetParagraphFormat, para);
CopyMemory(&copy, para->member.para.pFmt, sizeof(PARAFORMAT2));
if (pFmt->dwMask & PFM_ALIGNMENT)
para->member.para.pFmt->wAlignment = pFmt->wAlignment;
/* FIXME to be continued (indents, bulleting and such) */
if (memcmp(&copy, para->member.para.pFmt, sizeof(PARAFORMAT2)))
para->member.para.nFlags &= ~MEPF_WRAPPED;
}
void ME_SetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
{
int nFrom, nTo;
ME_DisplayItem *para, *para_end, *run;
int nOffset;
ME_GetSelection(editor, &nFrom, &nTo);
if (nTo>nFrom) /* selection consists of chars from nFrom up to nTo-1 */
nTo--;
ME_RunOfsFromCharOfs(editor, nFrom, &run, &nOffset);
para = ME_GetParagraph(run);
ME_RunOfsFromCharOfs(editor, nTo, &run, &nOffset);
para_end = ME_GetParagraph(run);
do {
ME_SetParaFormat(editor, para, pFmt);
if (para == para_end)
break;
para = para->member.para.next_para;
} while(1);
ME_Repaint(editor);
}
void ME_GetParaFormat(ME_TextEditor *editor, ME_DisplayItem *para, PARAFORMAT2 *pFmt)
{
if (pFmt->cbSize >= sizeof(PARAFORMAT2))
{
CopyMemory(pFmt, para->member.para.pFmt, sizeof(PARAFORMAT2));
return;
}
CopyMemory(pFmt, para->member.para.pFmt, pFmt->cbSize);
}
void ME_GetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
{
int nFrom, nTo;
ME_DisplayItem *para, *para_end, *run;
int nOffset;
PARAFORMAT2 tmp;
ME_GetSelection(editor, &nFrom, &nTo);
if (nTo>nFrom) /* selection consists of chars from nFrom up to nTo-1 */
nTo--;
ME_RunOfsFromCharOfs(editor, nFrom, &run, &nOffset);
para = ME_GetParagraph(run);
ME_RunOfsFromCharOfs(editor, nTo, &run, &nOffset);
para_end = ME_GetParagraph(run);
ME_GetParaFormat(editor, para, pFmt);
if (para == para_end) return;
do {
ZeroMemory(&tmp, sizeof(tmp));
tmp.cbSize = sizeof(tmp);
ME_GetParaFormat(editor, para, &tmp);
assert(tmp.dwMask & PFM_ALIGNMENT);
if (pFmt->wAlignment != tmp.wAlignment)
pFmt->dwMask &= ~PFM_ALIGNMENT;
if (para == para_end)
return;
para = para->member.para.next_para;
} while(1);
}
2 extern IID_IRichEditOle
3 extern IID_IRichEditOleCallback
4 stdcall CreateTextServices(ptr ptr ptr)
5 extern IID_ITextServices
6 extern IID_ITextHost
7 extern IID_ITextHost2
8 stdcall REExtendedRegisterClass()
9 stdcall RichEdit10ANSIWndProc(ptr long long long)
10 stdcall RichEditANSIWndProc(ptr long long long)
/*
* RichEdit GUIDs
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <windef.h>
/* there is no way to be consistent across different sets of headers - mingw, Wine, Win32 SDK*/
#define RICHEDIT_GUID(name, l, w1, w2) \
GUID name = { l, w1, w2, {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}
RICHEDIT_GUID(IID_IRichEditOle, 0x00020d00, 0, 0);
RICHEDIT_GUID(IID_IRichEditOleCallback, 0x00020d03, 0, 0);
#define TEXTSERV_GUID(name, l, w1, w2, b1, b2) \
GUID name = { l, w1, w2, {b1, b2, 0x00, 0xaa, 0x00, 0x6c, 0xad, 0xc5}}
TEXTSERV_GUID(IID_ITextServices, 0x8d33f740, 0xcf58, 0x11ce, 0xa8, 0x9d);
TEXTSERV_GUID(IID_ITextHost, 0xc5bdd8d0, 0xd26e, 0x11ce, 0xa8, 0x9e);
TEXTSERV_GUID(IID_ITextHost2, 0xc5bdd8d0, 0xd26e, 0x11ce, 0xa8, 0x9e);
/*
* RichEdit - Operations on rows of text (rows are recreated during
* wrapping and are used for displaying the document, they don't keep any
* true document content; delete all rows, rewrap all paragraphs and
* you get them back).
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
ME_DisplayItem *ME_FindRowStart(ME_Context *c, ME_DisplayItem *item,
int nRelPos) {
ME_DisplayItem *para = ME_GetParagraph(item);
ME_MustBeWrapped(c, para);
if(nRelPos>=0) { /* if this or preceding row */
while(nRelPos<=0) {
ME_DisplayItem *item2 = ME_FindItemBack(item, diStartRowOrParagraph);
if (item2->type == diParagraph)
{
if (item2->member.para.prev_para == NULL)
return item;
/* if skipping to the preceding paragraph, ensure it's wrapped */
ME_MustBeWrapped(c, item2->member.para.prev_para);
item = item2;
continue;
}
else if (item2->type == diStartRow)
{
nRelPos++;
if (nRelPos>0)
return item;
item = item2;
continue;
}
assert(0 == "bug in FindItemBack(item, diStartRowOrParagraph)");
item = item2;
}
return item;
}
while(nRelPos>0) { /* if one of the next rows */
ME_DisplayItem *item2 = ME_FindItemFwd(item, diStartRowOrParagraph);
if (!item2)
return item;
if (item2->type == diParagraph)
{
if (item2->member.para.next_para == NULL)
return item;
continue;
}
item = item2;
nRelPos--;
}
return item;
}
/* I'm sure these functions would simplify some code in caret ops etc,
* I just didn't remember them when I wrote that code
*/
ME_DisplayItem *ME_RowStart(ME_DisplayItem *item) {
return ME_FindItemBackOrHere(item, diStartRow);
}
ME_DisplayItem *ME_RowEnd(ME_DisplayItem *item) {
ME_DisplayItem *item2 = ME_FindItemFwd(item, diStartRowOrParagraphOrEnd);
if (!item2) return NULL;
return ME_FindItemBack(item, diRun);
}
/*
* RichEdit - operations on runs (diRun, rectangular pieces of paragraphs).
* Splitting/joining runs. Adjusting offsets after deleting/adding content.
* Character/pixel conversions.
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
int ME_CanJoinRuns(ME_Run *run1, ME_Run *run2)
{
if ((run1->nFlags | run2->nFlags) & (MERF_ENDPARA | MERF_GRAPHICS))
return 0;
if (run1->style != run2->style)
return 0;
if ((run1->nFlags & MERF_STYLEFLAGS) != (run2->nFlags & MERF_STYLEFLAGS))
return 0;
return 1;
}
void ME_SkipAndPropagateCharOffset(ME_DisplayItem *p, int shift)
{
p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
assert(p);
ME_PropagateCharOffset(p, shift);
}
void ME_PropagateCharOffset(ME_DisplayItem *p, int shift)
{
if (p->type == diRun) /* propagate in all runs in this para */
{
TRACE("PropagateCharOffset(%s, %d)\n", debugstr_w(p->member.run.strText->szData), shift);
do {
p->member.run.nCharOfs += shift;
assert(p->member.run.nCharOfs >= 0);
p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
} while(p->type == diRun);
}
if (p->type == diParagraph) /* propagate in all next paras */
{
do {
p->member.para.nCharOfs += shift;
assert(p->member.para.nCharOfs >= 0);
p = p->member.para.next_para;
} while(p->type == diParagraph);
}
if (p->type == diTextEnd)
{
p->member.para.nCharOfs += shift;
assert(p->member.para.nCharOfs >= 0);
}
}
void ME_CheckCharOffsets(ME_TextEditor *editor)
{
ME_DisplayItem *p = editor->pBuffer->pFirst;
int ofs = 0, ofsp = 0;
if(TRACE_ON(richedit))
{
TRACE("---\n");
ME_DumpDocument(editor->pBuffer);
}
do {
p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
switch(p->type) {
case diTextEnd:
TRACE("tend, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs);
assert(ofsp+ofs == p->member.para.nCharOfs);
return;
case diParagraph:
TRACE("para, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs);
assert(ofsp+ofs == p->member.para.nCharOfs);
ofsp = p->member.para.nCharOfs;
ofs = 0;
break;
case diRun:
TRACE("run, real ofs = %d (+ofsp = %d), counted = %d, len = %d, txt = \"%s\", flags=%08x, fx&mask = %08lx\n",
p->member.run.nCharOfs, p->member.run.nCharOfs+ofsp, ofsp+ofs,
p->member.run.strText->nLen, debugstr_w(p->member.run.strText->szData),
p->member.run.nFlags,
p->member.run.style->fmt.dwMask & p->member.run.style->fmt.dwEffects);
assert(ofs == p->member.run.nCharOfs);
ofs += ME_StrLen(p->member.run.strText);
break;
default:
assert(0);
}
} while(1);
}
int ME_CharOfsFromRunOfs(ME_TextEditor *editor, ME_DisplayItem *pRun, int nOfs)
{
ME_DisplayItem *pPara;
assert(pRun->type == diRun);
assert(pRun->member.run.nCharOfs != -1);
pPara = ME_FindItemBack(pRun, diParagraph);
assert(pPara);
assert(pPara->type==diParagraph);
return pPara->member.para.nCharOfs + pRun->member.run.nCharOfs
+ ME_VPosToPos(pRun->member.run.strText, nOfs);
}
void ME_CursorFromCharOfs(ME_TextEditor *editor, int nCharOfs, ME_Cursor *pCursor)
{
ME_RunOfsFromCharOfs(editor, nCharOfs, &pCursor->pRun, &pCursor->nOffset);
}
void ME_RunOfsFromCharOfs(ME_TextEditor *editor, int nCharOfs, ME_DisplayItem **ppRun, int *pOfs)
{
ME_DisplayItem *pPara;
int nParaOfs;
pPara = editor->pBuffer->pFirst->member.para.next_para;
assert(pPara);
assert(ppRun);
assert(pOfs);
while (pPara->type == diParagraph)
{
nParaOfs = pPara->member.para.nCharOfs;
assert(nCharOfs >= nParaOfs);
if (nCharOfs < pPara->member.para.next_para->member.para.nCharOfs)
{
*ppRun = ME_FindItemFwd(pPara, diRun);
assert(*ppRun);
while (!((*ppRun)->member.run.nFlags & MERF_ENDPARA))
{
ME_DisplayItem *pNext = ME_FindItemFwd(*ppRun, diRun);
assert(pNext);
assert(pNext->type == diRun);
if (nCharOfs < nParaOfs + pNext->member.run.nCharOfs) {
*pOfs = ME_PosToVPos((*ppRun)->member.run.strText,
nCharOfs - nParaOfs - (*ppRun)->member.run.nCharOfs);
return;
}
*ppRun = pNext;
}
if (nCharOfs == nParaOfs + (*ppRun)->member.run.nCharOfs) {
*pOfs = 0;
return;
}
}
pPara = pPara->member.para.next_para;
}
*ppRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
*pOfs = 0;
assert((*ppRun)->member.run.nFlags & MERF_ENDPARA);
}
void ME_JoinRuns(ME_TextEditor *editor, ME_DisplayItem *p)
{
ME_DisplayItem *pNext = p->next;
int i;
assert(p->type == diRun && pNext->type == diRun);
assert(p->member.run.nCharOfs != -1);
for (i=0; i<editor->nCursors; i++) {
if (editor->pCursors[i].pRun == pNext) {
editor->pCursors[i].pRun = p;
editor->pCursors[i].nOffset += ME_StrVLen(p->member.run.strText);
}
}
ME_AppendString(p->member.run.strText, pNext->member.run.strText);
ME_Remove(pNext);
ME_DestroyDisplayItem(pNext);
ME_UpdateRunFlags(editor, &p->member.run);
if(TRACE_ON(richedit))
{
TRACE("Before check after join\n");
ME_CheckCharOffsets(editor);
TRACE("After check after join\n");
}
}
ME_DisplayItem *ME_SplitRun(ME_Context *c, ME_DisplayItem *item, int nVChar)
{
ME_TextEditor *editor = c->editor;
ME_DisplayItem *item2 = NULL;
ME_Run *run, *run2;
assert(item->member.run.nCharOfs != -1);
if(TRACE_ON(richedit))
{
TRACE("Before check before split\n");
ME_CheckCharOffsets(editor);
TRACE("After check before split\n");
}
run = &item->member.run;
TRACE("Before split: %s(%ld, %ld)\n", debugstr_w(run->strText->szData),
run->pt.x, run->pt.y);
item2 = ME_SplitRunSimple(editor, item, nVChar);
run2 = &item2->member.run;
ME_CalcRunExtent(c, run);
ME_CalcRunExtent(c, run2);
run2->pt.x = run->pt.x+run->nWidth;
run2->pt.y = run->pt.y;
if(TRACE_ON(richedit))
{
TRACE("Before check after split\n");
ME_CheckCharOffsets(editor);
TRACE("After check after split\n");
TRACE("After split: %s(%ld, %ld), %s(%ld, %ld)\n",
debugstr_w(run->strText->szData), run->pt.x, run->pt.y,
debugstr_w(run2->strText->szData), run2->pt.x, run2->pt.y);
}
return item2;
}
/* split a run starting from voffset */
ME_DisplayItem *ME_SplitRunSimple(ME_TextEditor *editor, ME_DisplayItem *item, int nVChar)
{
ME_Run *run = &item->member.run;
ME_DisplayItem *item2;
ME_Run *run2;
int i;
assert(nVChar > 0 && nVChar < ME_StrVLen(run->strText));
assert(item->type == diRun);
assert(!(item->member.run.nFlags & MERF_GRAPHICS));
assert(item->member.run.nCharOfs != -1);
item2 = ME_MakeRun(run->style,
ME_VSplitString(run->strText, nVChar), run->nFlags&MERF_SPLITMASK);
item2->member.run.nCharOfs = item->member.run.nCharOfs+
ME_VPosToPos(item->member.run.strText, nVChar);
run2 = &item2->member.run;
ME_InsertBefore(item->next, item2);
ME_UpdateRunFlags(editor, run);
ME_UpdateRunFlags(editor, run2);
for (i=0; i<editor->nCursors; i++) {
if (editor->pCursors[i].pRun == item &&
editor->pCursors[i].nOffset >= nVChar) {
assert(item2->type == diRun);
editor->pCursors[i].pRun = item2;
editor->pCursors[i].nOffset -= nVChar;
}
}
ME_GetParagraph(item)->member.para.nFlags &= ~MEPF_WRAPPED;
return item2;
}
/* split the start and final whitespace into separate runs */
/* returns the last run added */
/*
ME_DisplayItem *ME_SplitFurther(ME_TextEditor *editor, ME_DisplayItem *item)
{
int i, nVLen, nChanged;
assert(item->type == diRun);
assert(!(item->member.run.nFlags & MERF_GRAPHICS));
return item;
}
*/
ME_DisplayItem *ME_MakeRun(ME_Style *s, ME_String *strData, int nFlags)
{
ME_DisplayItem *item = ME_MakeDI(diRun);
item->member.run.style = s;
item->member.run.strText = strData;
item->member.run.nFlags = nFlags;
item->member.run.nCharOfs = -1;
ME_AddRefStyle(s);
return item;
}
ME_DisplayItem *ME_InsertRun(ME_TextEditor *editor, int nCharOfs, ME_DisplayItem *pItem)
{
ME_Cursor tmp;
ME_DisplayItem *pDI;
ME_UndoItem *pUI;
assert(pItem->type == diRun || pItem->type == diUndoInsertRun);
pUI = ME_AddUndoItem(editor, diUndoDeleteRun, NULL);
pUI->nStart = nCharOfs;
pUI->nLen = pItem->member.run.strText->nLen;
ME_CursorFromCharOfs(editor, nCharOfs, &tmp);
if (tmp.nOffset) {
tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
tmp.nOffset = 0;
}
pDI = ME_MakeRun(pItem->member.run.style, ME_StrDup(pItem->member.run.strText), pItem->member.run.nFlags);
pDI->member.run.nCharOfs = tmp.pRun->member.run.nCharOfs;
ME_InsertBefore(tmp.pRun, pDI);
TRACE("Shift length:%d\n", pDI->member.run.strText->nLen);
ME_PropagateCharOffset(tmp.pRun, pDI->member.run.strText->nLen);
ME_GetParagraph(tmp.pRun)->member.para.nFlags &= ~MEPF_WRAPPED;
return pDI;
}
static inline int ME_IsWSpace(WCHAR ch)
{
return ch <= ' ';
}
void ME_UpdateRunFlags(ME_TextEditor *editor, ME_Run *run)
{
assert(run->nCharOfs != -1);
if (ME_IsSplitable(run->strText))
run->nFlags |= MERF_SPLITTABLE;
else
run->nFlags &= ~MERF_SPLITTABLE;
if (!(run->nFlags & MERF_GRAPHICS)) {
if (ME_IsWhitespaces(run->strText))
run->nFlags |= MERF_WHITESPACE | MERF_STARTWHITE | MERF_ENDWHITE;
else
{
run->nFlags &= ~MERF_WHITESPACE;
if (ME_IsWSpace(ME_GetCharFwd(run->strText,0)))
run->nFlags |= MERF_STARTWHITE;
else
run->nFlags &= ~MERF_STARTWHITE;
if (ME_IsWSpace(ME_GetCharBack(run->strText,0)))
run->nFlags |= MERF_ENDWHITE;
else
run->nFlags &= ~MERF_ENDWHITE;
}
}
else
run->nFlags &= ~(MERF_WHITESPACE | MERF_STARTWHITE | MERF_ENDWHITE);
}
void ME_GetGraphicsSize(ME_TextEditor *editor, ME_Run *run, SIZE *pSize)
{
assert(run->nFlags & MERF_GRAPHICS);
pSize->cx = 64;
pSize->cy = 64;
}
int ME_CharFromPoint(ME_TextEditor *editor, int cx, ME_Run *run)
{
int fit = 0;
HGDIOBJ hOldFont;
HDC hDC;
SIZE sz;
if (!run->strText->nLen)
return 0;
if (run->nFlags & MERF_GRAPHICS)
{
SIZE sz;
ME_GetGraphicsSize(editor, run, &sz);
if (cx < sz.cx)
return 0;
return 1;
}
hDC = GetDC(editor->hWnd);
hOldFont = ME_SelectStyleFont(hDC, run->style);
GetTextExtentExPointW(hDC, run->strText->szData, run->strText->nLen,
cx, &fit, NULL, &sz);
assert(run->style->hFont);
SelectObject(hDC, hOldFont);
ReleaseDC(editor->hWnd, hDC);
return fit;
}
int ME_CharFromPointCursor(ME_TextEditor *editor, int cx, ME_Run *run)
{
int fit = 0, fit1 = 0;
HGDIOBJ hOldFont;
HDC hDC;
SIZE sz, sz2, sz3;
if (!run->strText->nLen)
return 0;
if (run->nFlags & MERF_GRAPHICS)
{
SIZE sz;
ME_GetGraphicsSize(editor, run, &sz);
if (cx < sz.cx/2)
return 0;
return 1;
}
hDC = GetDC(editor->hWnd);
hOldFont = ME_SelectStyleFont(hDC, run->style);
GetTextExtentExPointW(hDC, run->strText->szData, run->strText->nLen,
cx, &fit, NULL, &sz);
if (fit != run->strText->nLen)
{
int chars = 1;
GetTextExtentPoint32W(hDC, run->strText->szData, fit, &sz2);
fit1 = ME_StrRelPos(run->strText, fit, &chars);
GetTextExtentPoint32W(hDC, run->strText->szData, fit1, &sz3);
if (cx >= (sz2.cx+sz3.cx)/2)
fit = fit1;
}
SelectObject(hDC, hOldFont);
ReleaseDC(editor->hWnd, hDC);
return fit;
}
int ME_PointFromChar(ME_TextEditor *editor, ME_Run *pRun, int nOffset)
{
SIZE size;
HDC hDC = GetDC(editor->hWnd);
HGDIOBJ hOldFont;
if (pRun->nFlags & MERF_GRAPHICS)
{
if (!nOffset) return 0;
ME_GetGraphicsSize(editor, pRun, &size);
return 1;
}
hOldFont = ME_SelectStyleFont(hDC, pRun->style);
GetTextExtentPoint32W(hDC, pRun->strText->szData, nOffset, &size);
SelectObject(hDC, hOldFont);
ReleaseDC(editor->hWnd, hDC);
return size.cx;
}
void ME_GetTextExtent(ME_Context *c, LPCWSTR szText, int nChars, ME_Style *s,
SIZE *size)
{
HDC hDC = c->hDC;
HGDIOBJ hOldFont;
hOldFont = ME_SelectStyleFont(hDC, s);
GetTextExtentPoint32W(hDC, szText, nChars, size);
SelectObject(hDC, hOldFont);
}
SIZE ME_GetRunSize(ME_Context *c, ME_Run *run, int nLen)
{
SIZE size;
int nMaxLen = ME_StrVLen(run->strText);
if (nLen>nMaxLen)
nLen = nMaxLen;
if (run->nFlags & MERF_GRAPHICS)
{
ME_GetGraphicsSize(c->editor, run, &size);
return size;
}
ME_GetTextExtent(c, run->strText->szData, nLen, run->style, &size);
return size;
}
void ME_CalcRunExtent(ME_Context *c, ME_Run *run)
{
SIZE size;
int nEnd = ME_StrVLen(run->strText);
if (run->nFlags & MERF_GRAPHICS) {
ME_GetGraphicsSize(c->editor, run, &size);
run->nWidth = size.cx;
run->nAscent = size.cy;
run->nDescent = 0;
return;
}
ME_GetTextExtent(c, run->strText->szData, nEnd, run->style, &size);
run->nWidth = size.cx;
run->nAscent = run->style->tm.tmAscent;
run->nDescent = run->style->tm.tmDescent;
}
void ME_MustBeWrapped(ME_Context *c, ME_DisplayItem *para)
{
assert(para->type == diParagraph);
/* FIXME */
}
void ME_SetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
{
int nFrom, nTo;
ME_GetSelection(editor, &nFrom, &nTo);
if (nFrom == nTo)
{
ME_Style *s;
if (!editor->pBuffer->pCharStyle)
editor->pBuffer->pCharStyle = ME_GetInsertStyle(editor, 0);
s = ME_ApplyStyle(editor->pBuffer->pCharStyle, pFmt);
ME_ReleaseStyle(editor->pBuffer->pCharStyle);
editor->pBuffer->pCharStyle = s;
}
else
ME_SetCharFormat(editor, nFrom, nTo-nFrom, pFmt);
}
void ME_SetCharFormat(ME_TextEditor *editor, int nOfs, int nChars, CHARFORMAT2W *pFmt)
{
ME_Cursor tmp, tmp2;
ME_DisplayItem *para;
ME_CursorFromCharOfs(editor, nOfs, &tmp);
if (tmp.nOffset)
tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
ME_CursorFromCharOfs(editor, nOfs+nChars, &tmp2);
if (tmp2.nOffset)
tmp2.pRun = ME_SplitRunSimple(editor, tmp2.pRun, tmp2.nOffset);
para = ME_GetParagraph(tmp.pRun);
para->member.para.nFlags &= ~MEPF_WRAPPED;
while(tmp.pRun != tmp2.pRun)
{
ME_UndoItem *undo = NULL;
ME_Style *new_style = ME_ApplyStyle(tmp.pRun->member.run.style, pFmt);
/* ME_DumpStyle(new_style); */
undo = ME_AddUndoItem(editor, diUndoSetCharFormat, NULL);
if (undo) {
undo->nStart = tmp.pRun->member.run.nCharOfs+para->member.para.nCharOfs;
undo->nLen = tmp.pRun->member.run.strText->nLen;
undo->di.member.ustyle = tmp.pRun->member.run.style;
/* we'd have to addref undo..ustyle and release tmp...style
but they'd cancel each other out so we can do nothing instead */
}
else
ME_ReleaseStyle(tmp.pRun->member.run.style);
tmp.pRun->member.run.style = new_style;
tmp.pRun = ME_FindItemFwd(tmp.pRun, diRunOrParagraph);
if (tmp.pRun->type == diParagraph)
{
para = tmp.pRun;
tmp.pRun = ME_FindItemFwd(tmp.pRun, diRun);
if (tmp.pRun != tmp2.pRun)
para->member.para.nFlags &= ~MEPF_WRAPPED;
}
assert(tmp.pRun);
}
}
void ME_SetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *mod)
{
ME_Style *style;
ME_UndoItem *undo;
assert(mod->cbSize == sizeof(CHARFORMAT2W));
undo = ME_AddUndoItem(editor, diUndoSetDefaultCharFormat, NULL);
if (undo) {
undo->nStart = -1;
undo->nLen = -1;
undo->di.member.ustyle = editor->pBuffer->pDefaultStyle;
ME_AddRefStyle(undo->di.member.ustyle);
}
style = ME_ApplyStyle(editor->pBuffer->pDefaultStyle, mod);
editor->pBuffer->pDefaultStyle->fmt = style->fmt;
editor->pBuffer->pDefaultStyle->tm = style->tm;
ME_ReleaseStyle(style);
ME_MarkAllForWrapping(editor);
/* pcf = editor->pBuffer->pDefaultStyle->fmt; */
}
void ME_GetRunCharFormat(ME_TextEditor *editor, ME_DisplayItem *run, CHARFORMAT2W *pFmt)
{
ME_CopyCharFormat(pFmt, &run->member.run.style->fmt);
}
void ME_GetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
{
int nFrom, nTo;
ME_GetSelection(editor, &nFrom, &nTo);
ME_CopyCharFormat(pFmt, &editor->pBuffer->pDefaultStyle->fmt);
}
void ME_GetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
{
int nFrom, nTo;
ME_GetSelection(editor, &nFrom, &nTo);
if (nFrom == nTo && editor->pBuffer->pCharStyle)
{
ME_CopyCharFormat(pFmt, &editor->pBuffer->pCharStyle->fmt);
return;
}
ME_GetCharFormat(editor, nFrom, nTo, pFmt);
}
void ME_GetCharFormat(ME_TextEditor *editor, int nFrom, int nTo, CHARFORMAT2W *pFmt)
{
ME_DisplayItem *run, *run_end;
int nOffset, nOffset2;
CHARFORMAT2W tmp;
if (nTo>nFrom) /* selection consists of chars from nFrom up to nTo-1 */
nTo--;
ME_RunOfsFromCharOfs(editor, nFrom, &run, &nOffset);
if (nFrom == nTo) /* special case - if selection is empty, take previous char's formatting */
{
if (!nOffset)
{
ME_DisplayItem *tmp_run = ME_FindItemBack(run, diRunOrParagraph);
if (tmp_run->type == diRun) {
ME_GetRunCharFormat(editor, tmp_run, pFmt);
return;
}
}
ME_GetRunCharFormat(editor, run, pFmt);
return;
}
ME_RunOfsFromCharOfs(editor, nTo, &run_end, &nOffset2);
ME_GetRunCharFormat(editor, run, pFmt);
if (run == run_end) return;
do {
/* FIXME add more style feature comparisons */
int nAttribs = CFM_SIZE | CFM_FACE | CFM_COLOR;
int nEffects = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE;
run = ME_FindItemFwd(run, diRun);
ZeroMemory(&tmp, sizeof(tmp));
tmp.cbSize = sizeof(tmp);
ME_GetRunCharFormat(editor, run, &tmp);
assert((tmp.dwMask & nAttribs) == nAttribs);
assert((tmp.dwMask & nEffects) == nEffects);
/* reset flags that differ */
if (pFmt->yHeight != tmp.yHeight)
pFmt->dwMask &= ~CFM_SIZE;
if (pFmt->dwMask & CFM_FACE)
{
if (!(tmp.dwMask & CFM_FACE))
pFmt->dwMask &= ~CFM_FACE;
else if (lstrcmpW(pFmt->szFaceName, tmp.szFaceName))
pFmt->dwMask &= ~CFM_FACE;
}
if (pFmt->yHeight != tmp.yHeight)
pFmt->dwMask &= ~CFM_SIZE;
if (pFmt->dwMask & CFM_COLOR)
{
if (!((pFmt->dwEffects&CFE_AUTOCOLOR) & (tmp.dwEffects&CFE_AUTOCOLOR)))
{
if (pFmt->crTextColor != tmp.crTextColor)
pFmt->dwMask &= ~CFM_COLOR;
}
}
pFmt->dwMask &= ~((pFmt->dwEffects ^ tmp.dwEffects) & nEffects);
} while(run != run_end);
}
/*
* RichEdit - string operations
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
int ME_GetOptimalBuffer(int nLen)
{
return ((2*nLen+1)+128)&~63;
}
ME_String *ME_MakeString(LPCWSTR szText)
{
ME_String *s = ALLOC_OBJ(ME_String);
s->nLen = lstrlenW(szText);
s->nBuffer = ME_GetOptimalBuffer(s->nLen+1);
s->szData = ALLOC_N_OBJ(WCHAR, s->nBuffer);
lstrcpyW(s->szData, szText);
return s;
}
ME_String *ME_MakeStringN(LPCWSTR szText, int nMaxChars)
{
ME_String *s = ALLOC_OBJ(ME_String);
int i;
for (i=0; i<nMaxChars && szText[i]; i++)
;
s->nLen = i;
s->nBuffer = ME_GetOptimalBuffer(s->nLen+1);
s->szData = ALLOC_N_OBJ(WCHAR, s->nBuffer);
lstrcpynW(s->szData, szText, s->nLen+1);
return s;
}
ME_String *ME_StrDup(ME_String *s)
{
return ME_MakeStringN(s->szData, s->nLen);
}
void ME_DestroyString(ME_String *s)
{
FREE_OBJ(s->szData);
FREE_OBJ(s);
}
void ME_AppendString(ME_String *s1, ME_String *s2)
{
if (s1->nLen+s2->nLen+1 <= s1->nBuffer) {
lstrcpyW(s1->szData+s1->nLen, s2->szData);
s1->nLen += s2->nLen;
}
else
{
WCHAR *buf;
s1->nBuffer = ME_GetOptimalBuffer(s1->nLen+s2->nLen+1);
buf = ALLOC_N_OBJ(WCHAR, s1->nBuffer);
lstrcpyW(buf, s1->szData);
lstrcpyW(buf+s1->nLen, s2->szData);
FREE_OBJ(s1->szData);
s1->szData = buf;
s1->nLen += s2->nLen;
}
}
ME_String *ME_ConcatString(ME_String *s1, ME_String *s2)
{
ME_String *s = ALLOC_OBJ(ME_String);
s->nLen = s1->nLen+s2->nLen;
s->nBuffer = ME_GetOptimalBuffer(s1->nLen+s2->nLen+1);
s->szData = ALLOC_N_OBJ(WCHAR, s->nBuffer);
lstrcpyW(s->szData, s1->szData);
lstrcpyW(s->szData+s1->nLen, s2->szData);
return s;
}
ME_String *ME_VSplitString(ME_String *orig, int charidx)
{
ME_String *s;
/*if (charidx<0) charidx = 0;
if (charidx>orig->nLen) charidx = orig->nLen;
*/
assert(charidx>=0);
assert(charidx<=orig->nLen);
s = ME_MakeString(orig->szData+charidx);
orig->nLen = charidx;
orig->szData[charidx] = L'\0';
return s;
}
int ME_IsWhitespaces(ME_String *s)
{
/* FIXME multibyte */
WCHAR *pos = s->szData;
while(*pos++ == ' ')
;
pos--;
if (*pos)
return 0;
else
return 1;
}
int ME_IsSplitable(ME_String *s)
{
/* FIXME multibyte */
WCHAR *pos = s->szData;
WCHAR ch;
while(*pos++ == L' ')
;
pos--;
while((ch = *pos++) != 0)
{
if (ch == L' ')
return 1;
}
return 0;
}
/* FIXME multibyte */
/*
int ME_CalcSkipChars(ME_String *s)
{
int cnt = 0;
while(cnt < s->nLen && s->szData[s->nLen-1-cnt]==' ')
cnt++;
return cnt;
}
*/
int ME_StrLen(ME_String *s) {
return s->nLen;
}
int ME_StrVLen(ME_String *s) {
return s->nLen;
}
/* FIXME we use widechars, not multibytes, inside, no need for complex logic anymore */
int ME_StrRelPos(ME_String *s, int nVChar, int *pRelChars)
{
TRACE("%s,%d,&%d\n", debugstr_w(s->szData), nVChar, *pRelChars);
assert(*pRelChars);
if (!*pRelChars) return nVChar;
if (*pRelChars>0)
{
while(nVChar<s->nLen && *pRelChars>0)
{
nVChar++;
(*pRelChars)--;
}
return nVChar;
}
while(nVChar>0 && *pRelChars<0)
{
nVChar--;
(*pRelChars)++;
}
return nVChar;
}
int ME_StrRelPos2(ME_String *s, int nVChar, int nRelChars)
{
return ME_StrRelPos(s, nVChar, &nRelChars);
}
int ME_VPosToPos(ME_String *s, int nVPos)
{
return nVPos;
/*
int i = 0, len = 0;
if (!nVPos)
return 0;
while (i < s->nLen)
{
if (i == nVPos)
return len;
if (s->szData[i]=='\\') i++;
i++;
len++;
}
return len;
*/
}
int ME_PosToVPos(ME_String *s, int nPos)
{
if (!nPos)
return 0;
return ME_StrRelPos2(s, 0, nPos);
}
void ME_StrDeleteV(ME_String *s, int nVChar, int nChars)
{
int end_ofs;
assert(nVChar >=0 && nVChar <= s->nLen);
assert(nChars >= 0);
assert(nVChar+nChars <= s->nLen);
end_ofs = ME_StrRelPos2(s, nVChar, nChars);
assert(end_ofs <= s->nLen);
memmove(s->szData+nVChar, s->szData+end_ofs, 2*(s->nLen+1-end_ofs));
s->nLen -= (end_ofs - nVChar);
}
int ME_GetCharFwd(ME_String *s, int nPos)
{
int nVPos = 0;
assert(nPos < ME_StrLen(s));
if (nPos)
nVPos = ME_StrRelPos2(s, nVPos, nPos);
if (nVPos < s->nLen)
return s->szData[nVPos];
return -1;
}
int ME_GetCharBack(ME_String *s, int nPos)
{
int nVPos = ME_StrVLen(s);
assert(nPos < ME_StrLen(s));
if (nPos)
nVPos = ME_StrRelPos2(s, nVPos, -nPos);
if (nVPos < s->nLen)
return s->szData[nVPos];
return -1;
}
int ME_FindNonWhitespaceV(ME_String *s, int nVChar) {
int i;
for (i = nVChar; isspace(s->szData[i]) && i<s->nLen; i++)
;
return i;
}
/* note: returns offset of the first trailing whitespace */
int ME_ReverseFindNonWhitespaceV(ME_String *s, int nVChar) {
int i;
for (i = nVChar; i>0 && isspace(s->szData[i-1]); i--)
;
return i;
}
/* note: returns offset of the first trailing nonwhitespace */
int ME_ReverseFindWhitespaceV(ME_String *s, int nVChar) {
int i;
for (i = nVChar; i>0 && !isspace(s->szData[i-1]); i--)
;
return i;
}
LPWSTR ME_ToUnicode(HWND hWnd, LPVOID psz)
{
if (IsWindowUnicode(hWnd))
return (LPWSTR)psz;
else {
WCHAR *tmp;
int nChars = MultiByteToWideChar(CP_ACP, 0, (char *)psz, -1, NULL, 0);
if((tmp = ALLOC_N_OBJ(WCHAR, nChars)) != NULL)
MultiByteToWideChar(CP_ACP, 0, (char *)psz, -1, tmp, nChars);
return tmp;
}
}
void ME_EndToUnicode(HWND hWnd, LPVOID psz)
{
if (IsWindowUnicode(hWnd))
FREE_OBJ(psz);
}
LPSTR ME_ToAnsi(HWND hWnd, LPVOID psz)
{
if (!IsWindowUnicode(hWnd))
return (LPSTR)psz;
else {
char *tmp;
int nChars = WideCharToMultiByte(CP_ACP, 0, (WCHAR *)psz, -1, NULL, 0, NULL, NULL);
if((tmp = ALLOC_N_OBJ(char, nChars)) != NULL)
WideCharToMultiByte(CP_ACP, 0, (WCHAR *)psz, -1, tmp, nChars, NULL, NULL);
return tmp;
}
}
void ME_EndToAnsi(HWND hWnd, LPVOID psz)
{
if (!IsWindowUnicode(hWnd))
FREE_OBJ(psz);
}
/*
* RichEdit style management functions
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
static int all_refs = 0;
CHARFORMAT2W *ME_ToCF2W(CHARFORMAT2W *to, CHARFORMAT2W *from)
{
if (from->cbSize == sizeof(CHARFORMATA))
{
CHARFORMATA *f = (CHARFORMATA *)from;
CopyMemory(to, f, sizeof(*f)-sizeof(f->szFaceName));
to->cbSize = sizeof(CHARFORMAT2W);
if (f->dwMask & CFM_FACE) {
MultiByteToWideChar(0, 0, f->szFaceName, -1, to->szFaceName, sizeof(to->szFaceName)/sizeof(WCHAR));
}
return to;
}
if (from->cbSize == sizeof(CHARFORMATW))
{
CHARFORMATW *f = (CHARFORMATW *)from;
CopyMemory(to, f, sizeof(*f));
/* theoretically, we don't need to zero the remaining memory */
ZeroMemory(((CHARFORMATW *)to)+1, sizeof(CHARFORMAT2W)-sizeof(CHARFORMATW));
to->cbSize = sizeof(CHARFORMAT2W);
return to;
}
if (from->cbSize == sizeof(CHARFORMAT2A))
{
CHARFORMATA *f = (CHARFORMATA *)from;
/* copy the A structure without face name */
CopyMemory(to, f, sizeof(CHARFORMATA)-sizeof(f->szFaceName));
/* convert face name */
if (f->dwMask & CFM_FACE)
MultiByteToWideChar(0, 0, f->szFaceName, -1, to->szFaceName, sizeof(to->szFaceName));
/* copy the rest of the 2A structure to 2W */
CopyMemory(1+((CHARFORMATW *)from), f+1, sizeof(CHARFORMAT2A)-sizeof(CHARFORMATA));
to->cbSize = sizeof(CHARFORMAT2W);
return to;
}
assert(from->cbSize >= sizeof(CHARFORMAT2W));
return from;
}
void ME_CopyToCF2W(CHARFORMAT2W *to, CHARFORMAT2W *from)
{
if (ME_ToCF2W(to, from) == from)
CopyMemory(to, from, sizeof(*from));
}
CHARFORMAT2W *ME_ToCFAny(CHARFORMAT2W *to, CHARFORMAT2W *from)
{
assert(from->cbSize == sizeof(CHARFORMAT2W));
if (to->cbSize == sizeof(CHARFORMATA))
{
CHARFORMATA *t = (CHARFORMATA *)to;
CopyMemory(t, from, sizeof(*t)-sizeof(t->szFaceName));
WideCharToMultiByte(0, 0, from->szFaceName, -1, t->szFaceName, sizeof(t->szFaceName), 0, 0);
t->cbSize = sizeof(*t); /* it was overwritten by CopyMemory */
return to;
}
if (to->cbSize == sizeof(CHARFORMATW))
{
CHARFORMATW *t = (CHARFORMATW *)to;
CopyMemory(t, from, sizeof(*t));
t->cbSize = sizeof(*t); /* it was overwritten by CopyMemory */
return to;
}
if (to->cbSize == sizeof(CHARFORMAT2A))
{
CHARFORMAT2A *t = (CHARFORMAT2A *)to;
/* copy the A structure without face name */
CopyMemory(t, from, sizeof(CHARFORMATA)-sizeof(t->szFaceName));
/* convert face name */
WideCharToMultiByte(0, 0, from->szFaceName, -1, t->szFaceName, sizeof(t->szFaceName), 0, 0);
/* copy the rest of the 2A structure to 2W */
CopyMemory(&t->wWeight, &from->wWeight, sizeof(CHARFORMAT2A)-sizeof(CHARFORMATA));
t->cbSize = sizeof(*t); /* it was overwritten by CopyMemory */
return to;
}
assert(to->cbSize >= sizeof(CHARFORMAT2W));
return from;
}
void ME_CopyToCFAny(CHARFORMAT2W *to, CHARFORMAT2W *from)
{
if (ME_ToCFAny(to, from) == from)
CopyMemory(to, from, to->cbSize);
}
ME_Style *ME_MakeStyle(CHARFORMAT2W *style) {
CHARFORMAT2W styledata;
ME_Style *s = ALLOC_OBJ(ME_Style);
style = ME_ToCF2W(&styledata, style);
memset(s, 0, sizeof(ME_Style));
if (style->cbSize <= sizeof(CHARFORMAT2W))
CopyMemory(&s->fmt, style, style->cbSize);
else
CopyMemory(&s->fmt, style, sizeof(CHARFORMAT2W));
s->fmt.cbSize = sizeof(CHARFORMAT2W);
s->nSequence = -2;
s->nRefs = 1;
s->hFont = NULL;
all_refs++;
return s;
}
#define COPY_STYLE_ITEM(mask, member) \
if (style->dwMask & mask) { \
s->fmt.dwMask |= mask;\
s->fmt.member = style->member;\
}
#define COPY_STYLE_ITEM_MEMCPY(mask, member) \
if (style->dwMask & mask) { \
s->fmt.dwMask |= mask;\
CopyMemory(s->fmt.member, style->member, sizeof(style->member));\
}
void ME_InitCharFormat2W(CHARFORMAT2W *pFmt)
{
ZeroMemory(pFmt, sizeof(CHARFORMAT2W));
pFmt->cbSize = sizeof(CHARFORMAT2W);
}
ME_Style *ME_ApplyStyle(ME_Style *sSrc, CHARFORMAT2W *style)
{
CHARFORMAT2W styledata;
ME_Style *s = ME_MakeStyle(&sSrc->fmt);
style = ME_ToCF2W(&styledata, style);
COPY_STYLE_ITEM(CFM_ANIMATION, bAnimation);
COPY_STYLE_ITEM(CFM_BACKCOLOR, crBackColor);
COPY_STYLE_ITEM(CFM_CHARSET, bCharSet);
COPY_STYLE_ITEM(CFM_COLOR, crTextColor);
COPY_STYLE_ITEM_MEMCPY(CFM_FACE, szFaceName);
COPY_STYLE_ITEM(CFM_KERNING, wKerning);
COPY_STYLE_ITEM(CFM_LCID, lcid);
COPY_STYLE_ITEM(CFM_OFFSET, yOffset);
COPY_STYLE_ITEM(CFM_REVAUTHOR, bRevAuthor);
COPY_STYLE_ITEM(CFM_SIZE, yHeight);
COPY_STYLE_ITEM(CFM_SPACING, sSpacing);
COPY_STYLE_ITEM(CFM_STYLE, sStyle);
COPY_STYLE_ITEM(CFM_UNDERLINETYPE, bUnderlineType);
COPY_STYLE_ITEM(CFM_WEIGHT, wWeight);
s->fmt.dwEffects &= ~(style->dwMask);
s->fmt.dwEffects |= style->dwEffects & style->dwMask;
s->fmt.dwMask |= style->dwMask;
if (style->dwMask & CFM_COLOR)
{
if (style->dwEffects & CFE_AUTOCOLOR)
s->fmt.dwEffects |= CFE_AUTOCOLOR;
else
s->fmt.dwEffects &= ~CFE_AUTOCOLOR;
}
return s;
}
void ME_CopyCharFormat(CHARFORMAT2W *pDest, CHARFORMAT2W *pSrc)
{
/* using this with non-2W structs is forbidden */
assert(pSrc->cbSize == sizeof(CHARFORMAT2W));
assert(pDest->cbSize == sizeof(CHARFORMAT2W));
CopyMemory(pDest, pSrc, sizeof(CHARFORMAT2W));
}
static void ME_DumpStyleEffect(char **p, const char *name, CHARFORMAT2W *fmt, int mask)
{
*p += sprintf(*p, "%-22s%s\n", name, (fmt->dwMask & mask) ? ((fmt->dwEffects & mask) ? "YES" : "no") : "N/A");
}
void ME_DumpStyle(ME_Style *s)
{
char buf[2048];
ME_DumpStyleToBuf(&s->fmt, buf);
TRACE("%s\n", buf);
}
void ME_DumpStyleToBuf(CHARFORMAT2W *pFmt, char buf[2048])
{
/* FIXME only CHARFORMAT styles implemented */
/* this function sucks, doesn't check for buffer overruns but it's "good enough" as for debug code */
char *p;
p = buf;
p += sprintf(p, "Font face: ");
if (pFmt->dwMask & CFM_FACE) {
WCHAR *q = pFmt->szFaceName;
while(*q) {
*p++ = (*q > 255) ? '?' : *q;
q++;
}
} else
p += sprintf(p, "N/A");
if (pFmt->dwMask & CFM_SIZE)
p += sprintf(p, "\nFont size: %d\n", (int)pFmt->yHeight);
else
p += sprintf(p, "\nFont size: N/A\n");
if (pFmt->dwMask & CFM_OFFSET)
p += sprintf(p, "Char offset: %d\n", (int)pFmt->yOffset);
else
p += sprintf(p, "Char offset: N/A\n");
if (pFmt->dwMask & CFM_CHARSET)
p += sprintf(p, "Font charset: %d\n", (int)pFmt->bCharSet);
else
p += sprintf(p, "Font charset: N/A\n");
/* I'm assuming CFM_xxx and CFE_xxx are the same values, fortunately it IS true wrt used flags*/
ME_DumpStyleEffect(&p, "Font bold:", pFmt, CFM_BOLD);
ME_DumpStyleEffect(&p, "Font italic:", pFmt, CFM_ITALIC);
ME_DumpStyleEffect(&p, "Font underline:", pFmt, CFM_UNDERLINE);
ME_DumpStyleEffect(&p, "Font strikeout:", pFmt, CFM_STRIKEOUT);
p += sprintf(p, "Text color: ");
if (pFmt->dwMask & CFM_COLOR)
{
if (pFmt->dwEffects & CFE_AUTOCOLOR)
p += sprintf(p, "auto\n");
else
p += sprintf(p, "%06x\n", (int)pFmt->crTextColor);
}
else
p += sprintf(p, "N/A\n");
ME_DumpStyleEffect(&p, "Text protected:", pFmt, CFM_PROTECTED);
}
void ME_UnprepareStyle(ME_Style *s)
{
if (s->hFont) {
DeleteObject(s->hFont);
s->hFont = NULL;
s->nSequence = -2;
}
}
void ME_PrepareStyleFromDC(ME_Style *s, HDC hDC, int nSequence)
{
HGDIOBJ hOldFont;
LOGFONTW lf;
int rx, ry;
if (nSequence == s->nSequence && s->hFont)
return;
/* assert(s); */
rx = GetDeviceCaps(hDC, LOGPIXELSX);
ry = GetDeviceCaps(hDC, LOGPIXELSY);
if (s->hFont) {
DeleteObject(s->hFont);
s->hFont = NULL;
}
ZeroMemory(&lf, sizeof(lf));
lstrcpyW(lf.lfFaceName, s->fmt.szFaceName);
lf.lfHeight = -s->fmt.yHeight*ry/1440;
lf.lfWeight = 400;
if (s->fmt.dwEffects & s->fmt.dwMask & CFM_BOLD)
lf.lfWeight = 700;
if (s->fmt.dwEffects & s->fmt.dwMask & CFM_WEIGHT)
lf.lfWeight = s->fmt.wWeight;
if (s->fmt.dwEffects & s->fmt.dwMask & CFM_ITALIC)
lf.lfItalic = 1;
if (s->fmt.dwEffects & s->fmt.dwMask & CFM_UNDERLINE)
lf.lfUnderline = 1;
if (s->fmt.dwEffects & s->fmt.dwMask & CFM_STRIKEOUT)
lf.lfStrikeOut = 1;
/*lf.lfQuality = PROOF_QUALITY; */
lf.lfPitchAndFamily = s->fmt.bPitchAndFamily;
lf.lfCharSet = s->fmt.bCharSet;
s->hFont = CreateFontIndirectW(&lf);
assert(s->hFont);
GetObjectW(s->hFont, sizeof(LOGFONTW), &lf);
hOldFont = SelectObject(hDC, s->hFont);
GetTextMetricsW(hDC, &s->tm);
SelectObject(hDC, hOldFont);
s->nSequence = nSequence;
}
HFONT ME_SelectStyleFont(HDC hDC, ME_Style *s)
{
HFONT hOldFont;
ME_PrepareStyleFromDC(s, hDC, -1);
assert(s->hFont);
hOldFont = SelectObject(hDC, s->hFont);
return hOldFont;
}
void ME_PrepareStyle(ME_Context *c, ME_Style *s)
{
ME_PrepareStyleFromDC(s, c->hDC, c->nSequence);
}
void ME_DestroyStyle(ME_Style *s) {
if (s->hFont)
{
DeleteObject(s->hFont);
s->hFont = NULL;
}
FREE_OBJ(s);
}
void ME_AddRefStyle(ME_Style *s)
{
assert(s->nRefs>0); /* style with 0 references isn't supposed to exist */
s->nRefs++;
all_refs++;
}
void ME_ReleaseStyle(ME_Style *s)
{
s->nRefs--;
all_refs--;
if (s->nRefs==0)
TRACE("destroy style %p, total refs=%d\n", s, all_refs);
else
TRACE("release style %p, new refs=%d, total refs=%d\n", s, s->nRefs, all_refs);
if (!all_refs) FIXME("all style references freed (good!)\n");
assert(s->nRefs>=0);
if (!s->nRefs)
ME_DestroyStyle(s);
}
ME_Style *ME_GetInsertStyle(ME_TextEditor *editor, int nCursor) {
if (ME_IsSelection(editor))
{
ME_Cursor c;
int from, to;
ME_GetSelection(editor, &from, &to);
ME_CursorFromCharOfs(editor, from, &c);
ME_AddRefStyle(c.pRun->member.run.style);
return c.pRun->member.run.style;
}
if (editor->pBuffer->pCharStyle) {
ME_AddRefStyle(editor->pBuffer->pCharStyle);
return editor->pBuffer->pCharStyle;
}
else
{
ME_Cursor *pCursor = &editor->pCursors[nCursor];
ME_DisplayItem *pRunItem = pCursor->pRun;
ME_DisplayItem *pPrevItem = NULL;
if (pCursor->nOffset) {
ME_Run *pRun = &pRunItem->member.run;
ME_AddRefStyle(pRun->style);
return pRun->style;
}
pPrevItem = ME_FindItemBack(pRunItem, diRunOrParagraph);
if (pPrevItem->type == diRun)
{
ME_AddRefStyle(pPrevItem->member.run.style);
return pPrevItem->member.run.style;
}
else
{
ME_AddRefStyle(pRunItem->member.run.style);
return pRunItem->member.run.style;
}
}
}
void ME_SaveTempStyle(ME_TextEditor *editor)
{
ME_Style *old_style = editor->pBuffer->pCharStyle;
editor->pBuffer->pCharStyle = ME_GetInsertStyle(editor, 0);
if (old_style)
ME_ReleaseStyle(old_style);
}
void ME_ClearTempStyle(ME_TextEditor *editor)
{
if (!editor->pBuffer->pCharStyle) return;
ME_ReleaseStyle(editor->pBuffer->pCharStyle);
editor->pBuffer->pCharStyle = NULL;
}
/*
* RichEdit - functions dealing with editor object
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
void ME_EmptyUndoStack(ME_TextEditor *editor)
{
ME_DisplayItem *p, *pNext;
TRACE("Emptying undo stack\n");
p = editor->pUndoStack;
editor->pUndoStack = NULL;
while(p) {
pNext = p->next;
ME_DestroyDisplayItem(p);
p = pNext;
}
p = editor->pRedoStack;
editor->pRedoStack = NULL;
while(p) {
pNext = p->next;
ME_DestroyDisplayItem(p);
p = pNext;
}
}
ME_UndoItem *ME_AddUndoItem(ME_TextEditor *editor, ME_DIType type, ME_DisplayItem *pdi) {
if (editor->nUndoMode == umIgnore)
return NULL;
else
{
ME_DisplayItem *pItem = (ME_DisplayItem *)ALLOC_OBJ(ME_UndoItem);
switch(type)
{
case diUndoEndTransaction:
break;
case diUndoSetParagraphFormat:
assert(pdi);
CopyMemory(&pItem->member.para, &pdi->member.para, sizeof(ME_Paragraph));
pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
CopyMemory(pItem->member.para.pFmt, pdi->member.para.pFmt, sizeof(PARAFORMAT2));
break;
case diUndoInsertRun:
assert(pdi);
CopyMemory(&pItem->member.run, &pdi->member.run, sizeof(ME_Run));
pItem->member.run.strText = ME_StrDup(pItem->member.run.strText);
ME_AddRefStyle(pItem->member.run.style);
break;
case diUndoSetCharFormat:
case diUndoSetDefaultCharFormat:
break;
case diUndoDeleteRun:
case diUndoJoinParagraphs:
break;
case diUndoSplitParagraph:
pItem->member.para.pFmt = ALLOC_OBJ(PARAFORMAT2);
pItem->member.para.pFmt->cbSize = sizeof(PARAFORMAT2);
pItem->member.para.pFmt->dwMask = 0;
break;
default:
assert(0 == "AddUndoItem, unsupported item type");
return NULL;
}
pItem->type = type;
pItem->prev = NULL;
if (editor->nUndoMode == umAddToUndo || editor->nUndoMode == umAddBackToUndo)
{
if (editor->nUndoMode == umAddToUndo)
TRACE("Pushing id=%s to undo stack, deleting redo stack\n", ME_GetDITypeName(type));
else
TRACE("Pushing id=%s to undo stack\n", ME_GetDITypeName(type));
pItem->next = editor->pUndoStack;
if (editor->pUndoStack)
editor->pUndoStack->prev = pItem;
editor->pUndoStack = pItem;
/* any new operation (not redo) clears the redo stack */
if (editor->nUndoMode == umAddToUndo) {
ME_DisplayItem *p = editor->pRedoStack;
while(p)
{
ME_DisplayItem *pp = p->next;
ME_DestroyDisplayItem(p);
p = pp;
}
editor->pRedoStack = NULL;
}
}
else if (editor->nUndoMode == umAddToRedo)
{
TRACE("Pushing id=%s to redo stack\n", ME_GetDITypeName(type));
pItem->next = editor->pRedoStack;
if (editor->pRedoStack)
editor->pRedoStack->prev = pItem;
editor->pRedoStack = pItem;
}
else
assert(0);
return (ME_UndoItem *)pItem;
}
}
void ME_CommitUndo(ME_TextEditor *editor) {
assert(editor->nUndoMode == umAddToUndo);
/* no transactions, no need to commit */
if (!editor->pUndoStack)
return;
/* no need to commit empty transactions */
if (editor->pUndoStack->type == diUndoEndTransaction)
return;
ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
ME_SendSelChange(editor);
editor->nModifyStep++;
}
void ME_PlayUndoItem(ME_TextEditor *editor, ME_DisplayItem *pItem)
{
ME_UndoItem *pUItem = (ME_UndoItem *)pItem;
TRACE("Playing undo/redo item, id=%s\n", ME_GetDITypeName(pItem->type));
switch(pItem->type)
{
case diUndoEndTransaction:
assert(0);
case diUndoSetParagraphFormat:
{
ME_Cursor tmp;
ME_CursorFromCharOfs(editor, pItem->member.para.nCharOfs, &tmp);
ME_SetParaFormat(editor, ME_FindItemBack(tmp.pRun, diParagraph), pItem->member.para.pFmt);
break;
}
case diUndoSetCharFormat:
{
ME_SetCharFormat(editor, pUItem->nStart, pUItem->nLen, &pItem->member.ustyle->fmt);
break;
}
case diUndoSetDefaultCharFormat:
{
ME_SetDefaultCharFormat(editor, &pItem->member.ustyle->fmt);
break;
}
case diUndoInsertRun:
{
ME_InsertRun(editor, pItem->member.run.nCharOfs, pItem);
break;
}
case diUndoDeleteRun:
{
ME_InternalDeleteText(editor, pUItem->nStart, pUItem->nLen);
break;
}
case diUndoJoinParagraphs:
{
ME_Cursor tmp;
ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
/* the only thing that's needed is paragraph offset, so no need to split runs */
ME_JoinParagraphs(editor, ME_GetParagraph(tmp.pRun));
break;
}
case diUndoSplitParagraph:
{
ME_Cursor tmp;
ME_DisplayItem *new_para;
ME_CursorFromCharOfs(editor, pUItem->nStart, &tmp);
if (tmp.nOffset)
tmp.pRun = ME_SplitRunSimple(editor, tmp.pRun, tmp.nOffset);
new_para = ME_SplitParagraph(editor, tmp.pRun, tmp.pRun->member.run.style);
assert(pItem->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
CopyMemory(new_para->member.para.pFmt, pItem->member.para.pFmt, sizeof(PARAFORMAT2));
break;
}
default:
assert(0 == "PlayUndoItem, unexpected type");
}
}
void ME_Undo(ME_TextEditor *editor) {
ME_DisplayItem *p;
ME_UndoMode nMode = editor->nUndoMode;
assert(nMode == umAddToUndo || nMode == umIgnore);
/* no undo items ? */
if (!editor->pUndoStack)
return;
/* watch out for uncommited transactions ! */
assert(editor->pUndoStack->type == diUndoEndTransaction);
editor->nUndoMode = umAddToRedo;
p = editor->pUndoStack->next;
ME_DestroyDisplayItem(editor->pUndoStack);
do {
ME_DisplayItem *pp = p;
ME_PlayUndoItem(editor, p);
p = p->next;
ME_DestroyDisplayItem(pp);
} while(p && p->type != diUndoEndTransaction);
ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
editor->pUndoStack = p;
if (p)
p->prev = NULL;
editor->nUndoMode = nMode;
editor->nModifyStep--;
ME_UpdateRepaint(editor);
}
void ME_Redo(ME_TextEditor *editor) {
ME_DisplayItem *p;
ME_UndoMode nMode = editor->nUndoMode;
assert(nMode == umAddToUndo || nMode == umIgnore);
/* no redo items ? */
if (!editor->pRedoStack)
return;
/* watch out for uncommited transactions ! */
assert(editor->pRedoStack->type == diUndoEndTransaction);
editor->nUndoMode = umAddBackToUndo;
p = editor->pRedoStack->next;
ME_DestroyDisplayItem(editor->pRedoStack);
do {
ME_DisplayItem *pp = p;
ME_PlayUndoItem(editor, p);
p = p->next;
ME_DestroyDisplayItem(pp);
} while(p && p->type != diUndoEndTransaction);
ME_AddUndoItem(editor, diUndoEndTransaction, NULL);
editor->pRedoStack = p;
if (p)
p->prev = NULL;
editor->nUndoMode = nMode;
editor->nModifyStep++;
ME_UpdateRepaint(editor);
}
/*
* RichEdit - Paragraph wrapping. Don't try to understand it. You've been
* warned !
*
* Copyright 2004 by Krzysztof Foltman
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "editor.h"
WINE_DEFAULT_DEBUG_CHANNEL(richedit);
/*
* Unsolved problems:
*
* - center and right align in WordPad omits all spaces at the start, we don't
* - objects/images are not handled yet
* - no tabs
*/
ME_DisplayItem *ME_MakeRow(int height, int baseline, int width)
{
ME_DisplayItem *item = ME_MakeDI(diStartRow);
item->member.row.nHeight = height;
item->member.row.nBaseline = baseline;
item->member.row.nWidth = width;
return item;
}
void ME_BeginRow(ME_WrapContext *wc)
{
wc->pRowStart = NULL;
wc->bOverflown = FALSE;
wc->pLastSplittableRun = NULL;
wc->nAvailWidth = wc->nTotalWidth - (wc->nRow ? wc->nLeftMargin : wc->nFirstMargin) - wc->nRightMargin;
wc->pt.x = 0;
}
void ME_InsertRowStart(ME_WrapContext *wc, ME_DisplayItem *pEnd)
{
ME_DisplayItem *p, *row, *para;
int ascent = 0, descent = 0, width=0, shift = 0, align = 0;
/* wrap text */
para = ME_GetParagraph(wc->pRowStart);
for (p = wc->pRowStart; p!=pEnd; p = p->next)
{
/* ENDPARA run shouldn't affect row height, except if it's the only run in the paragraph */
if (p->type==diRun && ((p==wc->pRowStart) || !(p->member.run.nFlags & MERF_ENDPARA))) { /* FIXME add more run types */
if (p->member.run.nAscent>ascent)
ascent = p->member.run.nAscent;
if (p->member.run.nDescent>descent)
descent = p->member.run.nDescent;
if (!(p->member.run.nFlags & (MERF_ENDPARA|MERF_SKIPPED)))
width += p->member.run.nWidth;
}
}
row = ME_MakeRow(ascent+descent, ascent, width);
row->member.row.nYPos = wc->pt.y;
row->member.row.nLMargin = (!wc->nRow ? wc->nFirstMargin : wc->nLeftMargin);
row->member.row.nRMargin = wc->nRightMargin;
assert(para->member.para.pFmt->dwMask & PFM_ALIGNMENT);
align = para->member.para.pFmt->wAlignment;
if (align == PFA_CENTER)
shift = (wc->nAvailWidth-width)/2;
if (align == PFA_RIGHT)
shift = wc->nAvailWidth-width;
for (p = wc->pRowStart; p!=pEnd; p = p->next)
{
if (p->type==diRun) { /* FIXME add more run types */
p->member.run.pt.x += row->member.row.nLMargin+shift;
}
}
ME_InsertBefore(wc->pRowStart, row);
wc->nRow++;
wc->pt.y += ascent+descent;
ME_BeginRow(wc);
}
void ME_WrapEndParagraph(ME_WrapContext *wc, ME_DisplayItem *p)
{
if (wc->pRowStart)
ME_InsertRowStart(wc, p->next);
/*
p = p->member.para.prev_para->next;
while(p) {
if (p->type == diParagraph || p->type == diTextEnd)
return;
if (p->type == diRun)
{
ME_Run *run = &p->member.run;
TRACE("%s - (%d, %d)\n", debugstr_w(run->strText->szData), run->pt.x, run->pt.y);
}
p = p->next;
}
*/
}
void ME_WrapSizeRun(ME_WrapContext *wc, ME_DisplayItem *p)
{
/* FIXME compose style (out of character and paragraph styles) here */
ME_UpdateRunFlags(wc->context->editor, &p->member.run);
ME_CalcRunExtent(wc->context, &p->member.run);
}
ME_DisplayItem *ME_MaximizeSplit(ME_WrapContext *wc, ME_DisplayItem *p, int i)
{
ME_DisplayItem *pp, *piter = p;
int j;
if (!i)
return NULL;
j = ME_ReverseFindNonWhitespaceV(p->member.run.strText, i);
if (j>0) {
pp = ME_SplitRun(wc->context, piter, j);
wc->pt.x += piter->member.run.nWidth;
return pp;
}
else
{
pp = piter;
/* omit all spaces before split point */
while(piter != wc->pRowStart)
{
piter = ME_FindItemBack(piter, diRun);
if (piter->member.run.nFlags & MERF_WHITESPACE)
{
pp = piter;
continue;
}
if (piter->member.run.nFlags & MERF_ENDWHITE)
{
j = ME_ReverseFindNonWhitespaceV(piter->member.run.strText, i);
pp = ME_SplitRun(wc->context, piter, i);
wc->pt = pp->member.run.pt;
return pp;
}
/* this run is the end of spaces, so the run edge is a good point to split */
wc->pt = pp->member.run.pt;
wc->bOverflown = TRUE;
TRACE("Split point is: %s|%s\n", debugstr_w(piter->member.run.strText->szData), debugstr_w(pp->member.run.strText->szData));
return pp;
}
wc->pt = piter->member.run.pt;
return piter;
}
}
ME_DisplayItem *ME_SplitByBacktracking(ME_WrapContext *wc, ME_DisplayItem *p, int loc)
{
ME_DisplayItem *piter = p, *pp;
int i, idesp, len;
ME_Run *run = &p->member.run;
idesp = i = ME_CharFromPoint(wc->context->editor, loc, run);
len = ME_StrVLen(run->strText);
assert(len>0);
assert(i<len);
if (i) {
/* don't split words */
i = ME_ReverseFindWhitespaceV(run->strText, i);
pp = ME_MaximizeSplit(wc, p, i);
if (pp)
return pp;
}
TRACE("Must backtrack to split at: %s\n", debugstr_w(p->member.run.strText->szData));
if (wc->pLastSplittableRun)
{
if (wc->pLastSplittableRun->member.run.nFlags & MERF_GRAPHICS)
{
wc->pt = wc->ptLastSplittableRun;
return wc->pLastSplittableRun;
}
else if (wc->pLastSplittableRun->member.run.nFlags & MERF_SPLITTABLE)
{
/* the following two lines are just to check if we forgot to call UpdateRunFlags earlier,
they serve no other purpose */
ME_UpdateRunFlags(wc->context->editor, run);
assert((wc->pLastSplittableRun->member.run.nFlags & MERF_SPLITTABLE));
piter = wc->pLastSplittableRun;
run = &piter->member.run;
len = ME_StrVLen(run->strText);
/* don't split words */
i = ME_ReverseFindWhitespaceV(run->strText, len);
if (i == len)
i = ME_ReverseFindNonWhitespaceV(run->strText, len);
if (i) {
ME_DisplayItem *piter2 = ME_SplitRun(wc->context, piter, i);
wc->pt = piter2->member.run.pt;
return piter2;
}
/* splittable = must have whitespaces */
assert(0 == "Splittable, but no whitespaces");
}
else
{
/* restart from the first run beginning with spaces */
wc->pt = wc->ptLastSplittableRun;
return wc->pLastSplittableRun;
}
}
TRACE("Backtracking failed, trying desperate: %s\n", debugstr_w(p->member.run.strText->szData));
/* OK, no better idea, so assume we MAY split words if we can split at all*/
if (idesp)
return ME_SplitRun(wc->context, piter, idesp);
else
if (wc->pRowStart && piter != wc->pRowStart)
{
/* don't need to break current run, because it's possible to split
before this run */
wc->bOverflown = TRUE;
return piter;
}
else
{
/* split point inside first character - no choice but split after that char */
int chars = 1;
int pos2 = ME_StrRelPos(run->strText, 0, &chars);
if (pos2 != len) {
/* the run is more than 1 char, so we may split */
return ME_SplitRun(wc->context, piter, pos2);
}
/* the run is one char, can't split it */
return piter;
}
}
ME_DisplayItem *ME_WrapHandleRun(ME_WrapContext *wc, ME_DisplayItem *p)
{
ME_DisplayItem *pp;
ME_Run *run;
int len;
assert(p->type == diRun);
if (!wc->pRowStart)
wc->pRowStart = p;
ME_WrapSizeRun(wc, p);
run = &p->member.run;
run->pt.x = wc->pt.x;
run->pt.y = wc->pt.y;
len = ME_StrVLen(run->strText);
if (wc->bOverflown) /* just skipping final whitespaces */
{
if (run->nFlags & MERF_WHITESPACE) {
p->member.run.nFlags |= MERF_SKIPPED;
/* wc->pt.x += run->nWidth; */
/* skip runs consisting of only whitespaces */
return p->next;
}
if (run->nFlags & MERF_STARTWHITE) {
/* try to split the run at the first non-white char */
int black;
black = ME_FindNonWhitespaceV(run->strText, 0);
if (black) {
wc->bOverflown = FALSE;
pp = ME_SplitRun(wc->context, p, black);
p->member.run.nFlags |= MERF_SKIPPED;
/*
run->pt = wc->pt;
wc->pt.x += run->nWidth;
*/
ME_InsertRowStart(wc, pp);
return pp;
}
}
/* black run: the row goes from pRowStart to the previous run */
ME_InsertRowStart(wc, p);
return p;
}
/* we're not at the end of the row */
/* will current run fit? */
if (wc->pt.x + run->nWidth > wc->nAvailWidth)
{
int loc = wc->nAvailWidth - wc->pt.x;
/* total white run ? */
if (run->nFlags & MERF_WHITESPACE) {
/* let the overflow logic handle it */
wc->bOverflown = TRUE;
return p;
}
/* graphics - we can split before */
if (run->nFlags & MERF_GRAPHICS) {
wc->bOverflown = TRUE;
return p;
}
/* can we separate out the last spaces ? (to use overflow logic later) */
if (run->nFlags & MERF_ENDWHITE)
{
/* we aren't sure if it's *really* necessary, it's a good start however */
int black = ME_ReverseFindNonWhitespaceV(run->strText, len);
ME_SplitRun(wc->context, p, black);
/* handle both parts again */
return p;
}
/* determine the split point by backtracking */
pp = ME_SplitByBacktracking(wc, p, loc);
if (pp == wc->pRowStart)
{
/* we had only spaces so far, entire content can be omitted */
wc->pt.x = 0;
return p->next;
}
if (p != pp) /* found a suitable split point */
{
wc->bOverflown = TRUE;
return pp;
}
/* we detected that it's best to split on start of this run */
if (wc->bOverflown)
return pp;
ERR("failure!\n");
/* not found anything - writing over margins is the only option left */
}
if ((run->nFlags & (MERF_SPLITTABLE | MERF_STARTWHITE))
|| ((run->nFlags & MERF_GRAPHICS) && (p != wc->pRowStart)))
{
wc->pLastSplittableRun = p;
wc->ptLastSplittableRun = wc->pt;
}
wc->pt.x += run->nWidth;
return p->next;
}
void ME_WrapTextParagraph(ME_Context *c, ME_DisplayItem *tp) {
ME_DisplayItem *p;
ME_WrapContext wc;
assert(tp->type == diParagraph);
if (tp->member.para.nFlags & MEPF_WRAPPED) {
return;
}
ME_PrepareParagraphForWrapping(c, tp);
wc.context = c;
/* wc.para_style = tp->member.para.style; */
wc.style = NULL;
wc.nFirstMargin = tp->member.para.nFirstMargin;
wc.nLeftMargin = tp->member.para.nLeftMargin;
wc.nRightMargin = tp->member.para.nRightMargin;
wc.nRow = 0;
wc.pt.x = 0;
wc.pt.y = 0;
wc.nTotalWidth = c->rcView.right - c->rcView.left;
wc.nAvailWidth = wc.nTotalWidth - wc.nFirstMargin - wc.nRightMargin;
wc.pRowStart = NULL;
ME_BeginRow(&wc);
for (p = tp->next; p!=tp->member.para.next_para; ) {
assert(p->type != diStartRow);
if (p->type == diRun) {
ME_PrepareStyle(c, p->member.run.style);
p = ME_WrapHandleRun(&wc, p);
continue;
}
p = p->next;
}
ME_WrapEndParagraph(&wc, p);
tp->member.para.nFlags |= MEPF_WRAPPED;
tp->member.para.nHeight = wc.pt.y;
}
void ME_PrepareParagraphForWrapping(ME_Context *c, ME_DisplayItem *tp) {
ME_DisplayItem *p;
/* remove all items that will be reinserted by paragraph wrapper anyway */
for (p = tp->next; p!=tp->member.para.next_para; p = p->next) {
switch(p->type) {
case diStartRow:
p = p->prev;
ME_Remove(p->next);
break;
default:
break;
}
}
/* join runs that can be joined, set up flags */
for (p = tp->next; p!=tp->member.para.next_para; p = p->next) {
int changed = 0;
switch(p->type) {
case diStartRow: assert(0); break; /* should have deleted it */
case diRun:
while (p->next->type == diRun) { /* FIXME */
if (ME_CanJoinRuns(&p->member.run, &p->next->member.run)) {
ME_JoinRuns(c->editor, p);
changed = 1;
}
else
break;
}
p->member.run.nFlags &= ~MERF_CALCBYWRAP;
break;
default:
break;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment