mf/session: Implement support for sinks that provide sample allocators.
[wine.git] / dlls / riched20 / table.c
blob0ce5a2c32c066749113fc10d4b079a6775bb1f54
1 /*
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
51 * just a delimiter.
54 #include "editor.h"
55 #include "rtf.h"
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 );
63 ME_Paragraph *para;
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 );
69 cursor->para = para;
70 cursor->run = para_first_run( para );
71 return para;
74 ME_Paragraph* table_insert_row_start( ME_TextEditor *editor, ME_Cursor *cursor )
76 ME_Paragraph *para;
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;
85 ME_Cursor cursor;
87 cursor.para = para;
88 cursor.run = para_first_run( para );
89 cursor.nOffset = 0;
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;
105 prev_para = para;
106 para = para_next( para );
108 return start_row;
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 )
116 WCHAR tab = '\t';
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 )
123 ME_Paragraph *para;
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 )
131 ME_Cell *cell;
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 );
141 return para;
144 ME_Paragraph* table_row_start( ME_Paragraph *para )
146 ME_Cell *cell;
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 );
157 return para;
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;
169 return 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 );
222 ME_Cursor c2 = *c;
223 ME_Paragraph *this_para = c->para, *end_para;
225 ME_MoveCursorChars( editor, &c2, *num_chars, FALSE );
226 end_para = c2.para;
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
231 * paragraphs.
233 int remaining = start_ofs + *num_chars - c2.run->nCharOfs - end_para->nCharOfs;
234 if (remaining)
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
253 * of the row. */
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 );
276 if (truancate_del)
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;
283 break;
285 this_para = next_para;
289 else /* v1.0 - 3.0 */
291 ME_Run *run;
292 int chars_to_boundary;
294 if ((this_para->nCharOfs != start_ofs || this_para == end_para) && para_in_table( this_para ))
296 run = c->run;
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;
308 run = NULL;
309 if (start_ofs > this_para->nCharOfs)
311 cur_para = para_prev( end_para );
312 run = para_end_run( cur_para );
314 if (!run)
316 cur_para = end_para;
317 run = para_first_run( end_para );
319 if (run)
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';
332 ME_Run *run;
333 int i;
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;
388 ME_Cell *cell;
389 int i;
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 );
402 else
404 para = para_next( table_row_end( para ) );
405 if (para->nFlags & MEPF_ROWSTART) cell = para_cell( para_next( para ) );
406 else
408 /* Insert row */
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);
418 return;
421 /* Select cell */
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 );
434 assert(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;
449 i = 1;
451 else
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);
461 return;
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);
481 int from, to;
482 from = ME_GetCursorOfs(&editor->pCursors[0]);
483 to = ME_GetCursorOfs(&editor->pCursors[1]);
484 if (from <= to)
486 fromCursor = editor->pCursors[0];
487 toCursor = editor->pCursors[1];
489 else
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 );
515 else
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 );
521 else
522 table_select_next_cell_or_append( editor, toCursor.run );
525 ME_InvalidateSelection(editor);
526 ME_Repaint(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 */
554 tableDef->gapH = 10;
555 return tableDef;
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 */
565 tableDef->gapH = 10;
566 else /* v1.0 - 3.0 */
567 tableDef->gapH = 0;