xactengine3_0: New DLL.
[wine.git] / dlls / riched20 / para.c
blobbde0654b6668809d19daa2323e78416d3e5ffba0
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 void mark_para_rewrap(ME_TextEditor *editor, ME_DisplayItem *para)
28 para->member.para.nFlags |= MEPF_REWRAP;
29 add_marked_para(editor, para);
32 ME_DisplayItem *get_di_from_para(ME_Paragraph *para)
34 return (ME_DisplayItem *)((ptrdiff_t)para - offsetof(ME_DisplayItem, member));
37 static ME_DisplayItem *make_para(ME_TextEditor *editor)
39 ME_DisplayItem *item = ME_MakeDI(diParagraph);
41 ME_SetDefaultParaFormat(editor, &item->member.para.fmt);
42 item->member.para.nFlags = MEPF_REWRAP;
43 item->member.para.next_marked = item->member.para.prev_marked = NULL;
45 return item;
48 void destroy_para(ME_TextEditor *editor, ME_DisplayItem *item)
50 assert(item->type == diParagraph);
52 if (item->member.para.nWidth == editor->nTotalWidth)
54 item->member.para.nWidth = 0;
55 editor->nTotalWidth = get_total_width(editor);
57 editor->total_rows -= item->member.para.nRows;
58 ME_DestroyString(item->member.para.text);
59 para_num_clear( &item->member.para.para_num );
60 remove_marked_para(editor, item);
61 ME_DestroyDisplayItem(item);
64 int get_total_width(ME_TextEditor *editor)
66 ME_Paragraph *para;
67 int total_width = 0;
69 if (editor->pBuffer->pFirst && editor->pBuffer->pLast)
71 para = &editor->pBuffer->pFirst->next->member.para;
72 while (para != &editor->pBuffer->pLast->member.para && para->next_para)
74 total_width = max(total_width, para->nWidth);
75 para = &para->next_para->member.para;
79 return total_width;
82 void remove_marked_para(ME_TextEditor *editor, ME_DisplayItem *di)
84 ME_DisplayItem *head = editor->first_marked_para;
86 assert(di->type == diParagraph);
87 if (!di->member.para.next_marked && !di->member.para.prev_marked)
89 if (di == head)
90 editor->first_marked_para = NULL;
92 else if (di->member.para.next_marked && di->member.para.prev_marked)
94 di->member.para.prev_marked->member.para.next_marked = di->member.para.next_marked;
95 di->member.para.next_marked->member.para.prev_marked = di->member.para.prev_marked;
96 di->member.para.prev_marked = di->member.para.next_marked = NULL;
98 else if (di->member.para.next_marked)
100 assert(di == editor->first_marked_para);
101 editor->first_marked_para = di->member.para.next_marked;
102 di->member.para.next_marked->member.para.prev_marked = NULL;
103 di->member.para.next_marked = NULL;
105 else
107 di->member.para.prev_marked->member.para.next_marked = NULL;
108 di->member.para.prev_marked = NULL;
112 void add_marked_para(ME_TextEditor *editor, ME_DisplayItem *di)
114 ME_DisplayItem *iter = editor->first_marked_para;
116 if (!iter)
118 editor->first_marked_para = di;
119 return;
121 while (iter)
123 if (iter == di)
124 return;
125 else if (di->member.para.nCharOfs < iter->member.para.nCharOfs)
127 if (iter == editor->first_marked_para)
128 editor->first_marked_para = di;
129 di->member.para.next_marked = iter;
130 iter->member.para.prev_marked = di;
131 break;
133 else if (di->member.para.nCharOfs >= iter->member.para.nCharOfs)
135 if (!iter->member.para.next_marked || di->member.para.nCharOfs < iter->member.para.next_marked->member.para.nCharOfs)
137 if (iter->member.para.next_marked)
139 di->member.para.next_marked = iter->member.para.next_marked;
140 iter->member.para.next_marked->member.para.prev_marked = di;
142 di->member.para.prev_marked = iter;
143 iter->member.para.next_marked = di;
144 break;
147 iter = iter->member.para.next_marked;
151 void ME_MakeFirstParagraph(ME_TextEditor *editor)
153 static const WCHAR cr_lf[] = {'\r','\n',0};
154 ME_Context c;
155 CHARFORMAT2W cf;
156 const CHARFORMATW *host_cf;
157 LOGFONTW lf;
158 HFONT hf;
159 ME_TextBuffer *text = editor->pBuffer;
160 ME_DisplayItem *para = make_para(editor);
161 ME_DisplayItem *run;
162 ME_Style *style;
163 int eol_len;
165 ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));
167 hf = GetStockObject(SYSTEM_FONT);
168 assert(hf);
169 GetObjectW(hf, sizeof(LOGFONTW), &lf);
170 ZeroMemory(&cf, sizeof(cf));
171 cf.cbSize = sizeof(cf);
172 cf.dwMask = CFM_ANIMATION|CFM_BACKCOLOR|CFM_CHARSET|CFM_COLOR|CFM_FACE|CFM_KERNING|CFM_LCID|CFM_OFFSET;
173 cf.dwMask |= CFM_REVAUTHOR|CFM_SIZE|CFM_SPACING|CFM_STYLE|CFM_UNDERLINETYPE|CFM_WEIGHT;
174 cf.dwMask |= CFM_ALLCAPS|CFM_BOLD|CFM_DISABLED|CFM_EMBOSS|CFM_HIDDEN;
175 cf.dwMask |= CFM_IMPRINT|CFM_ITALIC|CFM_LINK|CFM_OUTLINE|CFM_PROTECTED;
176 cf.dwMask |= CFM_REVISED|CFM_SHADOW|CFM_SMALLCAPS|CFM_STRIKEOUT;
177 cf.dwMask |= CFM_SUBSCRIPT|CFM_UNDERLINE;
179 cf.dwEffects = CFE_AUTOCOLOR | CFE_AUTOBACKCOLOR;
180 lstrcpyW(cf.szFaceName, lf.lfFaceName);
181 /* Convert system font height from logical units to twips for cf.yHeight */
182 cf.yHeight = (lf.lfHeight * 72 * 1440) / (c.dpi.cy * c.dpi.cy);
183 if (lf.lfWeight > FW_NORMAL) cf.dwEffects |= CFE_BOLD;
184 cf.wWeight = lf.lfWeight;
185 if (lf.lfItalic) cf.dwEffects |= CFE_ITALIC;
186 if (lf.lfUnderline) cf.dwEffects |= CFE_UNDERLINE;
187 cf.bUnderlineType = CFU_UNDERLINE;
188 if (lf.lfStrikeOut) cf.dwEffects |= CFE_STRIKEOUT;
189 cf.bPitchAndFamily = lf.lfPitchAndFamily;
190 cf.bCharSet = lf.lfCharSet;
191 cf.lcid = GetSystemDefaultLCID();
193 style = ME_MakeStyle(&cf);
194 text->pDefaultStyle = style;
196 if (ITextHost_TxGetCharFormat(editor->texthost, &host_cf) == S_OK)
198 ZeroMemory(&cf, sizeof(cf));
199 cf.cbSize = sizeof(cf);
200 cfany_to_cf2w(&cf, (CHARFORMAT2W *)host_cf);
201 ME_SetDefaultCharFormat(editor, &cf);
204 eol_len = editor->bEmulateVersion10 ? 2 : 1;
205 para->member.para.text = ME_MakeStringN( cr_lf, eol_len );
207 run = ME_MakeRun(style, MERF_ENDPARA);
208 run->member.run.nCharOfs = 0;
209 run->member.run.len = eol_len;
210 run->member.run.para = &para->member.para;
212 para->member.para.eop_run = &run->member.run;
214 ME_InsertBefore(text->pLast, para);
215 ME_InsertBefore(text->pLast, run);
216 para->member.para.prev_para = text->pFirst;
217 para->member.para.next_para = text->pLast;
218 text->pFirst->member.para.next_para = para;
219 text->pLast->member.para.prev_para = para;
221 text->pLast->member.para.nCharOfs = editor->bEmulateVersion10 ? 2 : 1;
223 add_marked_para(editor, para);
224 ME_DestroyContext(&c);
227 static void ME_MarkForWrapping(ME_TextEditor *editor, ME_DisplayItem *first, const ME_DisplayItem *last)
229 while(first != last)
231 mark_para_rewrap(editor, first);
232 first = first->member.para.next_para;
236 void ME_MarkAllForWrapping(ME_TextEditor *editor)
238 ME_MarkForWrapping(editor, editor->pBuffer->pFirst->member.para.next_para, editor->pBuffer->pLast);
241 static void ME_UpdateTableFlags(ME_DisplayItem *para)
243 para->member.para.fmt.dwMask |= PFM_TABLE|PFM_TABLEROWDELIMITER;
244 if (para->member.para.pCell) {
245 para->member.para.nFlags |= MEPF_CELL;
246 } else {
247 para->member.para.nFlags &= ~MEPF_CELL;
249 if (para->member.para.nFlags & MEPF_ROWEND) {
250 para->member.para.fmt.wEffects |= PFE_TABLEROWDELIMITER;
251 } else {
252 para->member.para.fmt.wEffects &= ~PFE_TABLEROWDELIMITER;
254 if (para->member.para.nFlags & (MEPF_ROWSTART|MEPF_CELL|MEPF_ROWEND))
255 para->member.para.fmt.wEffects |= PFE_TABLE;
256 else
257 para->member.para.fmt.wEffects &= ~PFE_TABLE;
260 static inline BOOL para_num_same_list( const PARAFORMAT2 *item, const PARAFORMAT2 *base )
262 return item->wNumbering == base->wNumbering &&
263 item->wNumberingStart == base->wNumberingStart &&
264 item->wNumberingStyle == base->wNumberingStyle &&
265 !(item->wNumberingStyle & PFNS_NEWNUMBER);
268 static int para_num_get_num( ME_Paragraph *para )
270 ME_DisplayItem *prev;
271 int num = para->fmt.wNumberingStart;
273 for (prev = para->prev_para; prev->type == diParagraph;
274 para = &prev->member.para, prev = prev->member.para.prev_para, num++)
276 if (!para_num_same_list( &prev->member.para.fmt, &para->fmt )) break;
278 return num;
281 static ME_String *para_num_get_str( ME_Paragraph *para, WORD num )
283 /* max 4 Roman letters (representing '8') / decade + '(' + ')' */
284 ME_String *str = ME_MakeStringEmpty( 20 + 2 );
285 WCHAR *p;
286 static const WCHAR fmtW[] = {'%', 'd', 0};
287 static const WORD letter_base[] = { 1, 26, 26 * 26, 26 * 26 * 26 };
288 /* roman_base should start on a '5' not a '1', otherwise the 'total' code will need adjusting.
289 'N' and 'O' are what MS uses for 5000 and 10000, their version doesn't work well above 30000,
290 but we'll use 'P' as the obvious extension, this gets us up to 2^16, which is all we care about. */
291 static const struct
293 int base;
294 char letter;
296 roman_base[] =
298 {50000, 'P'}, {10000, 'O'}, {5000, 'N'}, {1000, 'M'},
299 {500, 'D'}, {100, 'C'}, {50, 'L'}, {10, 'X'}, {5, 'V'}, {1, 'I'}
301 int i, len;
302 WORD letter, total, char_offset = 0;
304 if (!str) return NULL;
306 p = str->szData;
308 if ((para->fmt.wNumberingStyle & 0xf00) == PFNS_PARENS)
309 *p++ = '(';
311 switch (para->fmt.wNumbering)
313 case PFN_ARABIC:
314 default:
315 p += swprintf( p, 20, fmtW, num );
316 break;
318 case PFN_LCLETTER:
319 char_offset = 'a' - 'A';
320 /* fall through */
321 case PFN_UCLETTER:
322 if (!num) num = 1;
324 /* This is not base-26 (or 27) as zeros don't count unless they are leading zeros.
325 It's simplest to start with the least significant letter, so first calculate how many letters are needed. */
326 for (i = 0, total = 0; i < ARRAY_SIZE( letter_base ); i++)
328 total += letter_base[i];
329 if (num < total) break;
331 len = i;
332 for (i = 0; i < len; i++)
334 num -= letter_base[i];
335 letter = (num / letter_base[i]) % 26;
336 p[len - i - 1] = letter + 'A' + char_offset;
338 p += len;
339 *p = 0;
340 break;
342 case PFN_LCROMAN:
343 char_offset = 'a' - 'A';
344 /* fall through */
345 case PFN_UCROMAN:
346 if (!num) num = 1;
348 for (i = 0; i < ARRAY_SIZE( roman_base ); i++)
350 if (i > 0)
352 if (i % 2 == 0) /* eg 5000, check for 9000 */
353 total = roman_base[i].base + 4 * roman_base[i + 1].base;
354 else /* eg 1000, check for 4000 */
355 total = 4 * roman_base[i].base;
357 if (num / total)
359 *p++ = roman_base[(i & ~1) + 1].letter + char_offset;
360 *p++ = roman_base[i - 1].letter + char_offset;
361 num -= total;
362 continue;
366 len = num / roman_base[i].base;
367 while (len--)
369 *p++ = roman_base[i].letter + char_offset;
370 num -= roman_base[i].base;
373 *p = 0;
374 break;
377 switch (para->fmt.wNumberingStyle & 0xf00)
379 case PFNS_PARENS:
380 case PFNS_PAREN:
381 *p++ = ')';
382 *p = 0;
383 break;
385 case PFNS_PERIOD:
386 *p++ = '.';
387 *p = 0;
388 break;
391 str->nLen = p - str->szData;
392 return str;
395 void para_num_init( ME_Context *c, ME_Paragraph *para )
397 ME_Style *style;
398 CHARFORMAT2W cf;
399 static const WCHAR bullet_font[] = {'S','y','m','b','o','l',0};
400 static const WCHAR bullet_str[] = {0xb7, 0};
401 static const WCHAR spaceW[] = {' ', 0};
402 SIZE sz;
404 if (!para->fmt.wNumbering) return;
405 if (para->para_num.style && para->para_num.text) return;
407 if (!para->para_num.style)
409 style = para->eop_run->style;
411 if (para->fmt.wNumbering == PFN_BULLET)
413 cf.cbSize = sizeof(cf);
414 cf.dwMask = CFM_FACE | CFM_CHARSET;
415 memcpy( cf.szFaceName, bullet_font, sizeof(bullet_font) );
416 cf.bCharSet = SYMBOL_CHARSET;
417 style = ME_ApplyStyle( c->editor, style, &cf );
419 else
421 ME_AddRefStyle( style );
424 para->para_num.style = style;
427 if (!para->para_num.text)
429 if (para->fmt.wNumbering != PFN_BULLET)
430 para->para_num.text = para_num_get_str( para, para_num_get_num( para ) );
431 else
432 para->para_num.text = ME_MakeStringConst( bullet_str, 1 );
435 select_style( c, para->para_num.style );
436 GetTextExtentPointW( c->hDC, para->para_num.text->szData, para->para_num.text->nLen, &sz );
437 para->para_num.width = sz.cx;
438 GetTextExtentPointW( c->hDC, spaceW, 1, &sz );
439 para->para_num.width += sz.cx;
442 void para_num_clear( struct para_num *pn )
444 if (pn->style)
446 ME_ReleaseStyle( pn->style );
447 pn->style = NULL;
449 ME_DestroyString( pn->text );
450 pn->text = NULL;
453 static void para_num_clear_list( ME_TextEditor *editor, ME_Paragraph *para, const PARAFORMAT2 *orig_fmt )
457 mark_para_rewrap(editor, get_di_from_para(para));
458 para_num_clear( &para->para_num );
459 if (para->next_para->type != diParagraph) break;
460 para = &para->next_para->member.para;
461 } while (para_num_same_list( &para->fmt, orig_fmt ));
464 static BOOL ME_SetParaFormat(ME_TextEditor *editor, ME_Paragraph *para, const PARAFORMAT2 *pFmt)
466 PARAFORMAT2 copy;
467 DWORD dwMask;
469 assert(para->fmt.cbSize == sizeof(PARAFORMAT2));
470 dwMask = pFmt->dwMask;
471 if (pFmt->cbSize < sizeof(PARAFORMAT))
472 return FALSE;
473 else if (pFmt->cbSize < sizeof(PARAFORMAT2))
474 dwMask &= PFM_ALL;
475 else
476 dwMask &= PFM_ALL2;
478 add_undo_set_para_fmt( editor, para );
480 copy = para->fmt;
482 #define COPY_FIELD(m, f) \
483 if (dwMask & (m)) { \
484 para->fmt.dwMask |= m; \
485 para->fmt.f = pFmt->f; \
488 COPY_FIELD(PFM_NUMBERING, wNumbering);
489 COPY_FIELD(PFM_STARTINDENT, dxStartIndent);
490 if (dwMask & PFM_OFFSETINDENT)
491 para->fmt.dxStartIndent += pFmt->dxStartIndent;
492 COPY_FIELD(PFM_RIGHTINDENT, dxRightIndent);
493 COPY_FIELD(PFM_OFFSET, dxOffset);
494 COPY_FIELD(PFM_ALIGNMENT, wAlignment);
495 if (dwMask & PFM_TABSTOPS)
497 para->fmt.cTabCount = pFmt->cTabCount;
498 memcpy(para->fmt.rgxTabs, pFmt->rgxTabs, pFmt->cTabCount*sizeof(LONG));
501 #define EFFECTS_MASK (PFM_RTLPARA|PFM_KEEP|PFM_KEEPNEXT|PFM_PAGEBREAKBEFORE| \
502 PFM_NOLINENUMBER|PFM_NOWIDOWCONTROL|PFM_DONOTHYPHEN|PFM_SIDEBYSIDE| \
503 PFM_TABLE)
504 /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
505 if (dwMask & EFFECTS_MASK)
507 para->fmt.dwMask |= dwMask & EFFECTS_MASK;
508 para->fmt.wEffects &= ~HIWORD(dwMask);
509 para->fmt.wEffects |= pFmt->wEffects & HIWORD(dwMask);
511 #undef EFFECTS_MASK
513 COPY_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
514 COPY_FIELD(PFM_SPACEAFTER, dySpaceAfter);
515 COPY_FIELD(PFM_LINESPACING, dyLineSpacing);
516 COPY_FIELD(PFM_STYLE, sStyle);
517 COPY_FIELD(PFM_LINESPACING, bLineSpacingRule);
518 COPY_FIELD(PFM_SHADING, wShadingWeight);
519 COPY_FIELD(PFM_SHADING, wShadingStyle);
520 COPY_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
521 COPY_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
522 COPY_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
523 COPY_FIELD(PFM_BORDER, wBorderSpace);
524 COPY_FIELD(PFM_BORDER, wBorderWidth);
525 COPY_FIELD(PFM_BORDER, wBorders);
527 para->fmt.dwMask |= dwMask;
528 #undef COPY_FIELD
530 if (memcmp(&copy, &para->fmt, sizeof(PARAFORMAT2)))
532 mark_para_rewrap(editor, get_di_from_para(para));
533 if (((dwMask & PFM_NUMBERING) && (copy.wNumbering != para->fmt.wNumbering)) ||
534 ((dwMask & PFM_NUMBERINGSTART) && (copy.wNumberingStart != para->fmt.wNumberingStart)) ||
535 ((dwMask & PFM_NUMBERINGSTYLE) && (copy.wNumberingStyle != para->fmt.wNumberingStyle)))
537 para_num_clear_list( editor, para, &copy );
541 return TRUE;
544 /* split paragraph at the beginning of the run */
545 ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *run,
546 ME_Style *style, const WCHAR *eol_str, int eol_len,
547 int paraFlags)
549 ME_DisplayItem *next_para = NULL;
550 ME_DisplayItem *run_para = NULL;
551 ME_DisplayItem *new_para = make_para(editor);
552 ME_DisplayItem *end_run;
553 int ofs, i;
554 ME_DisplayItem *pp;
555 int run_flags = MERF_ENDPARA;
557 if (!editor->bEmulateVersion10) { /* v4.1 */
558 /* At most 1 of MEPF_CELL, MEPF_ROWSTART, or MEPF_ROWEND should be set. */
559 assert(!(paraFlags & ~(MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND)));
560 assert(!(paraFlags & (paraFlags-1)));
561 if (paraFlags == MEPF_CELL)
562 run_flags |= MERF_ENDCELL;
563 else if (paraFlags == MEPF_ROWSTART)
564 run_flags |= MERF_TABLESTART|MERF_HIDDEN;
565 } else { /* v1.0 - v3.0 */
566 assert(!(paraFlags & (MEPF_CELL|MEPF_ROWSTART|MEPF_ROWEND)));
568 assert(run->type == diRun);
569 run_para = ME_GetParagraph(run);
570 assert(run_para->member.para.fmt.cbSize == sizeof(PARAFORMAT2));
572 /* Clear any cached para numbering following this paragraph */
573 if (run_para->member.para.fmt.wNumbering)
574 para_num_clear_list( editor, &run_para->member.para, &run_para->member.para.fmt );
576 new_para->member.para.text = ME_VSplitString( run_para->member.para.text, run->member.run.nCharOfs );
578 end_run = ME_MakeRun(style, run_flags);
579 ofs = end_run->member.run.nCharOfs = run->member.run.nCharOfs;
580 end_run->member.run.len = eol_len;
581 end_run->member.run.para = run->member.run.para;
582 ME_AppendString( run_para->member.para.text, eol_str, eol_len );
583 next_para = run_para->member.para.next_para;
584 assert(next_para == ME_FindItemFwd(run_para, diParagraphOrEnd));
586 add_undo_join_paras( editor, run_para->member.para.nCharOfs + ofs );
588 /* Update selection cursors to point to the correct paragraph. */
589 for (i = 0; i < editor->nCursors; i++) {
590 if (editor->pCursors[i].pPara == run_para &&
591 run->member.run.nCharOfs <= editor->pCursors[i].pRun->member.run.nCharOfs)
593 editor->pCursors[i].pPara = new_para;
597 /* the new paragraph will have a different starting offset, so let's update its runs */
598 pp = run;
599 while(pp->type == diRun) {
600 pp->member.run.nCharOfs -= ofs;
601 pp->member.run.para = &new_para->member.para;
602 pp = ME_FindItemFwd(pp, diRunOrParagraphOrEnd);
604 new_para->member.para.nCharOfs = run_para->member.para.nCharOfs + ofs;
605 new_para->member.para.nCharOfs += eol_len;
606 new_para->member.para.nFlags = 0;
607 mark_para_rewrap(editor, new_para);
609 /* FIXME initialize format style and call ME_SetParaFormat blah blah */
610 new_para->member.para.fmt = run_para->member.para.fmt;
611 new_para->member.para.border = run_para->member.para.border;
613 /* insert paragraph into paragraph double linked list */
614 new_para->member.para.prev_para = run_para;
615 new_para->member.para.next_para = next_para;
616 run_para->member.para.next_para = new_para;
617 next_para->member.para.prev_para = new_para;
619 /* insert end run of the old paragraph, and new paragraph, into DI double linked list */
620 ME_InsertBefore(run, new_para);
621 ME_InsertBefore(new_para, end_run);
623 /* Fix up the paras' eop_run ptrs */
624 new_para->member.para.eop_run = run_para->member.para.eop_run;
625 run_para->member.para.eop_run = &end_run->member.run;
627 if (!editor->bEmulateVersion10) { /* v4.1 */
628 if (paraFlags & (MEPF_ROWSTART|MEPF_CELL))
630 ME_DisplayItem *cell = ME_MakeDI(diCell);
631 ME_InsertBefore(new_para, cell);
632 new_para->member.para.pCell = cell;
633 cell->member.cell.next_cell = NULL;
634 if (paraFlags & MEPF_ROWSTART)
636 run_para->member.para.nFlags |= MEPF_ROWSTART;
637 cell->member.cell.prev_cell = NULL;
638 cell->member.cell.parent_cell = run_para->member.para.pCell;
639 if (run_para->member.para.pCell)
640 cell->member.cell.nNestingLevel = run_para->member.para.pCell->member.cell.nNestingLevel + 1;
641 else
642 cell->member.cell.nNestingLevel = 1;
643 } else {
644 cell->member.cell.prev_cell = run_para->member.para.pCell;
645 assert(cell->member.cell.prev_cell);
646 cell->member.cell.prev_cell->member.cell.next_cell = cell;
647 assert(run_para->member.para.nFlags & MEPF_CELL);
648 assert(!(run_para->member.para.nFlags & MEPF_ROWSTART));
649 cell->member.cell.nNestingLevel = cell->member.cell.prev_cell->member.cell.nNestingLevel;
650 cell->member.cell.parent_cell = cell->member.cell.prev_cell->member.cell.parent_cell;
652 } else if (paraFlags & MEPF_ROWEND) {
653 run_para->member.para.nFlags |= MEPF_ROWEND;
654 run_para->member.para.pCell = run_para->member.para.pCell->member.cell.parent_cell;
655 new_para->member.para.pCell = run_para->member.para.pCell;
656 assert(run_para->member.para.prev_para->member.para.nFlags & MEPF_CELL);
657 assert(!(run_para->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART));
658 if (new_para->member.para.pCell != new_para->member.para.next_para->member.para.pCell
659 && new_para->member.para.next_para->member.para.pCell
660 && !new_para->member.para.next_para->member.para.pCell->member.cell.prev_cell)
662 /* Row starts just after the row that was ended. */
663 new_para->member.para.nFlags |= MEPF_ROWSTART;
665 } else {
666 new_para->member.para.pCell = run_para->member.para.pCell;
668 ME_UpdateTableFlags(run_para);
669 ME_UpdateTableFlags(new_para);
672 /* force rewrap of the */
673 if (run_para->member.para.prev_para->type == diParagraph)
674 mark_para_rewrap(editor, run_para->member.para.prev_para);
676 mark_para_rewrap(editor, new_para->member.para.prev_para);
678 /* we've added the end run, so we need to modify nCharOfs in the next paragraphs */
679 ME_PropagateCharOffset(next_para, eol_len);
680 editor->nParagraphs++;
682 return new_para;
685 /* join tp with tp->member.para.next_para, keeping tp's style; this
686 * is consistent with the original */
687 ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp,
688 BOOL keepFirstParaFormat)
690 ME_DisplayItem *pNext, *pFirstRunInNext, *pRun, *pTmp, *pCell = NULL;
691 int i, shift;
692 int end_len;
693 CHARFORMAT2W fmt;
694 ME_Cursor startCur, endCur;
695 ME_String *eol_str;
697 assert(tp->type == diParagraph);
698 assert(tp->member.para.next_para);
699 assert(tp->member.para.next_para->type == diParagraph);
701 /* Clear any cached para numbering following this paragraph */
702 if (tp->member.para.fmt.wNumbering)
703 para_num_clear_list( editor, &tp->member.para, &tp->member.para.fmt );
705 pNext = tp->member.para.next_para;
707 /* Need to locate end-of-paragraph run here, in order to know end_len */
708 pRun = ME_FindItemBack(pNext, diRunOrParagraph);
710 assert(pRun);
711 assert(pRun->type == diRun);
712 assert(pRun->member.run.nFlags & MERF_ENDPARA);
714 end_len = pRun->member.run.len;
715 eol_str = ME_VSplitString( tp->member.para.text, pRun->member.run.nCharOfs );
716 ME_AppendString( tp->member.para.text, pNext->member.para.text->szData, pNext->member.para.text->nLen );
718 /* null char format operation to store the original char format for the ENDPARA run */
719 ME_InitCharFormat2W(&fmt);
720 endCur.pPara = pNext;
721 endCur.pRun = ME_FindItemFwd(pNext, diRun);
722 endCur.nOffset = 0;
723 startCur = endCur;
724 ME_PrevRun(&startCur.pPara, &startCur.pRun, TRUE);
725 ME_SetCharFormat(editor, &startCur, &endCur, &fmt);
727 if (!editor->bEmulateVersion10) { /* v4.1 */
728 /* Table cell/row properties are always moved over from the removed para. */
729 tp->member.para.nFlags = pNext->member.para.nFlags;
730 tp->member.para.pCell = pNext->member.para.pCell;
732 /* Remove cell boundary if it is between the end paragraph run and the next
733 * paragraph display item. */
734 for (pTmp = pRun->next; pTmp != pNext; pTmp = pTmp->next)
736 if (pTmp->type == diCell)
738 pCell = pTmp;
739 break;
744 add_undo_split_para( editor, &pNext->member.para, eol_str, pCell ? &pCell->member.cell : NULL );
746 if (pCell)
748 ME_Remove( pCell );
749 if (pCell->member.cell.prev_cell)
750 pCell->member.cell.prev_cell->member.cell.next_cell = pCell->member.cell.next_cell;
751 if (pCell->member.cell.next_cell)
752 pCell->member.cell.next_cell->member.cell.prev_cell = pCell->member.cell.prev_cell;
753 ME_DestroyDisplayItem( pCell );
756 if (!keepFirstParaFormat)
758 add_undo_set_para_fmt( editor, &tp->member.para );
759 tp->member.para.fmt = pNext->member.para.fmt;
760 tp->member.para.border = pNext->member.para.border;
763 shift = pNext->member.para.nCharOfs - tp->member.para.nCharOfs - end_len;
765 pFirstRunInNext = ME_FindItemFwd(pNext, diRunOrParagraph);
767 assert(pFirstRunInNext->type == diRun);
769 /* Update selection cursors so they don't point to the removed end
770 * paragraph run, and point to the correct paragraph. */
771 for (i=0; i < editor->nCursors; i++) {
772 if (editor->pCursors[i].pRun == pRun) {
773 editor->pCursors[i].pRun = pFirstRunInNext;
774 editor->pCursors[i].nOffset = 0;
775 } else if (editor->pCursors[i].pPara == pNext) {
776 editor->pCursors[i].pPara = tp;
780 pTmp = pNext;
781 do {
782 pTmp = ME_FindItemFwd(pTmp, diRunOrParagraphOrEnd);
783 if (pTmp->type != diRun)
784 break;
785 TRACE("shifting %s by %d (previous %d)\n", debugstr_run( &pTmp->member.run ), shift, pTmp->member.run.nCharOfs);
786 pTmp->member.run.nCharOfs += shift;
787 pTmp->member.run.para = &tp->member.para;
788 } while(1);
790 /* Fix up the para's eop_run ptr */
791 tp->member.para.eop_run = pNext->member.para.eop_run;
793 ME_Remove(pRun);
794 ME_DestroyDisplayItem(pRun);
796 if (editor->pLastSelStartPara == pNext)
797 editor->pLastSelStartPara = tp;
798 if (editor->pLastSelEndPara == pNext)
799 editor->pLastSelEndPara = tp;
801 tp->member.para.next_para = pNext->member.para.next_para;
802 pNext->member.para.next_para->member.para.prev_para = tp;
803 ME_Remove(pNext);
804 destroy_para(editor, pNext);
806 ME_PropagateCharOffset(tp->member.para.next_para, -end_len);
808 ME_CheckCharOffsets(editor);
810 editor->nParagraphs--;
811 mark_para_rewrap(editor, tp);
812 return tp;
815 ME_DisplayItem *ME_GetParagraph(ME_DisplayItem *item) {
816 return ME_FindItemBackOrHere(item, diParagraph);
819 void ME_DumpParaStyleToBuf(const PARAFORMAT2 *pFmt, char buf[2048])
821 char *p;
822 p = buf;
824 #define DUMP(mask, name, fmt, field) \
825 if (pFmt->dwMask & (mask)) p += sprintf(p, "%-22s" fmt "\n", name, pFmt->field); \
826 else p += sprintf(p, "%-22sN/A\n", name);
828 /* we take for granted that PFE_xxx is the hiword of the corresponding PFM_xxx */
829 #define DUMP_EFFECT(mask, name) \
830 p += sprintf(p, "%-22s%s\n", name, (pFmt->dwMask & (mask)) ? ((pFmt->wEffects & ((mask) >> 16)) ? "yes" : "no") : "N/A");
832 DUMP(PFM_NUMBERING, "Numbering:", "%u", wNumbering);
833 DUMP_EFFECT(PFM_DONOTHYPHEN, "Disable auto-hyphen:");
834 DUMP_EFFECT(PFM_KEEP, "No page break in para:");
835 DUMP_EFFECT(PFM_KEEPNEXT, "No page break in para & next:");
836 DUMP_EFFECT(PFM_NOLINENUMBER, "No line number:");
837 DUMP_EFFECT(PFM_NOWIDOWCONTROL, "No widow & orphan:");
838 DUMP_EFFECT(PFM_PAGEBREAKBEFORE, "Page break before:");
839 DUMP_EFFECT(PFM_RTLPARA, "RTL para:");
840 DUMP_EFFECT(PFM_SIDEBYSIDE, "Side by side:");
841 DUMP_EFFECT(PFM_TABLE, "Table:");
842 DUMP(PFM_OFFSETINDENT, "Offset indent:", "%d", dxStartIndent);
843 DUMP(PFM_STARTINDENT, "Start indent:", "%d", dxStartIndent);
844 DUMP(PFM_RIGHTINDENT, "Right indent:", "%d", dxRightIndent);
845 DUMP(PFM_OFFSET, "Offset:", "%d", dxOffset);
846 if (pFmt->dwMask & PFM_ALIGNMENT) {
847 switch (pFmt->wAlignment) {
848 case PFA_LEFT : p += sprintf(p, "Alignment: left\n"); break;
849 case PFA_RIGHT : p += sprintf(p, "Alignment: right\n"); break;
850 case PFA_CENTER : p += sprintf(p, "Alignment: center\n"); break;
851 case PFA_JUSTIFY: p += sprintf(p, "Alignment: justify\n"); break;
852 default : p += sprintf(p, "Alignment: incorrect %d\n", pFmt->wAlignment); break;
855 else p += sprintf(p, "Alignment: N/A\n");
856 DUMP(PFM_TABSTOPS, "Tab Stops:", "%d", cTabCount);
857 if (pFmt->dwMask & PFM_TABSTOPS) {
858 int i;
859 p += sprintf(p, "\t");
860 for (i = 0; i < pFmt->cTabCount; i++) p += sprintf(p, "%x ", pFmt->rgxTabs[i]);
861 p += sprintf(p, "\n");
863 DUMP(PFM_SPACEBEFORE, "Space Before:", "%d", dySpaceBefore);
864 DUMP(PFM_SPACEAFTER, "Space After:", "%d", dySpaceAfter);
865 DUMP(PFM_LINESPACING, "Line spacing:", "%d", dyLineSpacing);
866 DUMP(PFM_STYLE, "Text style:", "%d", sStyle);
867 DUMP(PFM_LINESPACING, "Line spacing rule:", "%u", bLineSpacingRule);
868 /* bOutlineLevel should be 0 */
869 DUMP(PFM_SHADING, "Shading Weight:", "%u", wShadingWeight);
870 DUMP(PFM_SHADING, "Shading Style:", "%u", wShadingStyle);
871 DUMP(PFM_NUMBERINGSTART, "Numbering Start:", "%u", wNumberingStart);
872 DUMP(PFM_NUMBERINGSTYLE, "Numbering Style:", "0x%x", wNumberingStyle);
873 DUMP(PFM_NUMBERINGTAB, "Numbering Tab:", "%u", wNumberingStyle);
874 DUMP(PFM_BORDER, "Border Space:", "%u", wBorderSpace);
875 DUMP(PFM_BORDER, "Border Width:", "%u", wBorderWidth);
876 DUMP(PFM_BORDER, "Borders:", "%u", wBorders);
878 #undef DUMP
879 #undef DUMP_EFFECT
882 void
883 ME_GetSelectionParas(ME_TextEditor *editor, ME_DisplayItem **para, ME_DisplayItem **para_end)
885 ME_Cursor *pEndCursor = &editor->pCursors[1];
887 *para = editor->pCursors[0].pPara;
888 *para_end = editor->pCursors[1].pPara;
889 if (*para == *para_end)
890 return;
892 if ((*para_end)->member.para.nCharOfs < (*para)->member.para.nCharOfs) {
893 ME_DisplayItem *tmp = *para;
895 *para = *para_end;
896 *para_end = tmp;
897 pEndCursor = &editor->pCursors[0];
900 /* The paragraph at the end of a non-empty selection isn't included
901 * if the selection ends at the start of the paragraph. */
902 if (!pEndCursor->pRun->member.run.nCharOfs && !pEndCursor->nOffset)
903 *para_end = (*para_end)->member.para.prev_para;
907 BOOL ME_SetSelectionParaFormat(ME_TextEditor *editor, const PARAFORMAT2 *pFmt)
909 ME_DisplayItem *para, *para_end;
911 ME_GetSelectionParas(editor, &para, &para_end);
913 do {
914 ME_SetParaFormat(editor, &para->member.para, pFmt);
915 if (para == para_end)
916 break;
917 para = para->member.para.next_para;
918 } while(1);
920 return TRUE;
923 static void ME_GetParaFormat(ME_TextEditor *editor,
924 const ME_DisplayItem *para,
925 PARAFORMAT2 *pFmt)
927 UINT cbSize = pFmt->cbSize;
928 if (pFmt->cbSize >= sizeof(PARAFORMAT2)) {
929 *pFmt = para->member.para.fmt;
930 } else {
931 CopyMemory(pFmt, &para->member.para.fmt, pFmt->cbSize);
932 pFmt->dwMask &= PFM_ALL;
934 pFmt->cbSize = cbSize;
937 void ME_GetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
939 ME_DisplayItem *para, *para_end;
940 PARAFORMAT2 *curFmt;
942 if (pFmt->cbSize < sizeof(PARAFORMAT)) {
943 pFmt->dwMask = 0;
944 return;
947 ME_GetSelectionParas(editor, &para, &para_end);
949 ME_GetParaFormat(editor, para, pFmt);
951 /* Invalidate values that change across the selected paragraphs. */
952 while (para != para_end)
954 para = para->member.para.next_para;
955 curFmt = &para->member.para.fmt;
957 #define CHECK_FIELD(m, f) \
958 if (pFmt->f != curFmt->f) pFmt->dwMask &= ~(m);
960 CHECK_FIELD(PFM_NUMBERING, wNumbering);
961 CHECK_FIELD(PFM_STARTINDENT, dxStartIndent);
962 CHECK_FIELD(PFM_RIGHTINDENT, dxRightIndent);
963 CHECK_FIELD(PFM_OFFSET, dxOffset);
964 CHECK_FIELD(PFM_ALIGNMENT, wAlignment);
965 if (pFmt->dwMask & PFM_TABSTOPS) {
966 if (pFmt->cTabCount != para->member.para.fmt.cTabCount ||
967 memcmp(pFmt->rgxTabs, curFmt->rgxTabs, curFmt->cTabCount*sizeof(int)))
968 pFmt->dwMask &= ~PFM_TABSTOPS;
971 if (pFmt->dwMask >= sizeof(PARAFORMAT2))
973 pFmt->dwMask &= ~((pFmt->wEffects ^ curFmt->wEffects) << 16);
974 CHECK_FIELD(PFM_SPACEBEFORE, dySpaceBefore);
975 CHECK_FIELD(PFM_SPACEAFTER, dySpaceAfter);
976 CHECK_FIELD(PFM_LINESPACING, dyLineSpacing);
977 CHECK_FIELD(PFM_STYLE, sStyle);
978 CHECK_FIELD(PFM_SPACEAFTER, bLineSpacingRule);
979 CHECK_FIELD(PFM_SHADING, wShadingWeight);
980 CHECK_FIELD(PFM_SHADING, wShadingStyle);
981 CHECK_FIELD(PFM_NUMBERINGSTART, wNumberingStart);
982 CHECK_FIELD(PFM_NUMBERINGSTYLE, wNumberingStyle);
983 CHECK_FIELD(PFM_NUMBERINGTAB, wNumberingTab);
984 CHECK_FIELD(PFM_BORDER, wBorderSpace);
985 CHECK_FIELD(PFM_BORDER, wBorderWidth);
986 CHECK_FIELD(PFM_BORDER, wBorders);
988 #undef CHECK_FIELD
992 void ME_SetDefaultParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
994 const PARAFORMAT2 *host_fmt;
995 HRESULT hr;
997 ZeroMemory(pFmt, sizeof(PARAFORMAT2));
998 pFmt->cbSize = sizeof(PARAFORMAT2);
999 pFmt->dwMask = PFM_ALL2;
1000 pFmt->wAlignment = PFA_LEFT;
1001 pFmt->sStyle = -1;
1002 pFmt->bOutlineLevel = TRUE;
1004 hr = ITextHost_TxGetParaFormat( editor->texthost, (const PARAFORMAT **)&host_fmt );
1005 if (SUCCEEDED(hr))
1007 /* Just use the alignment for now */
1008 if (host_fmt->dwMask & PFM_ALIGNMENT)
1009 pFmt->wAlignment = host_fmt->wAlignment;
1010 ITextHost_OnTxParaFormatChange( editor->texthost, (PARAFORMAT *)pFmt );