riched20: Pass a cursor ptr to the insert style retrieval function.
[wine.git] / dlls / riched20 / run.c
blobb1436ad50608753f7c9dbf6174acd34e4db98b11
1 /*
2 * RichEdit - operations on runs (diRun, rectangular pieces of paragraphs).
3 * Splitting/joining runs. Adjusting offsets after deleting/adding content.
4 * Character/pixel conversions.
6 * Copyright 2004 by Krzysztof Foltman
7 * Copyright 2006 by Phil Krylov
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24 #include "editor.h"
26 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
27 WINE_DECLARE_DEBUG_CHANNEL(richedit_check);
28 WINE_DECLARE_DEBUG_CHANNEL(richedit_lists);
30 ME_Run *run_next( ME_Run *run )
32 ME_DisplayItem *item = run_get_di( run );
34 if (ME_NextRun( NULL, &item, FALSE ))
35 return &item->member.run;
37 return NULL;
40 ME_Run *run_prev( ME_Run *run )
42 ME_DisplayItem *item = run_get_di( run );
44 if (ME_PrevRun( NULL, &item, FALSE ))
45 return &item->member.run;
47 return NULL;
50 ME_Run *run_next_all_paras( ME_Run *run )
52 ME_DisplayItem *item = run_get_di( run ), *dummy = para_get_di( run->para );
54 if (ME_NextRun( &dummy, &item, TRUE ))
55 return &item->member.run;
57 return NULL;
60 ME_Run *run_prev_all_paras( ME_Run *run )
62 ME_DisplayItem *item = run_get_di( run ), *dummy = para_get_di( run->para );
64 if (ME_PrevRun( &dummy, &item, TRUE ))
65 return &item->member.run;
67 return NULL;
70 /******************************************************************************
71 * ME_CanJoinRuns
73 * Returns TRUE if two runs can be safely merged into one, FALSE otherwise.
75 BOOL ME_CanJoinRuns(const ME_Run *run1, const ME_Run *run2)
77 if ((run1->nFlags | run2->nFlags) & MERF_NOJOIN)
78 return FALSE;
79 if (run1->style != run2->style)
80 return FALSE;
81 if ((run1->nFlags & MERF_STYLEFLAGS) != (run2->nFlags & MERF_STYLEFLAGS))
82 return FALSE;
83 return TRUE;
86 void ME_SkipAndPropagateCharOffset(ME_DisplayItem *p, int shift)
88 p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
89 assert(p);
90 ME_PropagateCharOffset(p, shift);
93 /******************************************************************************
94 * ME_PropagateCharOffsets
96 * Shifts (increases or decreases) character offset (relative to beginning of
97 * the document) of the part of the text starting from given place.
98 */
99 void ME_PropagateCharOffset(ME_DisplayItem *p, int shift)
101 /* Runs in one paragraph contain character offset relative to their owning
102 * paragraph. If we start the shifting from the run, we need to shift
103 * all the relative offsets until the end of the paragraph
105 if (p->type == diRun) /* propagate in all runs in this para */
107 TRACE("PropagateCharOffset(%s, %d)\n", debugstr_run( &p->member.run ), shift);
108 do {
109 p->member.run.nCharOfs += shift;
110 assert(p->member.run.nCharOfs >= 0);
111 p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
112 } while(p->type == diRun);
114 /* Runs in next paragraphs don't need their offsets updated, because they,
115 * again, those offsets are relative to their respective paragraphs.
116 * Instead of that, we're updating paragraphs' character offsets.
118 if (p->type == diParagraph) /* propagate in all next paras */
120 do {
121 p->member.para.nCharOfs += shift;
122 assert(p->member.para.nCharOfs >= 0);
123 p = p->member.para.next_para;
124 } while(p->type == diParagraph);
126 /* diTextEnd also has character offset in it, which makes finding text length
127 * easier. But it needs to be up to date first.
129 if (p->type == diTextEnd)
131 p->member.para.nCharOfs += shift;
132 assert(p->member.para.nCharOfs >= 0);
136 /******************************************************************************
137 * ME_CheckCharOffsets
139 * Checks if editor lists' validity and optionally dumps the document structure
141 void ME_CheckCharOffsets(ME_TextEditor *editor)
143 ME_DisplayItem *p = editor->pBuffer->pFirst;
144 int ofs = 0, ofsp = 0;
146 if (!TRACE_ON(richedit_check))
147 return;
149 TRACE_(richedit_check)("Checking begin\n");
150 if(TRACE_ON(richedit_lists))
152 TRACE_(richedit_lists)("---\n");
153 ME_DumpDocument(editor->pBuffer);
155 do {
156 p = ME_FindItemFwd(p, diRunOrParagraphOrEnd);
157 switch(p->type) {
158 case diTextEnd:
159 TRACE_(richedit_check)("tend, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs);
160 assert(ofsp+ofs == p->member.para.nCharOfs);
161 TRACE_(richedit_check)("Checking finished\n");
162 return;
163 case diParagraph:
164 TRACE_(richedit_check)("para, real ofsp = %d, counted = %d\n", p->member.para.nCharOfs, ofsp+ofs);
165 assert(ofsp+ofs == p->member.para.nCharOfs);
166 ofsp = p->member.para.nCharOfs;
167 ofs = 0;
168 break;
169 case diRun:
170 TRACE_(richedit_check)("run, real ofs = %d (+ofsp = %d), counted = %d, len = %d, txt = %s, flags=%08x, fx&mask = %08x\n",
171 p->member.run.nCharOfs, p->member.run.nCharOfs+ofsp, ofsp+ofs,
172 p->member.run.len, debugstr_run( &p->member.run ),
173 p->member.run.nFlags,
174 p->member.run.style->fmt.dwMask & p->member.run.style->fmt.dwEffects);
175 assert(ofs == p->member.run.nCharOfs);
176 assert(p->member.run.len);
177 ofs += p->member.run.len;
178 break;
179 case diCell:
180 TRACE_(richedit_check)("cell\n");
181 break;
182 default:
183 assert(0);
185 } while(1);
186 TRACE_(richedit_check)("Checking finished\n");
189 /******************************************************************************
190 * run_char_ofs
192 * Converts a character position relative to the start of the run to a
193 * character position relative to the start of the document.
196 int run_char_ofs( ME_Run *run, int ofs )
198 return run->para->nCharOfs + run->nCharOfs + ofs;
201 /******************************************************************************
202 * cursor_from_char_ofs
204 * Converts a character offset (relative to the start of the document) to
205 * a cursor structure (which contains a run and a position relative to that
206 * run).
208 void cursor_from_char_ofs( ME_TextEditor *editor, int char_ofs, ME_Cursor *cursor )
210 ME_Paragraph *para;
211 ME_Run *run;
213 char_ofs = min( max( char_ofs, 0 ), ME_GetTextLength( editor ) );
215 /* Find the paragraph at the offset. */
216 for (para = editor_first_para( editor );
217 para_next( para )->nCharOfs <= char_ofs;
218 para = para_next( para ))
221 char_ofs -= para->nCharOfs;
223 /* Find the run at the offset. */
224 for (run = para_first_run( para );
225 run_next( run ) && run_next( run )->nCharOfs <= char_ofs;
226 run = run_next( run ))
229 char_ofs -= run->nCharOfs;
231 cursor->pPara = para_get_di( para );
232 cursor->pRun = run_get_di( run );
233 cursor->nOffset = char_ofs;
236 /******************************************************************************
237 * run_join
239 * Merges two adjacent runs, the one given as a parameter and the next one.
241 void run_join( ME_TextEditor *editor, ME_Run *run )
243 ME_Run *next = run_next( run );
244 int i;
246 assert( run );
247 assert( run->nCharOfs != -1 );
248 para_mark_rewrap( editor, run->para );
250 /* Update all cursors so that they don't contain the soon deleted run */
251 for (i = 0; i < editor->nCursors; i++)
253 if (&editor->pCursors[i].pRun->member.run == next)
255 editor->pCursors[i].pRun = run_get_di( run );
256 editor->pCursors[i].nOffset += run->len;
260 run->len += next->len;
261 ME_Remove( run_get_di( next ) );
262 ME_DestroyDisplayItem( run_get_di( next ) );
263 ME_UpdateRunFlags( editor, run );
264 ME_CheckCharOffsets( editor );
267 /******************************************************************************
268 * run_split
270 * Does the most basic job of splitting a run into two - it does not
271 * update the positions and extents.
273 ME_Run *run_split( ME_TextEditor *editor, ME_Cursor *cursor )
275 ME_Run *run = &cursor->pRun->member.run, *new_run;
276 int i;
277 int nOffset = cursor->nOffset;
279 assert( !(run->nFlags & MERF_NONTEXT) );
281 new_run = run_create( run->style, run->nFlags & MERF_SPLITMASK );
282 new_run->nCharOfs = run->nCharOfs + nOffset;
283 new_run->len = run->len - nOffset;
284 new_run->para = run->para;
285 run->len = nOffset;
286 cursor->pRun = run_get_di( new_run );
287 cursor->nOffset = 0;
289 ME_InsertBefore( run_get_di( run )->next, run_get_di( new_run ) );
291 ME_UpdateRunFlags( editor, run );
292 ME_UpdateRunFlags( editor, new_run );
293 for (i = 0; i < editor->nCursors; i++)
295 if (editor->pCursors[i].pRun == run_get_di( run ) &&
296 editor->pCursors[i].nOffset >= nOffset)
298 editor->pCursors[i].pRun = run_get_di( new_run );
299 editor->pCursors[i].nOffset -= nOffset;
302 para_mark_rewrap( editor, run->para );
303 return run;
306 /******************************************************************************
307 * run_create
309 * A helper function to create run structures quickly.
311 ME_Run *run_create( ME_Style *s, int flags )
313 ME_DisplayItem *item = ME_MakeDI( diRun );
314 ME_Run *run = &item->member.run;
316 if (!item) return NULL;
318 ME_AddRefStyle( s );
319 run->style = s;
320 run->reobj = NULL;
321 run->nFlags = flags;
322 run->nCharOfs = -1;
323 run->len = 0;
324 run->para = NULL;
325 run->num_glyphs = 0;
326 run->max_glyphs = 0;
327 run->glyphs = NULL;
328 run->vis_attrs = NULL;
329 run->advances = NULL;
330 run->offsets = NULL;
331 run->max_clusters = 0;
332 run->clusters = NULL;
333 return run;
336 /******************************************************************************
337 * run_insert
339 * Inserts a new run with given style, flags and content at a given position,
340 * which is passed as a cursor structure (which consists of a run and
341 * a run-relative character offset).
343 ME_Run *run_insert( ME_TextEditor *editor, ME_Cursor *cursor, ME_Style *style,
344 const WCHAR *str, int len, int flags )
346 ME_Run *insert_before = &cursor->pRun->member.run, *run, *prev;
348 if (cursor->nOffset)
350 if (cursor->nOffset == insert_before->len)
352 insert_before = run_next_all_paras( insert_before );
353 if (!insert_before) insert_before = &cursor->pRun->member.run; /* Always insert before the final eop run */
355 else
357 run_split( editor, cursor );
358 insert_before = &cursor->pRun->member.run;
362 add_undo_delete_run( editor, insert_before->para->nCharOfs + insert_before->nCharOfs, len );
364 run = run_create( style, flags );
365 run->nCharOfs = insert_before->nCharOfs;
366 run->len = len;
367 run->para = insert_before->para;
368 ME_InsertString( run->para->text, run->nCharOfs, str, len );
369 ME_InsertBefore( run_get_di( insert_before ), run_get_di( run ) );
370 TRACE("Shift length:%d\n", len);
371 ME_PropagateCharOffset( run_get_di( insert_before ), len );
372 para_mark_rewrap( editor, insert_before->para );
374 /* Move any cursors that were at the end of the previous run to the end of the inserted run */
375 prev = run_prev_all_paras( run );
376 if (prev)
378 int i;
380 for (i = 0; i < editor->nCursors; i++)
382 if (editor->pCursors[i].pRun == run_get_di( prev ) &&
383 editor->pCursors[i].nOffset == prev->len)
385 editor->pCursors[i].pRun = run_get_di( run );
386 editor->pCursors[i].nOffset = len;
391 return run;
394 static BOOL run_is_splittable( const ME_Run *run )
396 WCHAR *str = get_text( run, 0 ), *p;
397 int i;
398 BOOL found_ink = FALSE;
400 for (i = 0, p = str; i < run->len; i++, p++)
402 if (ME_IsWSpace( *p ))
404 if (found_ink) return TRUE;
406 else
407 found_ink = TRUE;
409 return FALSE;
412 static BOOL run_is_entirely_ws( const ME_Run *run )
414 WCHAR *str = get_text( run, 0 ), *p;
415 int i;
417 for (i = 0, p = str; i < run->len; i++, p++)
418 if (!ME_IsWSpace( *p )) return FALSE;
420 return TRUE;
423 /******************************************************************************
424 * ME_UpdateRunFlags
426 * Determine some of run attributes given its content (style, text content).
427 * Some flags cannot be determined by this function (MERF_GRAPHICS,
428 * MERF_ENDPARA)
430 void ME_UpdateRunFlags(ME_TextEditor *editor, ME_Run *run)
432 assert(run->nCharOfs >= 0);
434 if (RUN_IS_HIDDEN(run) || run->nFlags & MERF_TABLESTART)
435 run->nFlags |= MERF_HIDDEN;
436 else
437 run->nFlags &= ~MERF_HIDDEN;
439 if (run_is_splittable( run ))
440 run->nFlags |= MERF_SPLITTABLE;
441 else
442 run->nFlags &= ~MERF_SPLITTABLE;
444 if (!(run->nFlags & MERF_NOTEXT))
446 if (run_is_entirely_ws( run ))
447 run->nFlags |= MERF_WHITESPACE | MERF_STARTWHITE | MERF_ENDWHITE;
448 else
450 run->nFlags &= ~MERF_WHITESPACE;
452 if (ME_IsWSpace( *get_text( run, 0 ) ))
453 run->nFlags |= MERF_STARTWHITE;
454 else
455 run->nFlags &= ~MERF_STARTWHITE;
457 if (ME_IsWSpace( *get_text( run, run->len - 1 ) ))
458 run->nFlags |= MERF_ENDWHITE;
459 else
460 run->nFlags &= ~MERF_ENDWHITE;
463 else
464 run->nFlags &= ~(MERF_WHITESPACE | MERF_STARTWHITE | MERF_ENDWHITE);
467 /******************************************************************************
468 * ME_CharFromPointContext
470 * Returns a character position inside the run given a run-relative
471 * pixel horizontal position.
473 * If closest is FALSE return the actual character
474 * If closest is TRUE will round to the closest leading edge.
475 * ie. if the second character is at pixel position 8 and third at 16 then for:
476 * closest = FALSE cx = 0..7 return 0, cx = 8..15 return 1
477 * closest = TRUE cx = 0..3 return 0, cx = 4..11 return 1.
479 int ME_CharFromPointContext(ME_Context *c, int cx, ME_Run *run, BOOL closest, BOOL visual_order)
481 ME_String *mask_text = NULL;
482 WCHAR *str;
483 int fit = 0;
484 SIZE sz, sz2, sz3;
485 if (!run->len || cx <= 0)
486 return 0;
488 if (run->nFlags & (MERF_TAB | MERF_ENDCELL))
490 if (!closest || cx < run->nWidth / 2) return 0;
491 return 1;
494 if (run->nFlags & MERF_GRAPHICS)
496 SIZE sz;
497 ME_GetOLEObjectSize(c, run, &sz);
498 if (!closest || cx < sz.cx / 2) return 0;
499 return 1;
502 if (run->para->nFlags & MEPF_COMPLEX)
504 int cp, trailing;
505 if (visual_order && run->script_analysis.fRTL) cx = run->nWidth - cx - 1;
507 ScriptXtoCP( cx, run->len, run->num_glyphs, run->clusters, run->vis_attrs, run->advances, &run->script_analysis,
508 &cp, &trailing );
509 TRACE("x %d cp %d trailing %d (run width %d) rtl %d log order %d\n", cx, cp, trailing, run->nWidth,
510 run->script_analysis.fRTL, run->script_analysis.fLogicalOrder);
511 return closest ? cp + trailing : cp;
514 if (c->editor->cPasswordMask)
516 mask_text = ME_MakeStringR( c->editor->cPasswordMask, run->len );
517 str = mask_text->szData;
519 else
520 str = get_text( run, 0 );
522 select_style(c, run->style);
523 GetTextExtentExPointW(c->hDC, str, run->len,
524 cx, &fit, NULL, &sz);
525 if (closest && fit != run->len)
527 GetTextExtentPoint32W(c->hDC, str, fit, &sz2);
528 GetTextExtentPoint32W(c->hDC, str, fit + 1, &sz3);
529 if (cx >= (sz2.cx+sz3.cx)/2)
530 fit = fit + 1;
533 ME_DestroyString( mask_text );
535 return fit;
538 int ME_CharFromPoint(ME_TextEditor *editor, int cx, ME_Run *run, BOOL closest, BOOL visual_order)
540 ME_Context c;
541 int ret;
543 ME_InitContext( &c, editor, ITextHost_TxGetDC( editor->texthost ) );
544 ret = ME_CharFromPointContext( &c, cx, run, closest, visual_order );
545 ME_DestroyContext(&c);
546 return ret;
549 /******************************************************************************
550 * ME_GetTextExtent
552 * Finds a width and a height of the text using a specified style
554 static void ME_GetTextExtent(ME_Context *c, LPCWSTR szText, int nChars, ME_Style *s, SIZE *size)
556 if (c->hDC)
558 select_style( c, s );
559 GetTextExtentPoint32W( c->hDC, szText, nChars, size );
561 else
563 size->cx = 0;
564 size->cy = 0;
568 /******************************************************************************
569 * ME_PointFromCharContext
571 * Returns a run-relative pixel position given a run-relative character
572 * position (character offset)
574 int ME_PointFromCharContext(ME_Context *c, ME_Run *pRun, int nOffset, BOOL visual_order)
576 SIZE size;
577 ME_String *mask_text = NULL;
578 WCHAR *str;
580 if (pRun->nFlags & MERF_GRAPHICS)
582 if (nOffset)
583 ME_GetOLEObjectSize(c, pRun, &size);
584 return nOffset != 0;
585 } else if (pRun->nFlags & MERF_ENDPARA) {
586 nOffset = 0;
589 if (pRun->para->nFlags & MEPF_COMPLEX)
591 int x;
592 ScriptCPtoX( nOffset, FALSE, pRun->len, pRun->num_glyphs, pRun->clusters,
593 pRun->vis_attrs, pRun->advances, &pRun->script_analysis, &x );
594 if (visual_order && pRun->script_analysis.fRTL) x = pRun->nWidth - x - 1;
595 return x;
597 if (c->editor->cPasswordMask)
599 mask_text = ME_MakeStringR(c->editor->cPasswordMask, pRun->len);
600 str = mask_text->szData;
602 else
603 str = get_text( pRun, 0 );
605 ME_GetTextExtent(c, str, nOffset, pRun->style, &size);
606 ME_DestroyString( mask_text );
607 return size.cx;
610 /******************************************************************************
611 * ME_PointFromChar
613 * Calls ME_PointFromCharContext after first creating a context.
615 int ME_PointFromChar(ME_TextEditor *editor, ME_Run *pRun, int nOffset, BOOL visual_order)
617 ME_Context c;
618 int ret;
620 ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost));
621 ret = ME_PointFromCharContext( &c, pRun, nOffset, visual_order );
622 ME_DestroyContext(&c);
624 return ret;
627 /******************************************************************************
628 * ME_GetRunSizeCommon
630 * Finds width, height, ascent and descent of a run, up to given character
631 * (nLen).
633 SIZE ME_GetRunSizeCommon(ME_Context *c, const ME_Paragraph *para, ME_Run *run, int nLen,
634 int startx, int *pAscent, int *pDescent)
636 static const WCHAR spaceW[] = {' ',0};
637 SIZE size;
639 nLen = min( nLen, run->len );
641 if (run->nFlags & MERF_ENDPARA)
643 nLen = min( nLen, 1 );
644 ME_GetTextExtent(c, spaceW, nLen, run->style, &size);
646 else if (para->nFlags & MEPF_COMPLEX)
648 size.cx = run->nWidth;
650 else if (c->editor->cPasswordMask)
652 ME_String *szMasked = ME_MakeStringR(c->editor->cPasswordMask,nLen);
653 ME_GetTextExtent(c, szMasked->szData, nLen,run->style, &size);
654 ME_DestroyString(szMasked);
656 else
658 ME_GetTextExtent(c, get_text( run, 0 ), nLen, run->style, &size);
660 *pAscent = run->style->tm.tmAscent;
661 *pDescent = run->style->tm.tmDescent;
662 size.cy = *pAscent + *pDescent;
664 if (run->nFlags & MERF_TAB)
666 int pos = 0, i = 0, ppos, shift = 0;
667 const PARAFORMAT2 *pFmt = &para->fmt;
669 if (c->editor->bEmulateVersion10 && /* v1.0 - 3.0 */
670 pFmt->dwMask & PFM_TABLE && pFmt->wEffects & PFE_TABLE)
671 /* The horizontal gap shifts the tab positions to leave the gap. */
672 shift = pFmt->dxOffset * 2;
673 do {
674 if (i < pFmt->cTabCount)
676 /* Only one side of the horizontal gap is needed at the end of
677 * the table row. */
678 if (i == pFmt->cTabCount -1)
679 shift = shift >> 1;
680 pos = shift + (pFmt->rgxTabs[i]&0x00FFFFFF);
681 i++;
683 else
685 pos += lDefaultTab - (pos % lDefaultTab);
687 ppos = ME_twips2pointsX(c, pos);
688 if (ppos > startx + run->pt.x) {
689 size.cx = ppos - startx - run->pt.x;
690 break;
692 } while(1);
693 size.cy = *pAscent + *pDescent;
694 return size;
696 if (run->nFlags & MERF_GRAPHICS)
698 ME_GetOLEObjectSize(c, run, &size);
699 if (size.cy > *pAscent)
700 *pAscent = size.cy;
701 /* descent is unchanged */
702 return size;
704 return size;
707 /******************************************************************************
708 * ME_SetSelectionCharFormat
710 * Applies a style change, either to a current selection, or to insert cursor
711 * (ie. the style next typed characters will use).
713 void ME_SetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
715 if (!ME_IsSelection(editor))
717 ME_Style *s;
718 if (!editor->pBuffer->pCharStyle)
719 editor->pBuffer->pCharStyle = style_get_insert_style( editor, editor->pCursors );
720 s = ME_ApplyStyle(editor, editor->pBuffer->pCharStyle, pFmt);
721 ME_ReleaseStyle(editor->pBuffer->pCharStyle);
722 editor->pBuffer->pCharStyle = s;
723 } else {
724 ME_Cursor *from, *to;
725 ME_GetSelection(editor, &from, &to);
726 ME_SetCharFormat(editor, from, to, pFmt);
730 /******************************************************************************
731 * ME_SetCharFormat
733 * Applies a style change to the specified part of the text
735 * The start and end cursors specify the part of the text. These cursors will
736 * be updated to stay valid, but this function may invalidate other
737 * non-selection cursors. The end cursor may be NULL to specify all the text
738 * following the start cursor.
740 * If no text is selected, then nothing is done.
742 void ME_SetCharFormat( ME_TextEditor *editor, ME_Cursor *start, ME_Cursor *end, CHARFORMAT2W *fmt )
744 ME_Run *run, *start_run = &start->pRun->member.run, *end_run = NULL;
746 if (end && start->pRun == end->pRun && start->nOffset == end->nOffset)
747 return;
749 if (start->nOffset == start->pRun->member.run.len)
750 start_run = run_next_all_paras( &start->pRun->member.run );
751 else if (start->nOffset)
753 /* run_split() may or may not update the cursors, depending on whether they
754 * are selection cursors, but we need to make sure they are valid. */
755 int split_offset = start->nOffset;
756 ME_Run *split_run = run_split( editor, start );
757 start_run = &start->pRun->member.run;
758 if (end && &end->pRun->member.run == split_run)
760 end->pRun = start->pRun;
761 end->nOffset -= split_offset;
765 if (end)
767 if (end->nOffset == end->pRun->member.run.len)
768 end_run = run_next_all_paras( &end->pRun->member.run );
769 else
771 if (end->nOffset) run_split( editor, end );
772 end_run = &end->pRun->member.run;
776 for (run = start_run; run != end_run; run = run_next_all_paras( run ))
778 ME_Style *new_style = ME_ApplyStyle( editor, run->style, fmt );
779 ME_Paragraph *para = run->para;
781 add_undo_set_char_fmt( editor, para->nCharOfs + run->nCharOfs,
782 run->len, &run->style->fmt );
783 ME_ReleaseStyle( run->style );
784 run->style = new_style;
786 /* The para numbering style depends on the eop style */
787 if ((run->nFlags & MERF_ENDPARA) && para->para_num.style)
789 ME_ReleaseStyle(para->para_num.style);
790 para->para_num.style = NULL;
792 para_mark_rewrap( editor, para );
796 static void run_copy_char_fmt( ME_Run *run, CHARFORMAT2W *fmt )
798 ME_CopyCharFormat( fmt, &run->style->fmt );
801 /******************************************************************************
802 * ME_GetDefaultCharFormat
804 * Retrieves the current default character style (the one applied where no
805 * other style was applied) .
807 void ME_GetDefaultCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
809 ME_CopyCharFormat(pFmt, &editor->pBuffer->pDefaultStyle->fmt);
812 /******************************************************************************
813 * ME_GetSelectionCharFormat
815 * If selection exists, it returns all style elements that are set consistently
816 * in the whole selection. If not, it just returns the current style.
818 void ME_GetSelectionCharFormat(ME_TextEditor *editor, CHARFORMAT2W *pFmt)
820 ME_Cursor *from, *to;
821 if (!ME_IsSelection(editor) && editor->pBuffer->pCharStyle)
823 ME_CopyCharFormat(pFmt, &editor->pBuffer->pCharStyle->fmt);
824 return;
826 ME_GetSelection(editor, &from, &to);
827 ME_GetCharFormat(editor, from, to, pFmt);
830 /******************************************************************************
831 * ME_GetCharFormat
833 * Returns the style consisting of those attributes which are consistently set
834 * in the whole character range.
836 void ME_GetCharFormat( ME_TextEditor *editor, const ME_Cursor *from,
837 const ME_Cursor *to, CHARFORMAT2W *fmt )
839 ME_Run *run, *run_end, *prev_run;
840 CHARFORMAT2W tmp;
842 run = &from->pRun->member.run;
843 /* special case - if selection is empty, take previous char's formatting */
844 if (from->pRun == to->pRun && from->nOffset == to->nOffset)
846 if (!from->nOffset && (prev_run = run_prev( run ))) run = prev_run;
847 run_copy_char_fmt( run, fmt );
848 return;
851 run_end = &to->pRun->member.run;
852 if (!to->nOffset) run_end = run_prev_all_paras( run_end );
854 run_copy_char_fmt( run, fmt );
856 if (run == run_end) return;
858 do {
859 /* FIXME add more style feature comparisons */
860 DWORD dwAttribs = CFM_SIZE | CFM_FACE | CFM_COLOR | CFM_UNDERLINETYPE;
861 DWORD dwEffects = CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE | CFM_STRIKEOUT | CFM_PROTECTED | CFM_LINK | CFM_SUPERSCRIPT;
863 run = run_next_all_paras( run );
865 memset( &tmp, 0, sizeof(tmp) );
866 tmp.cbSize = sizeof(tmp);
867 run_copy_char_fmt( run, &tmp );
869 assert((tmp.dwMask & dwAttribs) == dwAttribs);
870 /* reset flags that differ */
872 if (fmt->yHeight != tmp.yHeight) fmt->dwMask &= ~CFM_SIZE;
873 if (fmt->dwMask & CFM_FACE)
875 if (!(tmp.dwMask & CFM_FACE))
876 fmt->dwMask &= ~CFM_FACE;
877 else if (wcscmp( fmt->szFaceName, tmp.szFaceName ) ||
878 fmt->bPitchAndFamily != tmp.bPitchAndFamily)
879 fmt->dwMask &= ~CFM_FACE;
881 if (fmt->yHeight != tmp.yHeight) fmt->dwMask &= ~CFM_SIZE;
882 if (fmt->bUnderlineType != tmp.bUnderlineType) fmt->dwMask &= ~CFM_UNDERLINETYPE;
883 if (fmt->dwMask & CFM_COLOR)
885 if (!((fmt->dwEffects&CFE_AUTOCOLOR) & (tmp.dwEffects&CFE_AUTOCOLOR)))
887 if (fmt->crTextColor != tmp.crTextColor)
888 fmt->dwMask &= ~CFM_COLOR;
892 fmt->dwMask &= ~((fmt->dwEffects ^ tmp.dwEffects) & dwEffects);
893 fmt->dwEffects = tmp.dwEffects;
895 } while(run != run_end);