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