httpapi/tests: Added some basic tests for session and group creation.
[wine.git] / dlls / riched20 / para.c
blobf2a70e5746b601f545ed19540f605b07308c64fe
1 /*
2 * RichEdit - functions working on paragraphs of text (diParagraph).
3 *
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
20 */
22 #include "editor.h"
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
26 static ME_DisplayItem *make_para(ME_TextEditor *editor)
28 ME_DisplayItem *item = ME_MakeDI(diParagraph);
30 ME_SetDefaultParaFormat(editor, &item->member.para.fmt);
31 item->member.para.nFlags = MEPF_REWRAP;
32 return item;
35 void ME_MakeFirstParagraph(ME_TextEditor *editor)
37 ME_Context c;
38 CHARFORMAT2W cf;
39 LOGFONTW lf;
40 HFONT hf;
41 ME_TextBuffer *text = editor->pBuffer;
42 ME_DisplayItem *para = make_para(editor);
43 ME_DisplayItem *run;
44 ME_Style *style;
45 int eol_len;
46 WCHAR cr_lf[] = {'\r','\n',0};
48 ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));
50 hf = GetStockObject(SYSTEM_FONT);
51 assert(hf);
52 GetObjectW(hf, sizeof(LOGFONTW), &lf);
53 ZeroMemory(&cf, sizeof(cf));
54 cf.cbSize = sizeof(cf);
55 cf.dwMask = CFM_ANIMATION|CFM_BACKCOLOR|CFM_CHARSET|CFM_COLOR|CFM_FACE|CFM_KERNING|CFM_LCID|CFM_OFFSET;
56 cf.dwMask |= CFM_REVAUTHOR|CFM_SIZE|CFM_SPACING|CFM_STYLE|CFM_UNDERLINETYPE|CFM_WEIGHT;
57 cf.dwMask |= CFM_ALLCAPS|CFM_BOLD|CFM_DISABLED|CFM_EMBOSS|CFM_HIDDEN;
58 cf.dwMask |= CFM_IMPRINT|CFM_ITALIC|CFM_LINK|CFM_OUTLINE|CFM_PROTECTED;
59 cf.dwMask |= CFM_REVISED|CFM_SHADOW|CFM_SMALLCAPS|CFM_STRIKEOUT;
60 cf.dwMask |= CFM_SUBSCRIPT|CFM_UNDERLINE;
62 cf.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
63 lstrcpyW(cf.szFaceName, lf.lfFaceName);
64 /* Convert system font height from logical units to twips for cf.yHeight */
65 cf.yHeight = (lf.lfHeight * 72 * 1440) / (c.dpi.cy * c.dpi.cy);
66 if (lf.lfWeight > FW_NORMAL) cf.dwEffects |= CFE_BOLD;
67 cf.wWeight = lf.lfWeight;
68 if (lf.lfItalic) cf.dwEffects |= CFE_ITALIC;
69 if (lf.lfUnderline) cf.dwEffects |= CFE_UNDERLINE;
70 cf.bUnderlineType = CFU_UNDERLINE;
71 if (lf.lfStrikeOut) cf.dwEffects |= CFE_STRIKEOUT;
72 cf.bPitchAndFamily = lf.lfPitchAndFamily;
73 cf.bCharSet = lf.lfCharSet;
74 cf.lcid = GetSystemDefaultLCID();
76 style = ME_MakeStyle(&cf);
77 text->pDefaultStyle = style;
79 eol_len = editor->bEmulateVersion10 ? 2 : 1;
80 para->member.para.text = ME_MakeStringN( cr_lf, eol_len );
82 run = ME_MakeRun(style, MERF_ENDPARA);
83 run->member.run.nCharOfs = 0;
84 run->member.run.len = eol_len;
85 run->member.run.para = &para->member.para;
87 para->member.para.eop_run = &run->member.run;
89 ME_InsertBefore(text->pLast, para);
90 ME_InsertBefore(text->pLast, run);
91 para->member.para.prev_para = text->pFirst;
92 para->member.para.next_para = text->pLast;
93 text->pFirst->member.para.next_para = para;
94 text->pLast->member.para.prev_para = para;
96 text->pLast->member.para.nCharOfs = editor->bEmulateVersion10 ? 2 : 1;
98 ME_DestroyContext(&c);
101 static void ME_MarkForWrapping(ME_TextEditor *editor, ME_DisplayItem *first, const ME_DisplayItem *last)
103 while(first != last)
105 first->member.para.nFlags |= MEPF_REWRAP;
106 first = first->member.para.next_para;
110 void ME_MarkAllForWrapping(ME_TextEditor *editor)
112 ME_MarkForWrapping(editor, editor->pBuffer->pFirst->member.para.next_para, editor->pBuffer->pLast);
115 static void ME_UpdateTableFlags(ME_DisplayItem *para)
117 para->member.para.fmt.dwMask |= PFM_TABLE|PFM_TABLEROWDELIMITER;
118 if (para->member.para.pCell) {
119 para->member.para.nFlags |= MEPF_CELL;
120 } else {
121 para->member.para.nFlags &= ~MEPF_CELL;
123 if (para->member.para.nFlags & MEPF_ROWEND) {
124 para->member.para.fmt.wEffects |= PFE_TABLEROWDELIMITER;
125 } else {
126 para->member.para.fmt.wEffects &= ~PFE_TABLEROWDELIMITER;
128 if (para->member.para.nFlags & (MEPF_ROWSTART|MEPF_CELL|MEPF_ROWEND))
129 para->member.para.fmt.wEffects |= PFE_TABLE;
130 else
131 para->member.para.fmt.wEffects &= ~PFE_TABLE;
134 static inline BOOL para_num_same_list( const PARAFORMAT2 *item, const PARAFORMAT2 *base )
136 return item->wNumbering == base->wNumbering &&
137 item->wNumberingStart == base->wNumberingStart &&
138 item->wNumberingStyle == base->wNumberingStyle &&
139 !(item->wNumberingStyle & PFNS_NEWNUMBER);
142 static int para_num_get_num( ME_Paragraph *para )
144 ME_DisplayItem *prev;
145 int num = para->fmt.wNumberingStart;
147 for (prev = para->prev_para; prev->type == diParagraph;
148 para = &prev->member.para, prev = prev->member.para.prev_para, num++)
150 if (!para_num_same_list( &prev->member.para.fmt, &para->fmt )) break;
152 return num;
155 static ME_String *para_num_get_str( ME_Paragraph *para, WORD num )
157 /* max 4 Roman letters (representing '8') / decade + '(' + ')' */
158 ME_String *str = ME_MakeStringEmpty( 20 + 2 );
159 WCHAR *p;
160 static const WCHAR fmtW[] = {'%', 'd', 0};
161 static const WORD letter_base[] = { 1, 26, 26 * 26, 26 * 26 * 26 };
162 /* roman_base should start on a '5' not a '1', otherwise the 'total' code will need adjusting.
163 'N' and 'O' are what MS uses for 5000 and 10000, their version doesn't work well above 30000,
164 but we'll use 'P' as the obvious extension, this gets us up to 2^16, which is all we care about. */
165 static const struct
167 int base;
168 char letter;
170 roman_base[] =
172 {50000, 'P'}, {10000, 'O'}, {5000, 'N'}, {1000, 'M'},
173 {500, 'D'}, {100, 'C'}, {50, 'L'}, {10, 'X'}, {5, 'V'}, {1, 'I'}
175 int i, len;
176 WORD letter, total, char_offset = 0;
178 if (!str) return NULL;
180 p = str->szData;
182 if ((para->fmt.wNumberingStyle & 0xf00) == PFNS_PARENS)
183 *p++ = '(';
185 switch (para->fmt.wNumbering)
187 case PFN_ARABIC:
188 default:
189 p += sprintfW( p, fmtW, num );
190 break;
192 case PFN_LCLETTER:
193 char_offset = 'a' - 'A';
194 /* fall through */
195 case PFN_UCLETTER:
196 if (!num) num = 1;
198 /* This is not base-26 (or 27) as zeros don't count unless they are leading zeros.
199 It's simplest to start with the least significant letter, so first calculate how many letters are needed. */
200 for (i = 0, total = 0; i < sizeof(letter_base) / sizeof(letter_base[0]); i++)
202 total += letter_base[i];
203 if (num < total) break;
205 len = i;
206 for (i = 0; i < len; i++)
208 num -= letter_base[i];
209 letter = (num / letter_base[i]) % 26;
210 p[len - i - 1] = letter + 'A' + char_offset;
212 p += len;
213 *p = 0;
214 break;
216 case PFN_LCROMAN:
217 char_offset = 'a' - 'A';
218 /* fall through */
219 case PFN_UCROMAN:
220 if (!num) num = 1;
222 for (i = 0; i < sizeof(roman_base) / sizeof(roman_base[0]); i++)
224 if (i > 0)
226 if (i % 2 == 0) /* eg 5000, check for 9000 */
227 total = roman_base[i].base + 4 * roman_base[i + 1].base;
228 else /* eg 1000, check for 4000 */
229 total = 4 * roman_base[i].base;
231 if (num / total)
233 *p++ = roman_base[(i & ~1) + 1].letter + char_offset;
234 *p++ = roman_base[i - 1].letter + char_offset;
235 num -= total;
236 continue;
240 len = num / roman_base[i].base;
241 while (len--)
243 *p++ = roman_base[i].letter + char_offset;
244 num -= roman_base[i].base;
247 *p = 0;
248 break;
251 switch (para->fmt.wNumberingStyle & 0xf00)
253 case PFNS_PARENS:
254 case PFNS_PAREN:
255 *p++ = ')';
256 *p = 0;
257 break;
259 case PFNS_PERIOD:
260 *p++ = '.';
261 *p = 0;
262 break;
265 str->nLen = p - str->szData;
266 return str;
269 void para_num_init( ME_Context *c, ME_Paragraph *para )
271 ME_Style *style;
272 CHARFORMAT2W cf;
273 static const WCHAR bullet_font[] = {'S','y','m','b','o','l',0};
274 static const WCHAR bullet_str[] = {0xb7, 0};
275 static const WCHAR spaceW[] = {' ', 0};
276 HFONT old_font;
277 SIZE sz;
279 if (para->para_num.style && para->para_num.text) return;
281 if (!para->para_num.style)
283 style = para->eop_run->style;
285 if (para->fmt.wNumbering == PFN_BULLET)
287 cf.cbSize = sizeof(cf);
288 cf.dwMask = CFM_FACE | CFM_CHARSET;
289 memcpy( cf.szFaceName, bullet_font, sizeof(bullet_font) );
290 cf.bCharSet = SYMBOL_CHARSET;
291 style = ME_ApplyStyle( c->editor, style, &cf );
293 else
295 ME_AddRefStyle( style );
298 para->para_num.style = style;
301 if (!para->para_num.text)
303 if (para->fmt.wNumbering != PFN_BULLET)
304 para->para_num.text = para_num_get_str( para, para_num_get_num( para ) );
305 else
306 para->para_num.text = ME_MakeStringConst( bullet_str, 1 );
309 old_font = ME_SelectStyleFont( c, para->para_num.style );
310 GetTextExtentPointW( c->hDC, para->para_num.text->szData, para->para_num.text->nLen, &sz );
311 para->para_num.width = sz.cx;
312 GetTextExtentPointW( c->hDC, spaceW, 1, &sz );
313 para->para_num.width += sz.cx;
314 ME_UnselectStyleFont( c, para->para_num.style, old_font );
317 void para_num_clear( struct para_num *pn )
319 if (pn->style)
321 ME_ReleaseStyle( pn->style );
322 pn->style = NULL;
324 ME_DestroyString( pn->text );
325 pn->text = NULL;
328 static void para_num_clear_list( ME_Paragraph *para, const PARAFORMAT2 *orig_fmt )
332 para->nFlags |= MEPF_REWRAP;
333 para_num_clear( &para->para_num );
334 if (para->next_para->type != diParagraph) break;
335 para = &para->next_para->member.para;
336 } while (para_num_same_list( &para->fmt, orig_fmt ));
339 static BOOL ME_SetParaFormat(ME_TextEditor *editor, ME_Paragraph *para, const PARAFORMAT2 *pFmt)
341 PARAFORMAT2 copy;
342 DWORD dwMask;
344 assert(para->fmt.cbSize == sizeof(PARAFORMAT2));
345 dwMask = pFmt->dwMask;
346 if (pFmt->cbSize < sizeof(PARAFORMAT))
347 return FALSE;
348 else if (pFmt->cbSize < sizeof(PARAFORMAT2))
349 dwMask &= PFM_ALL;
350 else
351 dwMask &= PFM_ALL2;
353 add_undo_set_para_fmt( editor, para );
355 copy = para->fmt;
357 #define COPY_FIELD(m, f) \
358 if (dwMask & (m)) { \
359 para->fmt.dwMask |= m; \
360 para->fmt.f = pFmt->f; \
363 COPY_FIELD(PFM_NUMBERING, wNumbering);
364 COPY_FIELD(PFM_STARTINDENT, dxStartIndent);
365 if (dwMask & PFM_OFFSETINDENT)
366 para->fmt.dxStartIndent += pFmt->dxStartIndent;
367 COPY_FIELD(PFM_RIGHTINDENT, dxRightIndent);
368 COPY_FIELD(PFM_OFFSET, dxOffset);
369 COPY_FIELD(PFM_ALIGNMENT, wAlignment);
370 if (dwMask & PFM_TABSTOPS)
372 para->fmt.cTabCount = pFmt->cTabCount;
373 memcpy(para->fmt.rgxTabs, pFmt->rgxTabs, pFmt->cTabCount*sizeof(LONG));
376 #define EFFECTS_MASK (PFM_RTLPARA|PFM_KEEP|PFM_KEEPNEXT|PFM_PAGEBREAKBEFORE| \
377 PFM_NOLINENUMBER|PFM_NOWIDOWCONTROL|PFM_DONOTHYPHEN|PFM_SIDEBYSIDE| \
378 PFM_TABLE)
379 /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
380 if (dwMask & EFFECTS_MASK)
382 para->fmt.dwMask |= dwMask & EFFECTS_MASK;
383 para->fmt.wEffects &= ~HIWORD(dwMask);
384 para->fmt.wEffects |= pFmt->wEffects & HIWORD(dwMask);
386 #undef EFFECTS_MASK
388 COPY_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
389 COPY_FIELD(PFM_SPACEAFTER, dySpaceAfter);
390 COPY_FIELD(PFM_LINESPACING, dyLineSpacing);
391 COPY_FIELD(PFM_STYLE, sStyle);
392 COPY_FIELD(PFM_LINESPACING, bLineSpacingRule);
393 COPY_FIELD(PFM_SHADING, wShadingWeight);
394 COPY_FIELD(PFM_SHADING, wShadingStyle);
395 COPY_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
396 COPY_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
397 COPY_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
398 COPY_FIELD(PFM_BORDER, wBorderSpace);
399 COPY_FIELD(PFM_BORDER, wBorderWidth);
400 COPY_FIELD(PFM_BORDER, wBorders);
402 para->fmt.dwMask |= dwMask;
403 #undef COPY_FIELD
405 if (memcmp(&copy, &para->fmt, sizeof(PARAFORMAT2)))
407 para->nFlags |= MEPF_REWRAP;
408 if (((dwMask & PFM_NUMBERING) && (copy.wNumbering != para->fmt.wNumbering)) ||
409 ((dwMask & PFM_NUMBERINGSTART) && (copy.wNumberingStart != para->fmt.wNumberingStart)) ||
410 ((dwMask & PFM_NUMBERINGSTYLE) && (copy.wNumberingStyle != para->fmt.wNumberingStyle)))
412 para_num_clear_list( para, &copy );
416 return TRUE;
419 /* split paragraph at the beginning of the run */
420 ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *run,
421 ME_Style *style, const WCHAR *eol_str, int eol_len,
422 int paraFlags)
424 ME_DisplayItem *next_para = NULL;
425 ME_DisplayItem *run_para = NULL;
426 ME_DisplayItem *new_para = make_para(editor);
427 ME_DisplayItem *end_run;
428 int ofs, i;
429 ME_DisplayItem *pp;
430 int run_flags = MERF_ENDPARA;
432 if (!editor->bEmulateVersion10) { /* v4.1 */
433 /* At most 1 of MEPF_CELL, MEPF_ROWSTART, or MEPF_ROWEND should be set. */
434 assert(!(paraFlags & ~(MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND)));
435 assert(!(paraFlags & (paraFlags-1)));
436 if (paraFlags == MEPF_CELL)
437 run_flags |= MERF_ENDCELL;
438 else if (paraFlags == MEPF_ROWSTART)
439 run_flags |= MERF_TABLESTART|MERF_HIDDEN;
440 } else { /* v1.0 - v3.0 */
441 assert(!(paraFlags & (MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND)));
443 assert(run->type == diRun);
444 run_para = ME_GetParagraph(run);
445 assert(run_para->member.para.fmt.cbSize == sizeof(PARAFORMAT2));
447 /* Clear any cached para numbering following this paragraph */
448 if (run_para->member.para.fmt.wNumbering)
449 para_num_clear_list( &run_para->member.para, &run_para->member.para.fmt );
451 new_para->member.para.text = ME_VSplitString( run_para->member.para.text, run->member.run.nCharOfs );
453 end_run = ME_MakeRun(style, run_flags);
454 ofs = end_run->member.run.nCharOfs = run->member.run.nCharOfs;
455 end_run->member.run.len = eol_len;
456 end_run->member.run.para = run->member.run.para;
457 ME_AppendString( run_para->member.para.text, eol_str, eol_len );
458 next_para = run_para->member.para.next_para;
459 assert(next_para == ME_FindItemFwd(run_para, diParagraphOrEnd));
461 add_undo_join_paras( editor, run_para->member.para.nCharOfs + ofs );
463 /* Update selection cursors to point to the correct paragraph. */
464 for (i = 0; i < editor->nCursors; i++) {
465 if (editor->pCursors[i].pPara == run_para &&
466 run->member.run.nCharOfs <= editor->pCursors[i].pRun->member.run.nCharOfs)
468 editor->pCursors[i].pPara = new_para;
472 /* the new paragraph will have a different starting offset, so let's update its runs */
473 pp = run;
474 while(pp->type == diRun) {
475 pp->member.run.nCharOfs -= ofs;
476 pp->member.run.para = &new_para->member.para;
477 pp = ME_FindItemFwd(pp, diRunOrParagraphOrEnd);
479 new_para->member.para.nCharOfs = run_para->member.para.nCharOfs + ofs;
480 new_para->member.para.nCharOfs += eol_len;
481 new_para->member.para.nFlags = MEPF_REWRAP;
483 /* FIXME initialize format style and call ME_SetParaFormat blah blah */
484 new_para->member.para.fmt = run_para->member.para.fmt;
485 new_para->member.para.border = run_para->member.para.border;
487 /* insert paragraph into paragraph double linked list */
488 new_para->member.para.prev_para = run_para;
489 new_para->member.para.next_para = next_para;
490 run_para->member.para.next_para = new_para;
491 next_para->member.para.prev_para = new_para;
493 /* insert end run of the old paragraph, and new paragraph, into DI double linked list */
494 ME_InsertBefore(run, new_para);
495 ME_InsertBefore(new_para, end_run);
497 /* Fix up the paras' eop_run ptrs */
498 new_para->member.para.eop_run = run_para->member.para.eop_run;
499 run_para->member.para.eop_run = &end_run->member.run;
501 if (!editor->bEmulateVersion10) { /* v4.1 */
502 if (paraFlags & (MEPF_ROWSTART|MEPF_CELL))
504 ME_DisplayItem *cell = ME_MakeDI(diCell);
505 ME_InsertBefore(new_para, cell);
506 new_para->member.para.pCell = cell;
507 cell->member.cell.next_cell = NULL;
508 if (paraFlags & MEPF_ROWSTART)
510 run_para->member.para.nFlags |= MEPF_ROWSTART;
511 cell->member.cell.prev_cell = NULL;
512 cell->member.cell.parent_cell = run_para->member.para.pCell;
513 if (run_para->member.para.pCell)
514 cell->member.cell.nNestingLevel = run_para->member.para.pCell->member.cell.nNestingLevel + 1;
515 else
516 cell->member.cell.nNestingLevel = 1;
517 } else {
518 cell->member.cell.prev_cell = run_para->member.para.pCell;
519 assert(cell->member.cell.prev_cell);
520 cell->member.cell.prev_cell->member.cell.next_cell = cell;
521 assert(run_para->member.para.nFlags & MEPF_CELL);
522 assert(!(run_para->member.para.nFlags & MEPF_ROWSTART));
523 cell->member.cell.nNestingLevel = cell->member.cell.prev_cell->member.cell.nNestingLevel;
524 cell->member.cell.parent_cell = cell->member.cell.prev_cell->member.cell.parent_cell;
526 } else if (paraFlags & MEPF_ROWEND) {
527 run_para->member.para.nFlags |= MEPF_ROWEND;
528 run_para->member.para.pCell = run_para->member.para.pCell->member.cell.parent_cell;
529 new_para->member.para.pCell = run_para->member.para.pCell;
530 assert(run_para->member.para.prev_para->member.para.nFlags & MEPF_CELL);
531 assert(!(run_para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART));
532 if (new_para->member.para.pCell != new_para->member.para.next_para->member.para.pCell
533 && new_para->member.para.next_para->member.para.pCell
534 && !new_para->member.para.next_para->member.para.pCell->member.cell.prev_cell)
536 /* Row starts just after the row that was ended. */
537 new_para->member.para.nFlags |= MEPF_ROWSTART;
539 } else {
540 new_para->member.para.pCell = run_para->member.para.pCell;
542 ME_UpdateTableFlags(run_para);
543 ME_UpdateTableFlags(new_para);
546 /* force rewrap of the */
547 run_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP;
548 new_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP;
550 /* we've added the end run, so we need to modify nCharOfs in the next paragraphs */
551 ME_PropagateCharOffset(next_para, eol_len);
552 editor->nParagraphs++;
554 return new_para;
557 /* join tp with tp->member.para.next_para, keeping tp's style; this
558 * is consistent with the original */
559 ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp,
560 BOOL keepFirstParaFormat)
562 ME_DisplayItem *pNext, *pFirstRunInNext, *pRun, *pTmp, *pCell = NULL;
563 int i, shift;
564 int end_len;
565 CHARFORMAT2W fmt;
566 ME_Cursor startCur, endCur;
567 ME_String *eol_str;
569 assert(tp->type == diParagraph);
570 assert(tp->member.para.next_para);
571 assert(tp->member.para.next_para->type == diParagraph);
573 /* Clear any cached para numbering following this paragraph */
574 if (tp->member.para.fmt.wNumbering)
575 para_num_clear_list( &tp->member.para, &tp->member.para.fmt );
577 pNext = tp->member.para.next_para;
579 /* Need to locate end-of-paragraph run here, in order to know end_len */
580 pRun = ME_FindItemBack(pNext, diRunOrParagraph);
582 assert(pRun);
583 assert(pRun->type == diRun);
584 assert(pRun->member.run.nFlags & MERF_ENDPARA);
586 end_len = pRun->member.run.len;
587 eol_str = ME_VSplitString( tp->member.para.text, pRun->member.run.nCharOfs );
588 ME_AppendString( tp->member.para.text, pNext->member.para.text->szData, pNext->member.para.text->nLen );
590 /* null char format operation to store the original char format for the ENDPARA run */
591 ME_InitCharFormat2W(&fmt);
592 endCur.pPara = pNext;
593 endCur.pRun = ME_FindItemFwd(pNext, diRun);
594 endCur.nOffset = 0;
595 startCur = endCur;
596 ME_PrevRun(&startCur.pPara, &startCur.pRun, TRUE);
597 ME_SetCharFormat(editor, &startCur, &endCur, &fmt);
599 if (!editor->bEmulateVersion10) { /* v4.1 */
600 /* Table cell/row properties are always moved over from the removed para. */
601 tp->member.para.nFlags = pNext->member.para.nFlags;
602 tp->member.para.pCell = pNext->member.para.pCell;
604 /* Remove cell boundary if it is between the end paragraph run and the next
605 * paragraph display item. */
606 for (pTmp = pRun->next; pTmp != pNext; pTmp = pTmp->next)
608 if (pTmp->type == diCell)
610 pCell = pTmp;
611 break;
616 add_undo_split_para( editor, &pNext->member.para, eol_str, pCell ? &pCell->member.cell : NULL );
618 if (pCell)
620 ME_Remove( pCell );
621 if (pCell->member.cell.prev_cell)
622 pCell->member.cell.prev_cell->member.cell.next_cell = pCell->member.cell.next_cell;
623 if (pCell->member.cell.next_cell)
624 pCell->member.cell.next_cell->member.cell.prev_cell = pCell->member.cell.prev_cell;
625 ME_DestroyDisplayItem( pCell );
628 if (!keepFirstParaFormat)
630 add_undo_set_para_fmt( editor, &tp->member.para );
631 tp->member.para.fmt = pNext->member.para.fmt;
632 tp->member.para.border = pNext->member.para.border;
635 shift = pNext->member.para.nCharOfs - tp->member.para.nCharOfs - end_len;
637 pFirstRunInNext = ME_FindItemFwd(pNext, diRunOrParagraph);
639 assert(pFirstRunInNext->type == diRun);
641 /* Update selection cursors so they don't point to the removed end
642 * paragraph run, and point to the correct paragraph. */
643 for (i=0; i < editor->nCursors; i++) {
644 if (editor->pCursors[i].pRun == pRun) {
645 editor->pCursors[i].pRun = pFirstRunInNext;
646 editor->pCursors[i].nOffset = 0;
647 } else if (editor->pCursors[i].pPara == pNext) {
648 editor->pCursors[i].pPara = tp;
652 pTmp = pNext;
653 do {
654 pTmp = ME_FindItemFwd(pTmp, diRunOrParagraphOrEnd);
655 if (pTmp->type != diRun)
656 break;
657 TRACE("shifting %s by %d (previous %d)\n", debugstr_run( &pTmp->member.run ), shift, pTmp->member.run.nCharOfs);
658 pTmp->member.run.nCharOfs += shift;
659 pTmp->member.run.para = &tp->member.para;
660 } while(1);
662 /* Fix up the para's eop_run ptr */
663 tp->member.para.eop_run = pNext->member.para.eop_run;
665 ME_Remove(pRun);
666 ME_DestroyDisplayItem(pRun);
668 if (editor->pLastSelStartPara == pNext)
669 editor->pLastSelStartPara = tp;
670 if (editor->pLastSelEndPara == pNext)
671 editor->pLastSelEndPara = tp;
673 tp->member.para.next_para = pNext->member.para.next_para;
674 pNext->member.para.next_para->member.para.prev_para = tp;
675 ME_Remove(pNext);
676 ME_DestroyDisplayItem(pNext);
678 ME_PropagateCharOffset(tp->member.para.next_para, -end_len);
680 ME_CheckCharOffsets(editor);
682 editor->nParagraphs--;
683 tp->member.para.nFlags |= MEPF_REWRAP;
684 return tp;
687 ME_DisplayItem *ME_GetParagraph(ME_DisplayItem *item) {
688 return ME_FindItemBackOrHere(item, diParagraph);
691 void ME_DumpParaStyleToBuf(const PARAFORMAT2 *pFmt, char buf[2048])
693 char *p;
694 p = buf;
696 #define DUMP(mask, name, fmt, field) \
697 if (pFmt->dwMask & (mask)) p += sprintf(p, "%-22s" fmt "\n", name, pFmt->field); \
698 else p += sprintf(p, "%-22sN/A\n", name);
700 /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
701 #define DUMP_EFFECT(mask, name) \
702 p += sprintf(p, "%-22s%s\n", name, (pFmt->dwMask & (mask)) ? ((pFmt->wEffects & ((mask) >> 16)) ? "yes" : "no") : "N/A");
704 DUMP(PFM_NUMBERING, "Numbering:", "%u", wNumbering);
705 DUMP_EFFECT(PFM_DONOTHYPHEN, "Disable auto-hyphen:");
706 DUMP_EFFECT(PFM_KEEP, "No page break in para:");
707 DUMP_EFFECT(PFM_KEEPNEXT, "No page break in para & next:");
708 DUMP_EFFECT(PFM_NOLINENUMBER, "No line number:");
709 DUMP_EFFECT(PFM_NOWIDOWCONTROL, "No widow & orphan:");
710 DUMP_EFFECT(PFM_PAGEBREAKBEFORE, "Page break before:");
711 DUMP_EFFECT(PFM_RTLPARA, "RTL para:");
712 DUMP_EFFECT(PFM_SIDEBYSIDE, "Side by side:");
713 DUMP_EFFECT(PFM_TABLE, "Table:");
714 DUMP(PFM_OFFSETINDENT, "Offset indent:", "%d", dxStartIndent);
715 DUMP(PFM_STARTINDENT, "Start indent:", "%d", dxStartIndent);
716 DUMP(PFM_RIGHTINDENT, "Right indent:", "%d", dxRightIndent);
717 DUMP(PFM_OFFSET, "Offset:", "%d", dxOffset);
718 if (pFmt->dwMask & PFM_ALIGNMENT) {
719 switch (pFmt->wAlignment) {
720 case PFA_LEFT : p += sprintf(p, "Alignment: left\n"); break;
721 case PFA_RIGHT : p += sprintf(p, "Alignment: right\n"); break;
722 case PFA_CENTER : p += sprintf(p, "Alignment: center\n"); break;
723 case PFA_JUSTIFY: p += sprintf(p, "Alignment: justify\n"); break;
724 default : p += sprintf(p, "Alignment: incorrect %d\n", pFmt->wAlignment); break;
727 else p += sprintf(p, "Alignment: N/A\n");
728 DUMP(PFM_TABSTOPS, "Tab Stops:", "%d", cTabCount);
729 if (pFmt->dwMask & PFM_TABSTOPS) {
730 int i;
731 p += sprintf(p, "\t");
732 for (i = 0; i < pFmt->cTabCount; i++) p += sprintf(p, "%x ", pFmt->rgxTabs[i]);
733 p += sprintf(p, "\n");
735 DUMP(PFM_SPACEBEFORE, "Space Before:", "%d", dySpaceBefore);
736 DUMP(PFM_SPACEAFTER, "Space After:", "%d", dySpaceAfter);
737 DUMP(PFM_LINESPACING, "Line spacing:", "%d", dyLineSpacing);
738 DUMP(PFM_STYLE, "Text style:", "%d", sStyle);
739 DUMP(PFM_LINESPACING, "Line spacing rule:", "%u", bLineSpacingRule);
740 /* bOutlineLevel should be 0 */
741 DUMP(PFM_SHADING, "Shading Weight:", "%u", wShadingWeight);
742 DUMP(PFM_SHADING, "Shading Style:", "%u", wShadingStyle);
743 DUMP(PFM_NUMBERINGSTART, "Numbering Start:", "%u", wNumberingStart);
744 DUMP(PFM_NUMBERINGSTYLE, "Numbering Style:", "0x%x", wNumberingStyle);
745 DUMP(PFM_NUMBERINGTAB, "Numbering Tab:", "%u", wNumberingStyle);
746 DUMP(PFM_BORDER, "Border Space:", "%u", wBorderSpace);
747 DUMP(PFM_BORDER, "Border Width:", "%u", wBorderWidth);
748 DUMP(PFM_BORDER, "Borders:", "%u", wBorders);
750 #undef DUMP
751 #undef DUMP_EFFECT
754 void
755 ME_GetSelectionParas(ME_TextEditor *editor, ME_DisplayItem **para, ME_DisplayItem **para_end)
757 ME_Cursor *pEndCursor = &editor->pCursors[1];
759 *para = editor->pCursors[0].pPara;
760 *para_end = editor->pCursors[1].pPara;
761 if (*para == *para_end)
762 return;
764 if ((*para_end)->member.para.nCharOfs < (*para)->member.para.nCharOfs) {
765 ME_DisplayItem *tmp = *para;
767 *para = *para_end;
768 *para_end = tmp;
769 pEndCursor = &editor->pCursors[0];
772 /* The paragraph at the end of a non-empty selection isn't included
773 * if the selection ends at the start of the paragraph. */
774 if (!pEndCursor->pRun->member.run.nCharOfs && !pEndCursor->nOffset)
775 *para_end = (*para_end)->member.para.prev_para;
779 BOOL ME_SetSelectionParaFormat(ME_TextEditor *editor, const PARAFORMAT2 *pFmt)
781 ME_DisplayItem *para, *para_end;
783 ME_GetSelectionParas(editor, &para, &para_end);
785 do {
786 ME_SetParaFormat(editor, &para->member.para, pFmt);
787 if (para == para_end)
788 break;
789 para = para->member.para.next_para;
790 } while(1);
792 return TRUE;
795 static void ME_GetParaFormat(ME_TextEditor *editor,
796 const ME_DisplayItem *para,
797 PARAFORMAT2 *pFmt)
799 UINT cbSize = pFmt->cbSize;
800 if (pFmt->cbSize >= sizeof(PARAFORMAT2)) {
801 *pFmt = para->member.para.fmt;
802 } else {
803 CopyMemory(pFmt, &para->member.para.fmt, pFmt->cbSize);
804 pFmt->dwMask &= PFM_ALL;
806 pFmt->cbSize = cbSize;
809 void ME_GetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
811 ME_DisplayItem *para, *para_end;
812 PARAFORMAT2 *curFmt;
814 if (pFmt->cbSize < sizeof(PARAFORMAT)) {
815 pFmt->dwMask = 0;
816 return;
819 ME_GetSelectionParas(editor, &para, &para_end);
821 ME_GetParaFormat(editor, para, pFmt);
823 /* Invalidate values that change across the selected paragraphs. */
824 while (para != para_end)
826 para = para->member.para.next_para;
827 curFmt = &para->member.para.fmt;
829 #define CHECK_FIELD(m, f) \
830 if (pFmt->f != curFmt->f) pFmt->dwMask &= ~(m);
832 CHECK_FIELD(PFM_NUMBERING, wNumbering);
833 CHECK_FIELD(PFM_STARTINDENT, dxStartIndent);
834 CHECK_FIELD(PFM_RIGHTINDENT, dxRightIndent);
835 CHECK_FIELD(PFM_OFFSET, dxOffset);
836 CHECK_FIELD(PFM_ALIGNMENT, wAlignment);
837 if (pFmt->dwMask & PFM_TABSTOPS) {
838 if (pFmt->cTabCount != para->member.para.fmt.cTabCount ||
839 memcmp(pFmt->rgxTabs, curFmt->rgxTabs, curFmt->cTabCount*sizeof(int)))
840 pFmt->dwMask &= ~PFM_TABSTOPS;
843 if (pFmt->dwMask >= sizeof(PARAFORMAT2))
845 pFmt->dwMask &= ~((pFmt->wEffects ^ curFmt->wEffects) << 16);
846 CHECK_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
847 CHECK_FIELD(PFM_SPACEAFTER, dySpaceAfter);
848 CHECK_FIELD(PFM_LINESPACING, dyLineSpacing);
849 CHECK_FIELD(PFM_STYLE, sStyle);
850 CHECK_FIELD(PFM_SPACEAFTER, bLineSpacingRule);
851 CHECK_FIELD(PFM_SHADING, wShadingWeight);
852 CHECK_FIELD(PFM_SHADING, wShadingStyle);
853 CHECK_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
854 CHECK_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
855 CHECK_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
856 CHECK_FIELD(PFM_BORDER, wBorderSpace);
857 CHECK_FIELD(PFM_BORDER, wBorderWidth);
858 CHECK_FIELD(PFM_BORDER, wBorders);
860 #undef CHECK_FIELD
864 void ME_SetDefaultParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
866 const PARAFORMAT2 *host_fmt;
867 HRESULT hr;
869 ZeroMemory(pFmt, sizeof(PARAFORMAT2));
870 pFmt->cbSize = sizeof(PARAFORMAT2);
871 pFmt->dwMask = PFM_ALL2;
872 pFmt->wAlignment = PFA_LEFT;
873 pFmt->sStyle = -1;
874 pFmt->bOutlineLevel = TRUE;
876 hr = ITextHost_TxGetParaFormat( editor->texthost, (const PARAFORMAT **)&host_fmt );
877 if (SUCCEEDED(hr))
879 /* Just use the alignment for now */
880 if (host_fmt->dwMask & PFM_ALIGNMENT)
881 pFmt->wAlignment = host_fmt->wAlignment;
882 ITextHost_OnTxParaFormatChange( editor->texthost, (PARAFORMAT *)pFmt );