2 * RichEdit functions dealing with on tables
4 * Copyright 2008 by Dylan Smith
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.
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.
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
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.
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.
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.
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.
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
57 static const WCHAR cr_lf
[] = {'\r', '\n', 0};
59 static ME_Paragraph
* table_insert_end_para( ME_TextEditor
*editor
, ME_Cursor
*cursor
,
60 const WCHAR
*eol_str
, int eol_len
, int para_flags
)
62 ME_Style
*style
= style_get_insert_style( editor
, cursor
);
65 if (cursor
->nOffset
) run_split( editor
, cursor
);
67 para
= para_split( editor
, cursor
->run
, style
, eol_str
, eol_len
, para_flags
);
68 ME_ReleaseStyle( style
);
70 cursor
->run
= para_first_run( para
);
74 ME_Paragraph
* table_insert_row_start( ME_TextEditor
*editor
, ME_Cursor
*cursor
)
78 para
= table_insert_end_para( editor
, cursor
, cr_lf
, 2, MEPF_ROWSTART
);
79 return para_prev( para
);
82 ME_Paragraph
* table_insert_row_start_at_para( ME_TextEditor
*editor
, ME_Paragraph
*para
)
84 ME_Paragraph
*prev_para
, *end_para
, *start_row
;
88 cursor
.run
= para_first_run( para
);
91 start_row
= table_insert_row_start( editor
, &cursor
);
93 end_para
= para_next( editor
->pCursors
[0].para
);
94 prev_para
= para_next( start_row
);
95 para
= para_next( prev_para
);
97 while (para
!= end_para
)
99 para
->cell
= para_cell( prev_para
);
100 para
->nFlags
|= MEPF_CELL
;
101 para
->nFlags
&= ~(MEPF_ROWSTART
| MEPF_ROWEND
);
102 para
->fmt
.dwMask
|= PFM_TABLE
| PFM_TABLEROWDELIMITER
;
103 para
->fmt
.wEffects
|= PFE_TABLE
;
104 para
->fmt
.wEffects
&= ~PFE_TABLEROWDELIMITER
;
106 para
= para_next( para
);
111 /* Inserts a diCell and starts a new paragraph for the next cell.
113 * Returns the first paragraph of the new cell. */
114 ME_Paragraph
* table_insert_cell( ME_TextEditor
*editor
, ME_Cursor
*cursor
)
118 return table_insert_end_para( editor
, editor
->pCursors
, &tab
, 1, MEPF_CELL
);
121 ME_Paragraph
* table_insert_row_end( ME_TextEditor
*editor
, ME_Cursor
*cursor
)
125 para
= table_insert_end_para( editor
, cursor
, cr_lf
, 2, MEPF_ROWEND
);
126 return para_prev( para
);
129 ME_Paragraph
* table_row_end( ME_Paragraph
*para
)
133 if (para
->nFlags
& MEPF_ROWEND
) return para
;
134 if (para
->nFlags
& MEPF_ROWSTART
) para
= para_next( para
);
135 cell
= para_cell( para
);
136 while (cell_next( cell
))
137 cell
= cell_next( cell
);
139 para
= &ME_FindItemFwd( cell_get_di( cell
), diParagraph
)->member
.para
;
140 assert( para
&& para
->nFlags
& MEPF_ROWEND
);
144 ME_Paragraph
* table_row_start( ME_Paragraph
*para
)
148 if (para
->nFlags
& MEPF_ROWSTART
) return para
;
149 if (para
->nFlags
& MEPF_ROWEND
) para
= para_prev( para
);
150 cell
= para_cell( para
);
152 while (cell_prev( cell
))
153 cell
= cell_prev( cell
);
155 para
= &ME_FindItemBack( cell_get_di( cell
), diParagraph
)->member
.para
;
156 assert( para
&& para
->nFlags
& MEPF_ROWSTART
);
160 ME_Paragraph
* table_outer_para( ME_Paragraph
*para
)
162 if (para
->nFlags
& MEPF_ROWEND
) para
= para_prev( para
);
163 while (para_cell( para
))
165 para
= table_row_start( para
);
166 if (!para_cell( para
)) break;
167 para
= &ME_FindItemBack( cell_get_di( para_cell( para
) ), diParagraph
)->member
.para
;
172 ME_Cell
*table_row_first_cell( ME_Paragraph
*para
)
174 if (!para_in_table( para
)) return NULL
;
176 para
= para_next( table_row_start( para
) );
177 return para_cell( para
);
180 ME_Cell
*table_row_end_cell( ME_Paragraph
*para
)
182 if (!para_in_table( para
)) return NULL
;
184 para
= para_prev( table_row_end( para
));
185 return cell_next( para_cell( para
) );
188 ME_Cell
*cell_create( void )
190 ME_DisplayItem
*item
= ME_MakeDI( diCell
);
191 return &item
->member
.cell
;
194 ME_Cell
*cell_next( ME_Cell
*cell
)
196 return cell
->next_cell
;
199 ME_Cell
*cell_prev( ME_Cell
*cell
)
201 return cell
->prev_cell
;
204 ME_Paragraph
*cell_first_para( ME_Cell
*cell
)
206 return &ME_FindItemFwd( cell_get_di( cell
), diParagraph
)->member
.para
;
209 ME_Paragraph
*cell_end_para( ME_Cell
*cell
)
211 ME_Cell
*next
= cell_next( cell
);
213 if (!next
) return cell_first_para( cell
); /* End of row */
215 return &ME_FindItemBack( cell_get_di( next
), diParagraph
)->member
.para
;
218 /* Table rows should either be deleted completely or not at all. */
219 void table_protect_partial_deletion( ME_TextEditor
*editor
, ME_Cursor
*c
, int *num_chars
)
221 int start_ofs
= ME_GetCursorOfs( c
);
223 ME_Paragraph
*this_para
= c
->para
, *end_para
;
225 ME_MoveCursorChars( editor
, &c2
, *num_chars
, FALSE
);
227 if (c2
.run
->nFlags
& MERF_ENDPARA
)
229 /* End offset might be in the middle of the end paragraph run.
230 * If this is the case, then we need to use the next paragraph as the last
233 int remaining
= start_ofs
+ *num_chars
- c2
.run
->nCharOfs
- end_para
->nCharOfs
;
236 assert( remaining
< c2
.run
->len
);
237 end_para
= para_next( end_para
);
240 if (!editor
->bEmulateVersion10
) /* v4.1 */
242 if (para_cell( this_para
) != para_cell( end_para
) ||
243 ((this_para
->nFlags
| end_para
->nFlags
) & (MEPF_ROWSTART
| MEPF_ROWEND
)))
245 while (this_para
!= end_para
)
247 ME_Paragraph
*next_para
= para_next( this_para
);
248 BOOL truancate_del
= FALSE
;
249 if (this_para
->nFlags
& MEPF_ROWSTART
)
251 /* The following while loop assumes that next_para is MEPF_ROWSTART,
252 * so moving back one paragraph lets it be processed as the start
254 next_para
= this_para
;
255 this_para
= para_prev( this_para
);
257 else if (para_cell( next_para
) != para_cell( this_para
) || this_para
->nFlags
& MEPF_ROWEND
)
259 /* Start of the deletion from after the start of the table row. */
260 truancate_del
= TRUE
;
262 while (!truancate_del
&& next_para
->nFlags
& MEPF_ROWSTART
)
264 next_para
= para_next( table_row_end( next_para
) );
265 if (next_para
->nCharOfs
> start_ofs
+ *num_chars
)
267 /* End of deletion is not past the end of the table row. */
268 next_para
= para_next( this_para
);
269 /* Delete the end paragraph preceding the table row if the
270 * preceding table row will be empty. */
271 if (this_para
->nCharOfs
>= start_ofs
) next_para
= para_next( next_para
);
272 truancate_del
= TRUE
;
274 else this_para
= para_prev( next_para
);
278 ME_Run
*end_run
= para_end_run( para_prev( next_para
) );
279 int new_chars
= next_para
->nCharOfs
- start_ofs
- end_run
->len
;
280 new_chars
= max( new_chars
, 0 );
281 assert( new_chars
<= *num_chars
);
282 *num_chars
= new_chars
;
285 this_para
= next_para
;
289 else /* v1.0 - 3.0 */
292 int chars_to_boundary
;
294 if ((this_para
->nCharOfs
!= start_ofs
|| this_para
== end_para
) && para_in_table( this_para
))
297 /* Find the next tab or end paragraph to use as a delete boundary */
298 while (!(run
->nFlags
& (MERF_TAB
| MERF_ENDPARA
)))
299 run
= run_next( run
);
300 chars_to_boundary
= run
->nCharOfs
- c
->run
->nCharOfs
- c
->nOffset
;
301 *num_chars
= min( *num_chars
, chars_to_boundary
);
303 else if (para_in_table( end_para
))
305 /* The deletion starts from before the row, so don't join it with
306 * previous non-empty paragraphs. */
307 ME_Paragraph
*cur_para
;
309 if (start_ofs
> this_para
->nCharOfs
)
311 cur_para
= para_prev( end_para
);
312 run
= para_end_run( cur_para
);
317 run
= para_first_run( end_para
);
321 chars_to_boundary
= cur_para
->nCharOfs
+ run
->nCharOfs
- start_ofs
;
322 if (chars_to_boundary
>= 0) *num_chars
= min( *num_chars
, chars_to_boundary
);
325 if (*num_chars
< 0) *num_chars
= 0;
329 ME_Paragraph
* table_append_row( ME_TextEditor
*editor
, ME_Paragraph
*table_row
)
331 WCHAR endl
= '\r', tab
= '\t';
335 if (!editor
->bEmulateVersion10
) /* v4.1 */
337 ME_Cell
*new_cell
, *cell
;
338 ME_Paragraph
*para
, *prev_table_end
, *new_row_start
;
340 cell
= table_row_first_cell( table_row
);
341 prev_table_end
= table_row_end( table_row
);
342 para
= para_next( prev_table_end
);
343 run
= para_first_run( para
);
344 editor
->pCursors
[0].para
= para
;
345 editor
->pCursors
[0].run
= run
;
346 editor
->pCursors
[0].nOffset
= 0;
347 editor
->pCursors
[1] = editor
->pCursors
[0];
348 new_row_start
= table_insert_row_start( editor
, editor
->pCursors
);
349 new_cell
= table_row_first_cell( new_row_start
);
350 /* Copy cell properties */
351 new_cell
->nRightBoundary
= cell
->nRightBoundary
;
352 new_cell
->border
= cell
->border
;
353 while (cell_next( cell
))
355 cell
= cell_next( cell
);
356 para
= table_insert_cell( editor
, editor
->pCursors
);
357 new_cell
= para_cell( para
);
358 /* Copy cell properties */
359 new_cell
->nRightBoundary
= cell
->nRightBoundary
;
360 new_cell
->border
= cell
->border
;
362 para
= table_insert_row_end( editor
, editor
->pCursors
);
363 para
->fmt
= prev_table_end
->fmt
;
364 /* return the table row start for the inserted paragraph */
365 return new_row_start
;
367 else /* v1.0 - 3.0 */
369 run
= para_end_run( table_row
);
370 assert( para_in_table( table_row
) );
371 editor
->pCursors
[0].para
= table_row
;
372 editor
->pCursors
[0].run
= run
;
373 editor
->pCursors
[0].nOffset
= 0;
374 editor
->pCursors
[1] = editor
->pCursors
[0];
375 ME_InsertTextFromCursor( editor
, 0, &endl
, 1, run
->style
);
376 run
= editor
->pCursors
[0].run
;
377 for (i
= 0; i
< table_row
->fmt
.cTabCount
; i
++)
378 ME_InsertTextFromCursor( editor
, 0, &tab
, 1, run
->style
);
380 return para_next( table_row
);
384 /* Selects the next table cell or appends a new table row if at end of table */
385 static void table_select_next_cell_or_append( ME_TextEditor
*editor
, ME_Run
*run
)
387 ME_Paragraph
*para
= run
->para
;
391 assert( para_in_table( para
) );
392 if (!editor
->bEmulateVersion10
) /* v4.1 */
394 /* Get the initial cell */
395 if (para
->nFlags
& MEPF_ROWSTART
) cell
= para_cell( para_next( para
) );
396 else if (para
->nFlags
& MEPF_ROWEND
) cell
= para_cell( para_prev( para
) );
397 else cell
= para_cell( para
);
399 /* Get the next cell. */
400 if (cell_next( cell
) && cell_next( cell_next( cell
) ))
401 cell
= cell_next( cell
);
404 para
= para_next( table_row_end( para
) );
405 if (para
->nFlags
& MEPF_ROWSTART
) cell
= para_cell( para_next( para
) );
409 para
= para_prev( para
);
410 para
= table_append_row( editor
, table_row_start( para
) );
411 /* Put cursor at the start of the new table row */
412 para
= para_next( para
);
413 editor
->pCursors
[0].para
= para
;
414 editor
->pCursors
[0].run
= para_first_run( para
);
415 editor
->pCursors
[0].nOffset
= 0;
416 editor
->pCursors
[1] = editor
->pCursors
[0];
417 ME_WrapMarkedParagraphs(editor
);
422 editor
->pCursors
[1].para
= cell_first_para( cell
);
423 editor
->pCursors
[1].run
= para_first_run( editor
->pCursors
[1].para
);
424 editor
->pCursors
[1].nOffset
= 0;
425 editor
->pCursors
[0].para
= cell_end_para( cell
);
426 editor
->pCursors
[0].run
= para_end_run( editor
->pCursors
[0].para
);
427 editor
->pCursors
[0].nOffset
= 0;
429 else /* v1.0 - 3.0 */
431 if (run
->nFlags
& MERF_ENDPARA
&& para_in_table( para_next( para
) ))
433 run
= run_next_all_paras( run
);
436 for (i
= 0; i
< 2; i
++)
438 while (!(run
->nFlags
& MERF_TAB
))
440 if (!run_next( run
))
442 para
= para_next( run
->para
);
443 if (para_in_table( para
))
445 run
= para_first_run( para
);
446 editor
->pCursors
[0].para
= para
;
447 editor
->pCursors
[0].run
= run
;
448 editor
->pCursors
[0].nOffset
= 0;
453 /* Insert table row */
454 para
= table_append_row( editor
, para_prev( para
) );
455 /* Put cursor at the start of the new table row */
456 editor
->pCursors
[0].para
= para
;
457 editor
->pCursors
[0].run
= para_first_run( para
);
458 editor
->pCursors
[0].nOffset
= 0;
459 editor
->pCursors
[1] = editor
->pCursors
[0];
460 ME_WrapMarkedParagraphs(editor
);
464 else run
= run_next( run
);
466 if (i
== 0) run
= run_next_all_paras( run
);
467 editor
->pCursors
[i
].run
= run
;
468 editor
->pCursors
[i
].para
= run
->para
;
469 editor
->pCursors
[i
].nOffset
= 0;
475 void table_handle_tab( ME_TextEditor
*editor
, BOOL selected_row
)
477 /* FIXME: Shift tab should move to the previous cell. */
478 ME_Cursor fromCursor
, toCursor
;
479 ME_InvalidateSelection(editor
);
482 from
= ME_GetCursorOfs(&editor
->pCursors
[0]);
483 to
= ME_GetCursorOfs(&editor
->pCursors
[1]);
486 fromCursor
= editor
->pCursors
[0];
487 toCursor
= editor
->pCursors
[1];
491 fromCursor
= editor
->pCursors
[1];
492 toCursor
= editor
->pCursors
[0];
495 if (!editor
->bEmulateVersion10
) /* v4.1 */
497 if (!para_in_table( toCursor
.para
))
499 editor
->pCursors
[0] = toCursor
;
500 editor
->pCursors
[1] = toCursor
;
502 else table_select_next_cell_or_append( editor
, toCursor
.run
);
504 else /* v1.0 - 3.0 */
506 if (!para_in_table( fromCursor
.para
))
508 editor
->pCursors
[0] = fromCursor
;
509 editor
->pCursors
[1] = fromCursor
;
510 /* FIXME: For some reason the caret is shown at the start of the
511 * previous paragraph in v1.0 to v3.0 */
513 else if ((selected_row
|| !para_in_table( toCursor
.para
)))
514 table_select_next_cell_or_append( editor
, fromCursor
.run
);
517 ME_Run
*run
= run_prev( toCursor
.run
);
519 if (ME_IsSelection(editor
) && !toCursor
.nOffset
&& run
&& run
->nFlags
& MERF_TAB
)
520 table_select_next_cell_or_append( editor
, run
);
522 table_select_next_cell_or_append( editor
, toCursor
.run
);
525 ME_InvalidateSelection(editor
);
527 update_caret(editor
);
528 ME_SendSelChange(editor
);
531 /* Make sure the cursor is not in the hidden table row start paragraph
532 * without a selection. */
533 void table_move_from_row_start( ME_TextEditor
*editor
)
535 ME_Paragraph
*para
= editor
->pCursors
[0].para
;
537 if (para
== editor
->pCursors
[1].para
&& para
->nFlags
& MEPF_ROWSTART
)
539 /* The cursors should not be at the hidden start row paragraph without
540 * a selection, so the cursor is moved into the first cell. */
541 para
= para_next( para
);
542 editor
->pCursors
[0].para
= para
;
543 editor
->pCursors
[0].run
= para_first_run( para
);
544 editor
->pCursors
[0].nOffset
= 0;
545 editor
->pCursors
[1] = editor
->pCursors
[0];
549 struct RTFTable
*ME_MakeTableDef(ME_TextEditor
*editor
)
551 RTFTable
*tableDef
= heap_alloc_zero(sizeof(*tableDef
));
553 if (!editor
->bEmulateVersion10
) /* v4.1 */
558 void ME_InitTableDef(ME_TextEditor
*editor
, struct RTFTable
*tableDef
)
560 ZeroMemory(tableDef
->cells
, sizeof(tableDef
->cells
));
561 ZeroMemory(tableDef
->border
, sizeof(tableDef
->border
));
562 tableDef
->numCellsDefined
= 0;
563 tableDef
->leftEdge
= 0;
564 if (!editor
->bEmulateVersion10
) /* v4.1 */
566 else /* v1.0 - 3.0 */