2 * RichEdit - functions working on paragraphs of text (diParagraph).
4 * Copyright 2004 by Krzysztof Foltman
5 * Copyright 2006 by Phil Krylov
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit
);
26 static const WCHAR wszParagraphSign
[] = {0xB6, 0};
28 void ME_MakeFirstParagraph(HDC hDC
, ME_TextBuffer
*text
)
34 ME_DisplayItem
*para
= ME_MakeDI(diParagraph
);
38 hf
= (HFONT
)GetStockObject(SYSTEM_FONT
);
40 GetObjectW(hf
, sizeof(LOGFONTW
), &lf
);
41 ZeroMemory(&cf
, sizeof(cf
));
42 cf
.cbSize
= sizeof(cf
);
43 cf
.dwMask
= CFM_BACKCOLOR
|CFM_COLOR
|CFM_FACE
|CFM_SIZE
|CFM_CHARSET
;
44 cf
.dwMask
|= CFM_ALLCAPS
|CFM_BOLD
|CFM_DISABLED
|CFM_EMBOSS
|CFM_HIDDEN
;
45 cf
.dwMask
|= CFM_IMPRINT
|CFM_ITALIC
|CFM_LINK
|CFM_OUTLINE
|CFM_PROTECTED
;
46 cf
.dwMask
|= CFM_REVISED
|CFM_SHADOW
|CFM_SMALLCAPS
|CFM_STRIKEOUT
;
47 cf
.dwMask
|= CFM_SUBSCRIPT
|CFM_UNDERLINE
;
49 cf
.dwEffects
= CFE_AUTOCOLOR
| CFE_AUTOBACKCOLOR
;
50 lstrcpyW(cf
.szFaceName
, lf
.lfFaceName
);
51 cf
.yHeight
=lf
.lfHeight
*1440/GetDeviceCaps(hDC
, LOGPIXELSY
);
52 if (lf
.lfWeight
>=700) /* FIXME correct weight ? */
53 cf
.dwEffects
|= CFE_BOLD
;
54 cf
.wWeight
= lf
.lfWeight
;
55 if (lf
.lfItalic
) cf
.dwEffects
|= CFE_ITALIC
;
56 if (lf
.lfUnderline
) cf
.dwEffects
|= CFE_UNDERLINE
;
57 if (lf
.lfStrikeOut
) cf
.dwEffects
|= CFE_STRIKEOUT
;
59 ZeroMemory(&fmt
, sizeof(fmt
));
60 fmt
.cbSize
= sizeof(fmt
);
61 fmt
.dwMask
= PFM_ALIGNMENT
| PFM_OFFSET
| PFM_STARTINDENT
| PFM_RIGHTINDENT
| PFM_TABSTOPS
;
63 CopyMemory(para
->member
.para
.pFmt
, &fmt
, sizeof(PARAFORMAT2
));
65 style
= ME_MakeStyle(&cf
);
66 text
->pDefaultStyle
= style
;
68 run
= ME_MakeRun(style
, ME_MakeString(wszParagraphSign
), MERF_ENDPARA
);
69 run
->member
.run
.nCharOfs
= 0;
71 ME_InsertBefore(text
->pLast
, para
);
72 ME_InsertBefore(text
->pLast
, run
);
73 para
->member
.para
.prev_para
= text
->pFirst
;
74 para
->member
.para
.next_para
= text
->pLast
;
75 text
->pFirst
->member
.para
.next_para
= para
;
76 text
->pLast
->member
.para
.prev_para
= para
;
78 text
->pLast
->member
.para
.nCharOfs
= 1;
81 void ME_MarkAllForWrapping(ME_TextEditor
*editor
)
83 ME_MarkForWrapping(editor
, editor
->pBuffer
->pFirst
->member
.para
.next_para
, editor
->pBuffer
->pLast
);
86 void ME_MarkForWrapping(ME_TextEditor
*editor
, ME_DisplayItem
*first
, ME_DisplayItem
*last
)
90 first
->member
.para
.nFlags
|= MEPF_REWRAP
;
91 first
= first
->member
.para
.next_para
;
95 void ME_MarkForPainting(ME_TextEditor
*editor
, ME_DisplayItem
*first
, ME_DisplayItem
*last
)
99 first
->member
.para
.nFlags
|= MEPF_REPAINT
;
100 first
= first
->member
.para
.next_para
;
104 /* split paragraph at the beginning of the run */
105 ME_DisplayItem
*ME_SplitParagraph(ME_TextEditor
*editor
, ME_DisplayItem
*run
, ME_Style
*style
)
107 ME_DisplayItem
*next_para
= NULL
;
108 ME_DisplayItem
*run_para
= NULL
;
109 ME_DisplayItem
*new_para
= ME_MakeDI(diParagraph
);
110 ME_DisplayItem
*end_run
= ME_MakeRun(style
,ME_MakeString(wszParagraphSign
), MERF_ENDPARA
);
111 ME_UndoItem
*undo
= NULL
;
114 int end_len
= (editor
->bEmulateVersion10
? 2 : 1);
116 assert(run
->type
== diRun
);
118 run_para
= ME_GetParagraph(run
);
119 assert(run_para
->member
.para
.pFmt
->cbSize
== sizeof(PARAFORMAT2
));
121 ofs
= end_run
->member
.run
.nCharOfs
= run
->member
.run
.nCharOfs
;
122 next_para
= run_para
->member
.para
.next_para
;
123 assert(next_para
== ME_FindItemFwd(run_para
, diParagraphOrEnd
));
125 undo
= ME_AddUndoItem(editor
, diUndoJoinParagraphs
, NULL
);
127 undo
->nStart
= run_para
->member
.para
.nCharOfs
+ ofs
;
129 /* the new paragraph will have a different starting offset, so let's update its runs */
131 while(pp
->type
== diRun
) {
132 pp
->member
.run
.nCharOfs
-= ofs
;
133 pp
= ME_FindItemFwd(pp
, diRunOrParagraphOrEnd
);
135 new_para
->member
.para
.nCharOfs
= ME_GetParagraph(run
)->member
.para
.nCharOfs
+ofs
;
136 new_para
->member
.para
.nCharOfs
+= end_len
;
138 new_para
->member
.para
.nFlags
= MEPF_REWRAP
; /* FIXME copy flags (if applicable) */
139 /* FIXME initialize format style and call ME_SetParaFormat blah blah */
140 CopyMemory(new_para
->member
.para
.pFmt
, run_para
->member
.para
.pFmt
, sizeof(PARAFORMAT2
));
142 /* FIXME remove this as soon as nLeftMargin etc are replaced with proper fields of PARAFORMAT2 */
143 new_para
->member
.para
.nLeftMargin
= run_para
->member
.para
.nLeftMargin
;
144 new_para
->member
.para
.nRightMargin
= run_para
->member
.para
.nRightMargin
;
145 new_para
->member
.para
.nFirstMargin
= run_para
->member
.para
.nFirstMargin
;
147 new_para
->member
.para
.bTable
= run_para
->member
.para
.bTable
;
149 /* Inherit previous cell definitions if any */
150 new_para
->member
.para
.pCells
= NULL
;
151 if (run_para
->member
.para
.pCells
)
153 ME_TableCell
*pCell
, *pNewCell
;
155 for (pCell
= run_para
->member
.para
.pCells
; pCell
; pCell
= pCell
->next
)
157 pNewCell
= ALLOC_OBJ(ME_TableCell
);
158 pNewCell
->nRightBoundary
= pCell
->nRightBoundary
;
159 pNewCell
->next
= NULL
;
160 if (new_para
->member
.para
.pCells
)
161 new_para
->member
.para
.pLastCell
->next
= pNewCell
;
163 new_para
->member
.para
.pCells
= pNewCell
;
164 new_para
->member
.para
.pLastCell
= pNewCell
;
168 /* fix paragraph properties. FIXME only needed when called from RTF reader */
169 if (run_para
->member
.para
.pCells
&& !run_para
->member
.para
.bTable
)
171 /* Paragraph does not have an \intbl keyword, so any table definition
172 * stored is invalid */
173 ME_DestroyTableCellList(run_para
);
176 /* insert paragraph into paragraph double linked list */
177 new_para
->member
.para
.prev_para
= run_para
;
178 new_para
->member
.para
.next_para
= next_para
;
179 run_para
->member
.para
.next_para
= new_para
;
180 next_para
->member
.para
.prev_para
= new_para
;
182 /* insert end run of the old paragraph, and new paragraph, into DI double linked list */
183 ME_InsertBefore(run
, new_para
);
184 ME_InsertBefore(new_para
, end_run
);
186 /* force rewrap of the */
187 run_para
->member
.para
.prev_para
->member
.para
.nFlags
|= MEPF_REWRAP
;
188 new_para
->member
.para
.prev_para
->member
.para
.nFlags
|= MEPF_REWRAP
;
190 /* we've added the end run, so we need to modify nCharOfs in the next paragraphs */
191 ME_PropagateCharOffset(next_para
, end_len
);
192 editor
->nParagraphs
++;
197 /* join tp with tp->member.para.next_para, keeping tp's style; this
198 * is consistent with the original */
199 ME_DisplayItem
*ME_JoinParagraphs(ME_TextEditor
*editor
, ME_DisplayItem
*tp
)
201 ME_DisplayItem
*pNext
, *pFirstRunInNext
, *pRun
, *pTmp
;
203 ME_UndoItem
*undo
= NULL
;
204 int end_len
= (editor
->bEmulateVersion10
? 2 : 1);
206 assert(tp
->type
== diParagraph
);
207 assert(tp
->member
.para
.next_para
);
208 assert(tp
->member
.para
.next_para
->type
== diParagraph
);
210 pNext
= tp
->member
.para
.next_para
;
213 /* null char format operation to store the original char format for the ENDPARA run */
215 ME_InitCharFormat2W(&fmt
);
216 ME_SetCharFormat(editor
, pNext
->member
.para
.nCharOfs
- end_len
, end_len
, &fmt
);
218 undo
= ME_AddUndoItem(editor
, diUndoSplitParagraph
, NULL
);
221 undo
->nStart
= pNext
->member
.para
.nCharOfs
- end_len
;
222 assert(pNext
->member
.para
.pFmt
->cbSize
== sizeof(PARAFORMAT2
));
223 CopyMemory(undo
->di
.member
.para
.pFmt
, pNext
->member
.para
.pFmt
, sizeof(PARAFORMAT2
));
226 shift
= pNext
->member
.para
.nCharOfs
- tp
->member
.para
.nCharOfs
- end_len
;
228 pRun
= ME_FindItemBack(pNext
, diRunOrParagraph
);
229 pFirstRunInNext
= ME_FindItemFwd(pNext
, diRunOrParagraph
);
232 assert(pRun
->type
== diRun
);
233 assert(pRun
->member
.run
.nFlags
& MERF_ENDPARA
);
234 assert(pFirstRunInNext
->type
== diRun
);
236 /* if some cursor points at end of paragraph, make it point to the first
237 run of the next joined paragraph */
238 for (i
=0; i
<editor
->nCursors
; i
++) {
239 if (editor
->pCursors
[i
].pRun
== pRun
) {
240 editor
->pCursors
[i
].pRun
= pFirstRunInNext
;
241 editor
->pCursors
[i
].nOffset
= 0;
247 pTmp
= ME_FindItemFwd(pTmp
, diRunOrParagraphOrEnd
);
248 if (pTmp
->type
!= diRun
)
250 TRACE("shifting \"%s\" by %d (previous %d)\n", debugstr_w(pTmp
->member
.run
.strText
->szData
), shift
, pTmp
->member
.run
.nCharOfs
);
251 pTmp
->member
.run
.nCharOfs
+= shift
;
255 ME_DestroyDisplayItem(pRun
);
257 if (editor
->pLastSelStartPara
== pNext
)
258 editor
->pLastSelStartPara
= tp
;
259 if (editor
->pLastSelEndPara
== pNext
)
260 editor
->pLastSelEndPara
= tp
;
262 tp
->member
.para
.next_para
= pNext
->member
.para
.next_para
;
263 pNext
->member
.para
.next_para
->member
.para
.prev_para
= tp
;
265 ME_DestroyDisplayItem(pNext
);
267 ME_PropagateCharOffset(tp
->member
.para
.next_para
, -end_len
);
269 ME_CheckCharOffsets(editor
);
271 editor
->nParagraphs
--;
272 tp
->member
.para
.nFlags
|= MEPF_REWRAP
;
276 ME_DisplayItem
*ME_GetParagraph(ME_DisplayItem
*item
) {
277 return ME_FindItemBackOrHere(item
, diParagraph
);
280 static void ME_DumpStyleEffect(char **p
, const char *name
, PARAFORMAT2
*fmt
, int mask
)
282 *p
+= sprintf(*p
, "%-22s%s\n", name
, (fmt
->dwMask
& mask
) ? ((fmt
->wEffects
& mask
) ? "yes" : "no") : "N/A");
285 void ME_DumpParaStyleToBuf(PARAFORMAT2
*pFmt
, char buf
[2048])
287 /* FIXME only PARAFORMAT styles implemented */
290 p
+= sprintf(p
, "Alignment: %s\n",
291 !(pFmt
->dwMask
& PFM_ALIGNMENT
) ? "N/A" :
292 ((pFmt
->wAlignment
== PFA_LEFT
) ? "left" :
293 ((pFmt
->wAlignment
== PFA_RIGHT
) ? "right" :
294 ((pFmt
->wAlignment
== PFA_CENTER
) ? "center" :
295 /*((pFmt->wAlignment == PFA_JUSTIFY) ? "justify" : "incorrect")*/
298 if (pFmt
->dwMask
& PFM_OFFSET
)
299 p
+= sprintf(p
, "Offset: %d\n", (int)pFmt
->dxOffset
);
301 p
+= sprintf(p
, "Offset: N/A\n");
303 if (pFmt
->dwMask
& PFM_OFFSETINDENT
)
304 p
+= sprintf(p
, "Offset indent: %d\n", (int)pFmt
->dxStartIndent
);
306 p
+= sprintf(p
, "Offset indent: N/A\n");
308 if (pFmt
->dwMask
& PFM_STARTINDENT
)
309 p
+= sprintf(p
, "Start indent: %d\n", (int)pFmt
->dxStartIndent
);
311 p
+= sprintf(p
, "Start indent: N/A\n");
313 if (pFmt
->dwMask
& PFM_RIGHTINDENT
)
314 p
+= sprintf(p
, "Right indent: %d\n", (int)pFmt
->dxRightIndent
);
316 p
+= sprintf(p
, "Right indent: N/A\n");
318 ME_DumpStyleEffect(&p
, "Page break before:", pFmt
, PFM_PAGEBREAKBEFORE
);
321 void ME_SetParaFormat(ME_TextEditor
*editor
, ME_DisplayItem
*para
, PARAFORMAT2
*pFmt
)
324 assert(sizeof(*para
->member
.para
.pFmt
) == sizeof(PARAFORMAT2
));
325 ME_AddUndoItem(editor
, diUndoSetParagraphFormat
, para
);
327 CopyMemory(©
, para
->member
.para
.pFmt
, sizeof(PARAFORMAT2
));
329 if (pFmt
->dwMask
& PFM_ALIGNMENT
)
330 para
->member
.para
.pFmt
->wAlignment
= pFmt
->wAlignment
;
331 if (pFmt
->dwMask
& PFM_STARTINDENT
)
332 para
->member
.para
.pFmt
->dxStartIndent
= pFmt
->dxStartIndent
;
333 if (pFmt
->dwMask
& PFM_OFFSET
)
334 para
->member
.para
.pFmt
->dxOffset
= pFmt
->dxOffset
;
335 if (pFmt
->dwMask
& PFM_OFFSETINDENT
)
336 para
->member
.para
.pFmt
->dxStartIndent
+= pFmt
->dxStartIndent
;
338 if (pFmt
->dwMask
& PFM_TABSTOPS
)
340 para
->member
.para
.pFmt
->cTabCount
= pFmt
->cTabCount
;
341 memcpy(para
->member
.para
.pFmt
->rgxTabs
, pFmt
->rgxTabs
, pFmt
->cTabCount
*sizeof(int));
344 /* FIXME to be continued (indents, bulleting and such) */
346 if (memcmp(©
, para
->member
.para
.pFmt
, sizeof(PARAFORMAT2
)))
347 para
->member
.para
.nFlags
|= MEPF_REWRAP
;
352 ME_GetSelectionParas(ME_TextEditor
*editor
, ME_DisplayItem
**para
, ME_DisplayItem
**para_end
)
354 ME_Cursor
*pEndCursor
= &editor
->pCursors
[1];
356 *para
= ME_GetParagraph(editor
->pCursors
[0].pRun
);
357 *para_end
= ME_GetParagraph(editor
->pCursors
[1].pRun
);
358 if ((*para_end
)->member
.para
.nCharOfs
< (*para
)->member
.para
.nCharOfs
) {
359 ME_DisplayItem
*tmp
= *para
;
363 pEndCursor
= &editor
->pCursors
[0];
366 /* selection consists of chars from nFrom up to nTo-1 */
367 if ((*para_end
)->member
.para
.nCharOfs
> (*para
)->member
.para
.nCharOfs
) {
368 if (!pEndCursor
->nOffset
) {
369 *para_end
= ME_GetParagraph(ME_FindItemBack(pEndCursor
->pRun
, diRun
));
375 void ME_SetSelectionParaFormat(ME_TextEditor
*editor
, PARAFORMAT2
*pFmt
)
377 ME_DisplayItem
*para
, *para_end
;
379 ME_GetSelectionParas(editor
, ¶
, ¶_end
);
382 ME_SetParaFormat(editor
, para
, pFmt
);
383 if (para
== para_end
)
385 para
= para
->member
.para
.next_para
;
389 void ME_GetParaFormat(ME_TextEditor
*editor
, ME_DisplayItem
*para
, PARAFORMAT2
*pFmt
)
391 if (pFmt
->cbSize
>= sizeof(PARAFORMAT2
))
393 CopyMemory(pFmt
, para
->member
.para
.pFmt
, sizeof(PARAFORMAT2
));
396 CopyMemory(pFmt
, para
->member
.para
.pFmt
, pFmt
->cbSize
);
399 void ME_GetSelectionParaFormat(ME_TextEditor
*editor
, PARAFORMAT2
*pFmt
)
401 ME_DisplayItem
*para
, *para_end
;
404 ME_GetSelectionParas(editor
, ¶
, ¶_end
);
406 ME_GetParaFormat(editor
, para
, pFmt
);
407 if (para
== para_end
) return;
410 ZeroMemory(&tmp
, sizeof(tmp
));
411 tmp
.cbSize
= sizeof(tmp
);
412 ME_GetParaFormat(editor
, para
, &tmp
);
414 assert(tmp
.dwMask
& PFM_ALIGNMENT
);
415 if (pFmt
->wAlignment
!= tmp
.wAlignment
)
416 pFmt
->dwMask
&= ~PFM_ALIGNMENT
;
418 assert(tmp
.dwMask
& PFM_STARTINDENT
);
419 if (pFmt
->dxStartIndent
!= tmp
.dxStartIndent
)
420 pFmt
->dwMask
&= ~PFM_STARTINDENT
;
422 assert(tmp
.dwMask
& PFM_OFFSET
);
423 if (pFmt
->dxOffset
!= tmp
.dxOffset
)
424 pFmt
->dwMask
&= ~PFM_OFFSET
;
426 assert(tmp
.dwMask
& PFM_TABSTOPS
);
427 if (pFmt
->dwMask
& PFM_TABSTOPS
) {
428 if (pFmt
->cTabCount
!= tmp
.cTabCount
)
429 pFmt
->dwMask
&= ~PFM_TABSTOPS
;
431 if (memcmp(pFmt
->rgxTabs
, tmp
.rgxTabs
, tmp
.cTabCount
*sizeof(int)))
432 pFmt
->dwMask
&= ~PFM_TABSTOPS
;
435 if (para
== para_end
)
437 para
= para
->member
.para
.next_para
;