xref: /reactos/dll/win32/riched20/table.c (revision 9c5efed7)
1 /*
2  * RichEdit functions dealing with on tables
3  *
4  * Copyright 2008 by Dylan Smith
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20 
21 /*
22  * The implementation of tables differs greatly between version 3.0
23  * (in riched20.dll) and version 4.1 (in msftedit.dll) of richedit controls.
24  * Currently Wine is not distinguishing between version 3.0 and version 4.1,
25  * so v4.1 is assumed unless v1.0 is being emulated (i.e. riched32.dll is used).
26  * If this lack of distinction causes a bug in a Windows application, then Wine
27  * will need to start making this distinction.
28  *
29  * Richedit version 1.0 - 3.0:
30  *   Tables are implemented in these versions using tabs at the end of cells,
31  *   and tab stops to position the cells.  The paragraph format flag PFE_TABLE
32  *   will indicate that the paragraph is a table row.  Note that in this
33  *   implementation there is one paragraph per table row.
34  *
35  * Richedit version 4.1:
36  *   Tables are implemented such that cells can contain multiple paragraphs,
37  *   each with its own paragraph format, and cells may even contain tables
38  *   nested within the cell.
39  *
40  *   There is also a paragraph at the start of each table row that contains
41  *   the rows paragraph format (e.g. to change the row alignment to row), and a
42  *   paragraph at the end of the table row with the PFE_TABLEROWDELIMITER flag
43  *   set. The paragraphs at the start and end of the table row should always be
44  *   empty, but should have a length of 2.
45  *
46  *   Wine implements this using display items (ME_DisplayItem) with a type of
47  *   diCell.  These cell display items store the cell properties, and are
48  *   inserted into the editors linked list before each cell, and at the end of
49  *   the last cell. The cell display item for a cell comes before the paragraphs
50  *   for the cell, but the last cell display item refers to no cell, so it is
51  *   just a delimiter.
52  */
53 
54 #include "editor.h"
55 #include "rtf.h"
56 
57 static ME_Paragraph* table_insert_end_para( ME_TextEditor *editor, ME_Cursor *cursor,
58                                             const WCHAR *eol_str, int eol_len, int para_flags )
59 {
60     ME_Style *style = style_get_insert_style( editor, cursor );
61     ME_Paragraph *para;
62 
63     if (cursor->nOffset) run_split( editor, cursor );
64 
65     para = para_split( editor, cursor->run, style, eol_str, eol_len, para_flags );
66     ME_ReleaseStyle( style );
67     cursor->para = para;
68     cursor->run = para_first_run( para );
69     return para;
70 }
71 
72 ME_Paragraph* table_insert_row_start( ME_TextEditor *editor, ME_Cursor *cursor )
73 {
74     ME_Paragraph *para;
75 
76     para = table_insert_end_para( editor, cursor, L"\r\n", 2, MEPF_ROWSTART );
77     return para_prev( para );
78 }
79 
80 ME_Paragraph* table_insert_row_start_at_para( ME_TextEditor *editor, ME_Paragraph *para )
81 {
82     ME_Paragraph *prev_para, *end_para, *start_row;
83     ME_Cursor cursor;
84 
85     cursor.para = para;
86     cursor.run = para_first_run( para );
87     cursor.nOffset = 0;
88 
89     start_row = table_insert_row_start( editor, &cursor );
90 
91     end_para = para_next( editor->pCursors[0].para );
92     prev_para = para_next( start_row );
93     para = para_next( prev_para );
94 
95     while (para != end_para)
96     {
97         para->cell = para_cell( prev_para );
98         para->nFlags |= MEPF_CELL;
99         para->nFlags &= ~(MEPF_ROWSTART | MEPF_ROWEND);
100         para->fmt.dwMask |= PFM_TABLE | PFM_TABLEROWDELIMITER;
101         para->fmt.wEffects |= PFE_TABLE;
102         para->fmt.wEffects &= ~PFE_TABLEROWDELIMITER;
103         prev_para = para;
104         para = para_next( para );
105     }
106     return start_row;
107 }
108 
109 /* Inserts a diCell and starts a new paragraph for the next cell.
110  *
111  * Returns the first paragraph of the new cell. */
112 ME_Paragraph* table_insert_cell( ME_TextEditor *editor, ME_Cursor *cursor )
113 {
114     WCHAR tab = '\t';
115 
116     return table_insert_end_para( editor, editor->pCursors, &tab, 1, MEPF_CELL );
117 }
118 
119 ME_Paragraph* table_insert_row_end( ME_TextEditor *editor, ME_Cursor *cursor )
120 {
121     ME_Paragraph *para;
122 
123     para = table_insert_end_para( editor, cursor, L"\r\n", 2, MEPF_ROWEND );
124     return para_prev( para );
125 }
126 
127 ME_Paragraph* table_row_end( ME_Paragraph *para )
128 {
129   ME_Cell *cell;
130 
131   if (para->nFlags & MEPF_ROWEND) return para;
132   if (para->nFlags & MEPF_ROWSTART) para = para_next( para );
133   cell = para_cell( para );
134   while (cell_next( cell ))
135     cell = cell_next( cell );
136 
137   para = &ME_FindItemFwd( cell_get_di( cell ), diParagraph )->member.para;
138   assert( para && para->nFlags & MEPF_ROWEND );
139   return para;
140 }
141 
142 ME_Paragraph* table_row_start( ME_Paragraph *para )
143 {
144   ME_Cell *cell;
145 
146   if (para->nFlags & MEPF_ROWSTART) return para;
147   if (para->nFlags & MEPF_ROWEND) para = para_prev( para );
148   cell = para_cell( para );
149 
150   while (cell_prev( cell ))
151     cell = cell_prev( cell );
152 
153   para = &ME_FindItemBack( cell_get_di( cell ), diParagraph )->member.para;
154   assert( para && para->nFlags & MEPF_ROWSTART );
155   return para;
156 }
157 
158 ME_Paragraph* table_outer_para( ME_Paragraph *para )
159 {
160   if (para->nFlags & MEPF_ROWEND) para = para_prev( para );
161   while (para_cell( para ))
162   {
163     para = table_row_start( para );
164     if (!para_cell( para )) break;
165     para = &ME_FindItemBack( cell_get_di( para_cell( para ) ), diParagraph )->member.para;
166   }
167   return para;
168 }
169 
170 ME_Cell *table_row_first_cell( ME_Paragraph *para )
171 {
172     if (!para_in_table( para )) return NULL;
173 
174     para = para_next( table_row_start( para ) );
175     return para_cell( para );
176 }
177 
178 ME_Cell *table_row_end_cell( ME_Paragraph *para )
179 {
180     if (!para_in_table( para )) return NULL;
181 
182     para = para_prev( table_row_end( para ));
183     return cell_next( para_cell( para ) );
184 }
185 
186 ME_Cell *cell_create( void )
187 {
188     ME_DisplayItem *item = ME_MakeDI( diCell );
189     return &item->member.cell;
190 }
191 
192 ME_Cell *cell_next( ME_Cell *cell )
193 {
194     return cell->next_cell;
195 }
196 
197 ME_Cell *cell_prev( ME_Cell *cell )
198 {
199     return cell->prev_cell;
200 }
201 
202 ME_Paragraph *cell_first_para( ME_Cell *cell )
203 {
204     return &ME_FindItemFwd( cell_get_di( cell ), diParagraph )->member.para;
205 }
206 
207 ME_Paragraph *cell_end_para( ME_Cell *cell )
208 {
209     ME_Cell *next = cell_next( cell );
210 
211     if (!next) return cell_first_para( cell ); /* End of row */
212 
213     return &ME_FindItemBack( cell_get_di( next ), diParagraph )->member.para;
214 }
215 
216 /* Table rows should either be deleted completely or not at all. */
217 void table_protect_partial_deletion( ME_TextEditor *editor, ME_Cursor *c, int *num_chars )
218 {
219   int start_ofs = ME_GetCursorOfs( c );
220   ME_Cursor c2 = *c;
221   ME_Paragraph *this_para = c->para, *end_para;
222 
223   ME_MoveCursorChars( editor, &c2, *num_chars, FALSE );
224   end_para = c2.para;
225   if (c2.run->nFlags & MERF_ENDPARA)
226   {
227     /* End offset might be in the middle of the end paragraph run.
228      * If this is the case, then we need to use the next paragraph as the last
229      * paragraphs.
230      */
231     int remaining = start_ofs + *num_chars - c2.run->nCharOfs - end_para->nCharOfs;
232     if (remaining)
233     {
234       assert( remaining < c2.run->len );
235       end_para = para_next( end_para );
236     }
237   }
238   if (!editor->bEmulateVersion10) /* v4.1 */
239   {
240     if (para_cell( this_para ) != para_cell( end_para ) ||
241         ((this_para->nFlags | end_para->nFlags) & (MEPF_ROWSTART | MEPF_ROWEND)))
242     {
243       while (this_para != end_para)
244       {
245         ME_Paragraph *next_para = para_next( this_para );
246         BOOL truancate_del = FALSE;
247         if (this_para->nFlags & MEPF_ROWSTART)
248         {
249           /* The following while loop assumes that next_para is MEPF_ROWSTART,
250            * so moving back one paragraph lets it be processed as the start
251            * of the row. */
252           next_para = this_para;
253           this_para = para_prev( this_para );
254         }
255         else if (para_cell( next_para) != para_cell( this_para ) || this_para->nFlags & MEPF_ROWEND)
256         {
257           /* Start of the deletion from after the start of the table row. */
258           truancate_del = TRUE;
259         }
260         while (!truancate_del && next_para->nFlags & MEPF_ROWSTART)
261         {
262           next_para = para_next( table_row_end( next_para ) );
263           if (next_para->nCharOfs > start_ofs + *num_chars)
264           {
265             /* End of deletion is not past the end of the table row. */
266             next_para = para_next( this_para );
267             /* Delete the end paragraph preceding the table row if the
268              * preceding table row will be empty. */
269             if (this_para->nCharOfs >= start_ofs) next_para = para_next( next_para );
270             truancate_del = TRUE;
271           }
272           else this_para = para_prev( next_para );
273         }
274         if (truancate_del)
275         {
276           ME_Run *end_run = para_end_run( para_prev( next_para ) );
277           int new_chars = next_para->nCharOfs - start_ofs - end_run->len;
278           new_chars = max( new_chars, 0 );
279           assert( new_chars <= *num_chars);
280           *num_chars = new_chars;
281           break;
282         }
283         this_para = next_para;
284       }
285     }
286   }
287   else /* v1.0 - 3.0 */
288   {
289     ME_Run *run;
290     int chars_to_boundary;
291 
292     if ((this_para->nCharOfs != start_ofs || this_para == end_para) && para_in_table( this_para ))
293     {
294       run = c->run;
295       /* Find the next tab or end paragraph to use as a delete boundary */
296       while (!(run->nFlags & (MERF_TAB | MERF_ENDPARA)))
297         run = run_next( run );
298       chars_to_boundary = run->nCharOfs - c->run->nCharOfs - c->nOffset;
299       *num_chars = min( *num_chars, chars_to_boundary );
300     }
301     else if (para_in_table( end_para ))
302     {
303       /* The deletion starts from before the row, so don't join it with
304        * previous non-empty paragraphs. */
305       ME_Paragraph *cur_para;
306       run = NULL;
307       if (start_ofs > this_para->nCharOfs)
308       {
309           cur_para = para_prev( end_para );
310           run = para_end_run( cur_para );
311       }
312       if (!run)
313       {
314         cur_para = end_para;
315         run = para_first_run( end_para );
316       }
317       if (run)
318       {
319         chars_to_boundary = cur_para->nCharOfs + run->nCharOfs - start_ofs;
320         if (chars_to_boundary >= 0) *num_chars = min( *num_chars, chars_to_boundary );
321       }
322     }
323     if (*num_chars < 0) *num_chars = 0;
324   }
325 }
326 
327 ME_Paragraph* table_append_row( ME_TextEditor *editor, ME_Paragraph *table_row )
328 {
329   WCHAR endl = '\r', tab = '\t';
330   ME_Run *run;
331   int i;
332 
333   if (!editor->bEmulateVersion10) /* v4.1 */
334   {
335     ME_Cell *new_cell, *cell;
336     ME_Paragraph *para, *prev_table_end, *new_row_start;
337 
338     cell = table_row_first_cell( table_row );
339     prev_table_end = table_row_end( table_row );
340     para = para_next( prev_table_end );
341     run = para_first_run( para );
342     editor->pCursors[0].para = para;
343     editor->pCursors[0].run = run;
344     editor->pCursors[0].nOffset = 0;
345     editor->pCursors[1] = editor->pCursors[0];
346     new_row_start = table_insert_row_start( editor, editor->pCursors );
347     new_cell = table_row_first_cell( new_row_start );
348     /* Copy cell properties */
349     new_cell->nRightBoundary = cell->nRightBoundary;
350     new_cell->border = cell->border;
351     while (cell_next( cell ))
352     {
353       cell = cell_next( cell );
354       para = table_insert_cell( editor, editor->pCursors );
355       new_cell = para_cell( para );
356       /* Copy cell properties */
357       new_cell->nRightBoundary = cell->nRightBoundary;
358       new_cell->border = cell->border;
359     };
360     para = table_insert_row_end( editor, editor->pCursors );
361     para->fmt = prev_table_end->fmt;
362     /* return the table row start for the inserted paragraph */
363     return new_row_start;
364   }
365   else /* v1.0 - 3.0 */
366   {
367     run = para_end_run( table_row );
368     assert( para_in_table( table_row ) );
369     editor->pCursors[0].para = table_row;
370     editor->pCursors[0].run = run;
371     editor->pCursors[0].nOffset = 0;
372     editor->pCursors[1] = editor->pCursors[0];
373     ME_InsertTextFromCursor( editor, 0, &endl, 1, run->style );
374     run = editor->pCursors[0].run;
375     for (i = 0; i < table_row->fmt.cTabCount; i++)
376       ME_InsertTextFromCursor( editor, 0, &tab, 1, run->style );
377 
378     return para_next( table_row );
379   }
380 }
381 
382 /* Selects the next table cell or appends a new table row if at end of table */
383 static void table_select_next_cell_or_append( ME_TextEditor *editor, ME_Run *run )
384 {
385   ME_Paragraph *para = run->para;
386   ME_Cell *cell;
387   int i;
388 
389   assert( para_in_table( para ) );
390   if (!editor->bEmulateVersion10) /* v4.1 */
391   {
392     /* Get the initial cell */
393     if (para->nFlags & MEPF_ROWSTART) cell = para_cell( para_next( para ) );
394     else if (para->nFlags & MEPF_ROWEND) cell = para_cell( para_prev( para ) );
395     else cell = para_cell( para );
396 
397     /* Get the next cell. */
398     if (cell_next( cell ) && cell_next( cell_next( cell ) ))
399         cell = cell_next( cell );
400     else
401     {
402       para = para_next( table_row_end( para ) );
403       if (para->nFlags & MEPF_ROWSTART) cell = para_cell( para_next( para ) );
404       else
405       {
406         /* Insert row */
407         para = para_prev( para );
408         para = table_append_row( editor, table_row_start( para ) );
409         /* Put cursor at the start of the new table row */
410         para = para_next( para );
411         editor->pCursors[0].para = para;
412         editor->pCursors[0].run = para_first_run( para );
413         editor->pCursors[0].nOffset = 0;
414         editor->pCursors[1] = editor->pCursors[0];
415         ME_WrapMarkedParagraphs(editor);
416         return;
417       }
418     }
419     /* Select cell */
420     editor->pCursors[1].para = cell_first_para( cell );
421     editor->pCursors[1].run = para_first_run( editor->pCursors[1].para );
422     editor->pCursors[1].nOffset = 0;
423     editor->pCursors[0].para = cell_end_para( cell );
424     editor->pCursors[0].run = para_end_run( editor->pCursors[0].para );
425     editor->pCursors[0].nOffset = 0;
426   }
427   else /* v1.0 - 3.0 */
428   {
429     if (run->nFlags & MERF_ENDPARA && para_in_table( para_next( para ) ))
430     {
431       run = run_next_all_paras( run );
432       assert(run);
433     }
434     for (i = 0; i < 2; i++)
435     {
436       while (!(run->nFlags & MERF_TAB))
437       {
438         if (!run_next( run ))
439         {
440           para = para_next( run->para );
441           if (para_in_table( para ))
442           {
443             run = para_first_run( para );
444             editor->pCursors[0].para = para;
445             editor->pCursors[0].run = run;
446             editor->pCursors[0].nOffset = 0;
447             i = 1;
448           }
449           else
450           {
451             /* Insert table row */
452             para = table_append_row( editor, para_prev( para ) );
453             /* Put cursor at the start of the new table row */
454             editor->pCursors[0].para = para;
455             editor->pCursors[0].run = para_first_run( para );
456             editor->pCursors[0].nOffset = 0;
457             editor->pCursors[1] = editor->pCursors[0];
458             ME_WrapMarkedParagraphs(editor);
459             return;
460           }
461         }
462         else run = run_next( run );
463       }
464       if (i == 0) run = run_next_all_paras( run );
465       editor->pCursors[i].run = run;
466       editor->pCursors[i].para = run->para;
467       editor->pCursors[i].nOffset = 0;
468     }
469   }
470 }
471 
472 
473 void table_handle_tab( ME_TextEditor *editor, BOOL selected_row )
474 {
475   /* FIXME: Shift tab should move to the previous cell. */
476   ME_Cursor fromCursor, toCursor;
477   ME_InvalidateSelection(editor);
478   {
479     int from, to;
480     from = ME_GetCursorOfs(&editor->pCursors[0]);
481     to = ME_GetCursorOfs(&editor->pCursors[1]);
482     if (from <= to)
483     {
484       fromCursor = editor->pCursors[0];
485       toCursor = editor->pCursors[1];
486     }
487     else
488     {
489       fromCursor = editor->pCursors[1];
490       toCursor = editor->pCursors[0];
491     }
492   }
493   if (!editor->bEmulateVersion10) /* v4.1 */
494   {
495     if (!para_in_table( toCursor.para ))
496     {
497       editor->pCursors[0] = toCursor;
498       editor->pCursors[1] = toCursor;
499     }
500     else table_select_next_cell_or_append( editor, toCursor.run );
501   }
502   else /* v1.0 - 3.0 */
503   {
504     if (!para_in_table( fromCursor.para ))
505     {
506       editor->pCursors[0] = fromCursor;
507       editor->pCursors[1] = fromCursor;
508       /* FIXME: For some reason the caret is shown at the start of the
509        *        previous paragraph in v1.0 to v3.0 */
510     }
511     else if ((selected_row || !para_in_table( toCursor.para )))
512       table_select_next_cell_or_append( editor, fromCursor.run );
513     else
514     {
515       ME_Run *run = run_prev( toCursor.run );
516 
517       if (ME_IsSelection(editor) && !toCursor.nOffset && run && run->nFlags & MERF_TAB)
518         table_select_next_cell_or_append( editor, run );
519       else
520         table_select_next_cell_or_append( editor, toCursor.run );
521     }
522   }
523   ME_InvalidateSelection(editor);
524   ME_Repaint(editor);
525   update_caret(editor);
526   ME_SendSelChange(editor);
527 }
528 
529 /* Make sure the cursor is not in the hidden table row start paragraph
530  * without a selection. */
531 void table_move_from_row_start( ME_TextEditor *editor )
532 {
533   ME_Paragraph *para = editor->pCursors[0].para;
534 
535   if (para == editor->pCursors[1].para && para->nFlags & MEPF_ROWSTART)
536   {
537     /* The cursors should not be at the hidden start row paragraph without
538      * a selection, so the cursor is moved into the first cell. */
539     para = para_next( para );
540     editor->pCursors[0].para = para;
541     editor->pCursors[0].run = para_first_run( para );
542     editor->pCursors[0].nOffset = 0;
543     editor->pCursors[1] = editor->pCursors[0];
544   }
545 }
546 
547 struct RTFTable *ME_MakeTableDef(ME_TextEditor *editor)
548 {
549   RTFTable *tableDef = heap_alloc_zero(sizeof(*tableDef));
550 
551   if (!editor->bEmulateVersion10) /* v4.1 */
552     tableDef->gapH = 10;
553   return tableDef;
554 }
555 
556 void ME_InitTableDef(ME_TextEditor *editor, struct RTFTable *tableDef)
557 {
558   ZeroMemory(tableDef->cells, sizeof(tableDef->cells));
559   ZeroMemory(tableDef->border, sizeof(tableDef->border));
560   tableDef->numCellsDefined = 0;
561   tableDef->leftEdge = 0;
562   if (!editor->bEmulateVersion10) /* v4.1 */
563     tableDef->gapH = 10;
564   else /* v1.0 - 3.0 */
565     tableDef->gapH = 0;
566 }
567