riched20: Initial support for simple tables.
[wine] / dlls / riched20 / para.c
1 /*
2  * RichEdit - functions working on paragraphs of text (diParagraph).
3  * 
4  * Copyright 2004 by Krzysztof Foltman
5  * Copyright 2006 by Phil Krylov
6  *
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.
11  *
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.
16  *
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */ 
21
22 #include "editor.h"
23
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
25
26 static const WCHAR wszParagraphSign[] = {0xB6, 0};
27
28 void ME_MakeFirstParagraph(HDC hDC, ME_TextBuffer *text)
29 {
30   PARAFORMAT2 fmt;
31   CHARFORMAT2W cf;
32   LOGFONTW lf;
33   HFONT hf;
34   ME_DisplayItem *para = ME_MakeDI(diParagraph);
35   ME_DisplayItem *run;
36   ME_Style *style;
37
38   hf = (HFONT)GetStockObject(SYSTEM_FONT);
39   assert(hf);
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;
48   
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;
58   
59   ZeroMemory(&fmt, sizeof(fmt));
60   fmt.cbSize = sizeof(fmt);
61   fmt.dwMask = PFM_ALIGNMENT | PFM_OFFSET | PFM_STARTINDENT | PFM_RIGHTINDENT | PFM_TABSTOPS;
62
63   CopyMemory(para->member.para.pFmt, &fmt, sizeof(PARAFORMAT2));
64   
65   style = ME_MakeStyle(&cf);
66   text->pDefaultStyle = style;
67   
68   run = ME_MakeRun(style, ME_MakeString(wszParagraphSign), MERF_ENDPARA);
69   run->member.run.nCharOfs = 0;
70
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;
77
78   text->pLast->member.para.nCharOfs = 1;
79 }
80  
81 void ME_MarkAllForWrapping(ME_TextEditor *editor)
82 {
83   ME_MarkForWrapping(editor, editor->pBuffer->pFirst->member.para.next_para, editor->pBuffer->pLast);
84 }
85
86 void ME_MarkForWrapping(ME_TextEditor *editor, ME_DisplayItem *first, ME_DisplayItem *last)
87 {
88   while(first != last)
89   {
90     first->member.para.nFlags |= MEPF_REWRAP;
91     first = first->member.para.next_para;
92   }
93 }
94
95 /* split paragraph at the beginning of the run */
96 ME_DisplayItem *ME_SplitParagraph(ME_TextEditor *editor, ME_DisplayItem *run, ME_Style *style)
97 {
98   ME_DisplayItem *next_para = NULL;
99   ME_DisplayItem *run_para = NULL;
100   ME_DisplayItem *new_para = ME_MakeDI(diParagraph);
101   ME_DisplayItem *end_run = ME_MakeRun(style,ME_MakeString(wszParagraphSign), MERF_ENDPARA);
102   ME_UndoItem *undo = NULL;
103   int ofs;
104   ME_DisplayItem *pp;
105   int end_len = (editor->bEmulateVersion10 ? 2 : 1);
106   
107   assert(run->type == diRun);  
108
109   run_para = ME_GetParagraph(run);
110   assert(run_para->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
111
112   ofs = end_run->member.run.nCharOfs = run->member.run.nCharOfs;
113   next_para = run_para->member.para.next_para;
114   assert(next_para == ME_FindItemFwd(run_para, diParagraphOrEnd));
115   
116   undo = ME_AddUndoItem(editor, diUndoJoinParagraphs, NULL);
117   if (undo)
118     undo->nStart = run_para->member.para.nCharOfs + ofs;
119   
120   /* the new paragraph will have a different starting offset, so let's update its runs */
121   pp = run;
122   while(pp->type == diRun) {
123     pp->member.run.nCharOfs -= ofs;
124     pp = ME_FindItemFwd(pp, diRunOrParagraphOrEnd);
125   }
126   new_para->member.para.nCharOfs = ME_GetParagraph(run)->member.para.nCharOfs+ofs;
127   new_para->member.para.nCharOfs += end_len;
128   
129   new_para->member.para.nFlags = MEPF_REWRAP; /* FIXME copy flags (if applicable) */
130   /* FIXME initialize format style and call ME_SetParaFormat blah blah */
131   CopyMemory(new_para->member.para.pFmt, run_para->member.para.pFmt, sizeof(PARAFORMAT2));
132   
133   /* FIXME remove this as soon as nLeftMargin etc are replaced with proper fields of PARAFORMAT2 */
134   new_para->member.para.nLeftMargin = run_para->member.para.nLeftMargin;
135   new_para->member.para.nRightMargin = run_para->member.para.nRightMargin;
136   new_para->member.para.nFirstMargin = run_para->member.para.nFirstMargin;
137
138   new_para->member.para.bTable = run_para->member.para.bTable;
139   new_para->member.para.pCells = NULL;
140
141   /* fix paragraph properties. FIXME only needed when called from RTF reader */
142   if (run_para->member.para.pCells && !run_para->member.para.bTable)
143   {
144     /* Paragraph does not have an \intbl keyword, so any table definition
145      * stored is invalid */
146     ME_DestroyTableCellList(run_para);
147   }
148   
149   /* insert paragraph into paragraph double linked list */
150   new_para->member.para.prev_para = run_para;
151   new_para->member.para.next_para = next_para;
152   run_para->member.para.next_para = new_para;
153   next_para->member.para.prev_para = new_para;
154
155   /* insert end run of the old paragraph, and new paragraph, into DI double linked list */
156   ME_InsertBefore(run, new_para);
157   ME_InsertBefore(new_para, end_run);
158
159   /* force rewrap of the */
160   run_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP;
161   new_para->member.para.prev_para->member.para.nFlags |= MEPF_REWRAP;
162   
163   /* we've added the end run, so we need to modify nCharOfs in the next paragraphs */
164   ME_PropagateCharOffset(next_para, end_len);
165   editor->nParagraphs++;
166   
167   return new_para;
168 }
169
170 /* join tp with tp->member.para.next_para, keeping tp's style; this
171  * is consistent with the original */
172 ME_DisplayItem *ME_JoinParagraphs(ME_TextEditor *editor, ME_DisplayItem *tp)
173 {
174   ME_DisplayItem *pNext, *pFirstRunInNext, *pRun, *pTmp;
175   int i, shift;
176   ME_UndoItem *undo = NULL;
177   int end_len = (editor->bEmulateVersion10 ? 2 : 1);
178
179   assert(tp->type == diParagraph);
180   assert(tp->member.para.next_para);
181   assert(tp->member.para.next_para->type == diParagraph);
182   
183   pNext = tp->member.para.next_para;
184   
185   {
186     /* null char format operation to store the original char format for the ENDPARA run */
187     CHARFORMAT2W fmt;
188     ME_InitCharFormat2W(&fmt);
189     ME_SetCharFormat(editor, pNext->member.para.nCharOfs - end_len, end_len, &fmt);
190   }
191   undo = ME_AddUndoItem(editor, diUndoSplitParagraph, NULL);
192   if (undo)
193   {
194     undo->nStart = pNext->member.para.nCharOfs - end_len;
195     assert(pNext->member.para.pFmt->cbSize == sizeof(PARAFORMAT2));
196     CopyMemory(undo->di.member.para.pFmt, pNext->member.para.pFmt, sizeof(PARAFORMAT2));
197   }
198   
199   shift = pNext->member.para.nCharOfs - tp->member.para.nCharOfs - end_len;
200   
201   pRun = ME_FindItemBack(pNext, diRunOrParagraph);
202   pFirstRunInNext = ME_FindItemFwd(pNext, diRunOrParagraph);
203   
204   assert(pRun);
205   assert(pRun->type == diRun);
206   assert(pRun->member.run.nFlags & MERF_ENDPARA);
207   assert(pFirstRunInNext->type == diRun);
208   
209   /* if some cursor points at end of paragraph, make it point to the first
210      run of the next joined paragraph */
211   for (i=0; i<editor->nCursors; i++) {
212     if (editor->pCursors[i].pRun == pRun) {
213       editor->pCursors[i].pRun = pFirstRunInNext;
214       editor->pCursors[i].nOffset = 0;
215     }
216   }
217
218   pTmp = pNext;
219   do {
220     pTmp = ME_FindItemFwd(pTmp, diRunOrParagraphOrEnd);
221     if (pTmp->type != diRun)
222       break;
223     TRACE("shifting \"%s\" by %d (previous %d)\n", debugstr_w(pTmp->member.run.strText->szData), shift, pTmp->member.run.nCharOfs);
224     pTmp->member.run.nCharOfs += shift;
225   } while(1);
226   
227   ME_Remove(pRun);
228   ME_DestroyDisplayItem(pRun);
229
230   tp->member.para.next_para = pNext->member.para.next_para;
231   pNext->member.para.next_para->member.para.prev_para = tp;
232   ME_Remove(pNext);
233   ME_DestroyDisplayItem(pNext);
234
235   ME_PropagateCharOffset(tp->member.para.next_para, -end_len);
236   
237   ME_CheckCharOffsets(editor);
238   
239   editor->nParagraphs--;
240   tp->member.para.nFlags |= MEPF_REWRAP;
241   return tp;
242 }
243
244 ME_DisplayItem *ME_GetParagraph(ME_DisplayItem *item) {
245   return ME_FindItemBackOrHere(item, diParagraph);
246 }
247
248 static void ME_DumpStyleEffect(char **p, const char *name, PARAFORMAT2 *fmt, int mask)
249 {
250   *p += sprintf(*p, "%-22s%s\n", name, (fmt->dwMask & mask) ? ((fmt->wEffects & mask) ? "yes" : "no") : "N/A");
251 }
252
253 void ME_DumpParaStyleToBuf(PARAFORMAT2 *pFmt, char buf[2048])
254 {
255   /* FIXME only PARAFORMAT styles implemented */
256   char *p;
257   p = buf;
258   p += sprintf(p, "Alignment:            %s\n",
259     !(pFmt->dwMask & PFM_ALIGNMENT) ? "N/A" :
260       ((pFmt->wAlignment == PFA_LEFT) ? "left" :
261         ((pFmt->wAlignment == PFA_RIGHT) ? "right" :
262           ((pFmt->wAlignment == PFA_CENTER) ? "center" :
263             /*((pFmt->wAlignment == PFA_JUSTIFY) ? "justify" : "incorrect")*/
264             "incorrect"))));
265
266   if (pFmt->dwMask & PFM_OFFSET)
267     p += sprintf(p, "Offset:               %d\n", (int)pFmt->dxOffset);
268   else
269     p += sprintf(p, "Offset:               N/A\n");
270     
271   if (pFmt->dwMask & PFM_OFFSETINDENT)
272     p += sprintf(p, "Offset indent:        %d\n", (int)pFmt->dxStartIndent);
273   else
274     p += sprintf(p, "Offset indent:        N/A\n");
275     
276   if (pFmt->dwMask & PFM_STARTINDENT)
277     p += sprintf(p, "Start indent:         %d\n", (int)pFmt->dxStartIndent);
278   else
279     p += sprintf(p, "Start indent:         N/A\n");
280     
281   if (pFmt->dwMask & PFM_RIGHTINDENT)
282     p += sprintf(p, "Right indent:         %d\n", (int)pFmt->dxRightIndent);
283   else
284     p += sprintf(p, "Right indent:         N/A\n");
285     
286   ME_DumpStyleEffect(&p, "Page break before:", pFmt, PFM_PAGEBREAKBEFORE);
287 }
288
289 void ME_SetParaFormat(ME_TextEditor *editor, ME_DisplayItem *para, PARAFORMAT2 *pFmt)
290 {
291   PARAFORMAT2 copy;
292   assert(sizeof(*para->member.para.pFmt) == sizeof(PARAFORMAT2));
293   ME_AddUndoItem(editor, diUndoSetParagraphFormat, para);
294   
295   CopyMemory(&copy, para->member.para.pFmt, sizeof(PARAFORMAT2));
296
297   if (pFmt->dwMask & PFM_ALIGNMENT)
298     para->member.para.pFmt->wAlignment = pFmt->wAlignment;
299   if (pFmt->dwMask & PFM_STARTINDENT)
300     para->member.para.pFmt->dxStartIndent = pFmt->dxStartIndent;
301   if (pFmt->dwMask & PFM_OFFSET)
302     para->member.para.pFmt->dxOffset = pFmt->dxOffset;
303   if (pFmt->dwMask & PFM_OFFSETINDENT)
304     para->member.para.pFmt->dxStartIndent += pFmt->dxStartIndent;
305     
306   if (pFmt->dwMask & PFM_TABSTOPS)
307   {
308     para->member.para.pFmt->cTabCount = pFmt->cTabCount;
309     memcpy(para->member.para.pFmt->rgxTabs, pFmt->rgxTabs, pFmt->cTabCount*sizeof(int));
310   }
311     
312   /* FIXME to be continued (indents, bulleting and such) */
313
314   if (memcmp(&copy, para->member.para.pFmt, sizeof(PARAFORMAT2)))
315     para->member.para.nFlags |= MEPF_REWRAP;
316 }
317
318
319 void
320 ME_GetSelectionParas(ME_TextEditor *editor, ME_DisplayItem **para, ME_DisplayItem **para_end)
321 {
322   ME_Cursor *pEndCursor = &editor->pCursors[1];
323   
324   *para = ME_GetParagraph(editor->pCursors[0].pRun);
325   *para_end = ME_GetParagraph(editor->pCursors[1].pRun);
326   if ((*para_end)->member.para.nCharOfs < (*para)->member.para.nCharOfs) {
327     ME_DisplayItem *tmp = *para;
328
329     *para = *para_end;
330     *para_end = tmp;
331     pEndCursor = &editor->pCursors[0];
332   }
333   
334   /* selection consists of chars from nFrom up to nTo-1 */
335   if ((*para_end)->member.para.nCharOfs > (*para)->member.para.nCharOfs) {
336     if (!pEndCursor->nOffset) {
337       *para_end = ME_GetParagraph(ME_FindItemBack(pEndCursor->pRun, diRun));
338     }
339   }
340 }
341
342
343 void ME_SetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
344 {
345   ME_DisplayItem *para, *para_end;
346   
347   ME_GetSelectionParas(editor, &para, &para_end);
348  
349   do {
350     ME_SetParaFormat(editor, para, pFmt);
351     if (para == para_end)
352       break;
353     para = para->member.para.next_para;
354   } while(1);
355 }
356
357 void ME_GetParaFormat(ME_TextEditor *editor, ME_DisplayItem *para, PARAFORMAT2 *pFmt)
358 {
359   if (pFmt->cbSize >= sizeof(PARAFORMAT2))
360   {
361     CopyMemory(pFmt, para->member.para.pFmt, sizeof(PARAFORMAT2));
362     return;
363   }
364   CopyMemory(pFmt, para->member.para.pFmt, pFmt->cbSize);  
365 }
366
367 void ME_GetSelectionParaFormat(ME_TextEditor *editor, PARAFORMAT2 *pFmt)
368 {
369   ME_DisplayItem *para, *para_end;
370   PARAFORMAT2 tmp;
371   
372   ME_GetSelectionParas(editor, &para, &para_end);
373   
374   ME_GetParaFormat(editor, para, pFmt);
375   if (para == para_end) return;
376   
377   do {
378     ZeroMemory(&tmp, sizeof(tmp));
379     tmp.cbSize = sizeof(tmp);
380     ME_GetParaFormat(editor, para, &tmp);
381     
382     assert(tmp.dwMask & PFM_ALIGNMENT);    
383     if (pFmt->wAlignment != tmp.wAlignment)
384       pFmt->dwMask &= ~PFM_ALIGNMENT;
385     
386     assert(tmp.dwMask & PFM_STARTINDENT);
387     if (pFmt->dxStartIndent != tmp.dxStartIndent)
388       pFmt->dwMask &= ~PFM_STARTINDENT;
389     
390     assert(tmp.dwMask & PFM_OFFSET);
391     if (pFmt->dxOffset != tmp.dxOffset)
392       pFmt->dwMask &= ~PFM_OFFSET;
393     
394     assert(tmp.dwMask & PFM_TABSTOPS);    
395     if (pFmt->dwMask & PFM_TABSTOPS) {
396       if (pFmt->cTabCount != tmp.cTabCount)
397         pFmt->dwMask &= ~PFM_TABSTOPS;
398       else
399       if (memcmp(pFmt->rgxTabs, tmp.rgxTabs, tmp.cTabCount*sizeof(int)))
400         pFmt->dwMask &= ~PFM_TABSTOPS;
401     }
402     
403     if (para == para_end)
404       return;
405     para = para->member.para.next_para;
406   } while(1);
407 }