|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include <stdio.h> |
|
7 |
|
8 #include "mozilla/Assertions.h" |
|
9 #include "mozilla/dom/Selection.h" |
|
10 #include "mozilla/dom/Element.h" |
|
11 #include "nsAString.h" |
|
12 #include "nsAlgorithm.h" |
|
13 #include "nsCOMPtr.h" |
|
14 #include "nsDebug.h" |
|
15 #include "nsEditProperty.h" |
|
16 #include "nsEditor.h" |
|
17 #include "nsEditorUtils.h" |
|
18 #include "nsError.h" |
|
19 #include "nsGkAtoms.h" |
|
20 #include "nsHTMLEditUtils.h" |
|
21 #include "nsHTMLEditor.h" |
|
22 #include "nsIAtom.h" |
|
23 #include "nsIContent.h" |
|
24 #include "nsIDOMElement.h" |
|
25 #include "nsIDOMNode.h" |
|
26 #include "nsIDOMRange.h" |
|
27 #include "nsIEditor.h" |
|
28 #include "nsIFrame.h" |
|
29 #include "nsIHTMLEditor.h" |
|
30 #include "nsINode.h" |
|
31 #include "nsIPresShell.h" |
|
32 #include "nsISupportsUtils.h" |
|
33 #include "nsITableCellLayout.h" // For efficient access to table cell |
|
34 #include "nsITableEditor.h" |
|
35 #include "nsLiteralString.h" |
|
36 #include "nsQueryFrame.h" |
|
37 #include "nsString.h" |
|
38 #include "nsTArray.h" |
|
39 #include "nsTableCellFrame.h" |
|
40 #include "nsTableOuterFrame.h" |
|
41 #include "nscore.h" |
|
42 #include <algorithm> |
|
43 |
|
44 using namespace mozilla; |
|
45 using namespace mozilla::dom; |
|
46 |
|
47 /*************************************************************************** |
|
48 * stack based helper class for restoring selection after table edit |
|
49 */ |
|
50 class MOZ_STACK_CLASS nsSetSelectionAfterTableEdit |
|
51 { |
|
52 private: |
|
53 nsCOMPtr<nsITableEditor> mEd; |
|
54 nsCOMPtr<nsIDOMElement> mTable; |
|
55 int32_t mCol, mRow, mDirection, mSelected; |
|
56 public: |
|
57 nsSetSelectionAfterTableEdit(nsITableEditor *aEd, nsIDOMElement* aTable, |
|
58 int32_t aRow, int32_t aCol, int32_t aDirection, |
|
59 bool aSelected) : |
|
60 mEd(do_QueryInterface(aEd)) |
|
61 { |
|
62 mTable = aTable; |
|
63 mRow = aRow; |
|
64 mCol = aCol; |
|
65 mDirection = aDirection; |
|
66 mSelected = aSelected; |
|
67 } |
|
68 |
|
69 ~nsSetSelectionAfterTableEdit() |
|
70 { |
|
71 if (mEd) |
|
72 mEd->SetSelectionAfterTableEdit(mTable, mRow, mCol, mDirection, mSelected); |
|
73 } |
|
74 // This is needed to abort the caret reset in the destructor |
|
75 // when one method yields control to another |
|
76 void CancelSetCaret() {mEd = nullptr; mTable = nullptr;} |
|
77 }; |
|
78 |
|
79 // Stack-class to turn on/off selection batching for table selection |
|
80 class MOZ_STACK_CLASS nsSelectionBatcherForTable |
|
81 { |
|
82 private: |
|
83 nsCOMPtr<nsISelectionPrivate> mSelection; |
|
84 public: |
|
85 nsSelectionBatcherForTable(nsISelection *aSelection) |
|
86 { |
|
87 nsCOMPtr<nsISelection> sel(aSelection); |
|
88 mSelection = do_QueryInterface(sel); |
|
89 if (mSelection) mSelection->StartBatchChanges(); |
|
90 } |
|
91 virtual ~nsSelectionBatcherForTable() |
|
92 { |
|
93 if (mSelection) mSelection->EndBatchChanges(); |
|
94 } |
|
95 }; |
|
96 |
|
97 // Table Editing helper utilities (not exposed in IDL) |
|
98 |
|
99 NS_IMETHODIMP |
|
100 nsHTMLEditor::InsertCell(nsIDOMElement *aCell, int32_t aRowSpan, int32_t aColSpan, |
|
101 bool aAfter, bool aIsHeader, nsIDOMElement **aNewCell) |
|
102 { |
|
103 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
104 if (aNewCell) *aNewCell = nullptr; |
|
105 |
|
106 // And the parent and offsets needed to do an insert |
|
107 nsCOMPtr<nsIDOMNode> cellParent; |
|
108 nsresult res = aCell->GetParentNode(getter_AddRefs(cellParent)); |
|
109 NS_ENSURE_SUCCESS(res, res); |
|
110 NS_ENSURE_TRUE(cellParent, NS_ERROR_NULL_POINTER); |
|
111 |
|
112 int32_t cellOffset = GetChildOffset(aCell, cellParent); |
|
113 |
|
114 nsCOMPtr<nsIDOMElement> newCell; |
|
115 if (aIsHeader) |
|
116 res = CreateElementWithDefaults(NS_LITERAL_STRING("th"), getter_AddRefs(newCell)); |
|
117 else |
|
118 res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell)); |
|
119 |
|
120 if(NS_FAILED(res)) return res; |
|
121 if(!newCell) return NS_ERROR_FAILURE; |
|
122 |
|
123 //Optional: return new cell created |
|
124 if (aNewCell) |
|
125 { |
|
126 *aNewCell = newCell.get(); |
|
127 NS_ADDREF(*aNewCell); |
|
128 } |
|
129 |
|
130 if( aRowSpan > 1) |
|
131 { |
|
132 // Note: Do NOT use editor transaction for this |
|
133 nsAutoString newRowSpan; |
|
134 newRowSpan.AppendInt(aRowSpan, 10); |
|
135 newCell->SetAttribute(NS_LITERAL_STRING("rowspan"), newRowSpan); |
|
136 } |
|
137 if( aColSpan > 1) |
|
138 { |
|
139 // Note: Do NOT use editor transaction for this |
|
140 nsAutoString newColSpan; |
|
141 newColSpan.AppendInt(aColSpan, 10); |
|
142 newCell->SetAttribute(NS_LITERAL_STRING("colspan"), newColSpan); |
|
143 } |
|
144 if(aAfter) cellOffset++; |
|
145 |
|
146 //Don't let Rules System change the selection |
|
147 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
148 return InsertNode(newCell, cellParent, cellOffset); |
|
149 } |
|
150 |
|
151 NS_IMETHODIMP nsHTMLEditor::SetColSpan(nsIDOMElement *aCell, int32_t aColSpan) |
|
152 { |
|
153 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
154 nsAutoString newSpan; |
|
155 newSpan.AppendInt(aColSpan, 10); |
|
156 return SetAttribute(aCell, NS_LITERAL_STRING("colspan"), newSpan); |
|
157 } |
|
158 |
|
159 NS_IMETHODIMP nsHTMLEditor::SetRowSpan(nsIDOMElement *aCell, int32_t aRowSpan) |
|
160 { |
|
161 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
162 nsAutoString newSpan; |
|
163 newSpan.AppendInt(aRowSpan, 10); |
|
164 return SetAttribute(aCell, NS_LITERAL_STRING("rowspan"), newSpan); |
|
165 } |
|
166 |
|
167 /****************************************************************/ |
|
168 |
|
169 // Table Editing interface methods |
|
170 |
|
171 NS_IMETHODIMP |
|
172 nsHTMLEditor::InsertTableCell(int32_t aNumber, bool aAfter) |
|
173 { |
|
174 nsCOMPtr<nsIDOMElement> table; |
|
175 nsCOMPtr<nsIDOMElement> curCell; |
|
176 nsCOMPtr<nsIDOMNode> cellParent; |
|
177 int32_t cellOffset, startRowIndex, startColIndex; |
|
178 nsresult res = GetCellContext(nullptr, |
|
179 getter_AddRefs(table), |
|
180 getter_AddRefs(curCell), |
|
181 getter_AddRefs(cellParent), &cellOffset, |
|
182 &startRowIndex, &startColIndex); |
|
183 NS_ENSURE_SUCCESS(res, res); |
|
184 // Don't fail if no cell found |
|
185 NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
186 |
|
187 // Get more data for current cell in row we are inserting at (we need COLSPAN) |
|
188 int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
189 bool isSelected; |
|
190 res = GetCellDataAt(table, startRowIndex, startColIndex, |
|
191 getter_AddRefs(curCell), |
|
192 &curStartRowIndex, &curStartColIndex, &rowSpan, &colSpan, |
|
193 &actualRowSpan, &actualColSpan, &isSelected); |
|
194 NS_ENSURE_SUCCESS(res, res); |
|
195 NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE); |
|
196 int32_t newCellIndex = aAfter ? (startColIndex+colSpan) : startColIndex; |
|
197 //We control selection resetting after the insert... |
|
198 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, newCellIndex, ePreviousColumn, false); |
|
199 //...so suppress Rules System selection munging |
|
200 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
201 |
|
202 int32_t i; |
|
203 for (i = 0; i < aNumber; i++) |
|
204 { |
|
205 nsCOMPtr<nsIDOMElement> newCell; |
|
206 res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell)); |
|
207 if (NS_SUCCEEDED(res) && newCell) |
|
208 { |
|
209 if (aAfter) cellOffset++; |
|
210 res = InsertNode(newCell, cellParent, cellOffset); |
|
211 if(NS_FAILED(res)) break; |
|
212 } |
|
213 } |
|
214 return res; |
|
215 } |
|
216 |
|
217 |
|
218 NS_IMETHODIMP |
|
219 nsHTMLEditor::GetFirstRow(nsIDOMElement* aTableElement, nsIDOMNode** aRowNode) |
|
220 { |
|
221 NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER); |
|
222 |
|
223 *aRowNode = nullptr; |
|
224 |
|
225 NS_ENSURE_TRUE(aTableElement, NS_ERROR_NULL_POINTER); |
|
226 |
|
227 nsCOMPtr<nsIDOMElement> tableElement; |
|
228 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTableElement, getter_AddRefs(tableElement)); |
|
229 NS_ENSURE_SUCCESS(res, res); |
|
230 NS_ENSURE_TRUE(tableElement, NS_ERROR_NULL_POINTER); |
|
231 |
|
232 nsCOMPtr<nsIDOMNode> tableChild; |
|
233 res = tableElement->GetFirstChild(getter_AddRefs(tableChild)); |
|
234 NS_ENSURE_SUCCESS(res, res); |
|
235 |
|
236 while (tableChild) |
|
237 { |
|
238 nsCOMPtr<nsIContent> content = do_QueryInterface(tableChild); |
|
239 if (content) |
|
240 { |
|
241 nsIAtom *atom = content->Tag(); |
|
242 |
|
243 if (atom == nsEditProperty::tr) |
|
244 { |
|
245 // Found a row directly under <table> |
|
246 *aRowNode = tableChild; |
|
247 NS_ADDREF(*aRowNode); |
|
248 return NS_OK; |
|
249 } |
|
250 // Look for row in one of the row container elements |
|
251 if (atom == nsEditProperty::tbody || |
|
252 atom == nsEditProperty::thead || |
|
253 atom == nsEditProperty::tfoot) |
|
254 { |
|
255 nsCOMPtr<nsIDOMNode> rowNode; |
|
256 res = tableChild->GetFirstChild(getter_AddRefs(rowNode)); |
|
257 NS_ENSURE_SUCCESS(res, res); |
|
258 |
|
259 // We can encounter textnodes here -- must find a row |
|
260 while (rowNode && !nsHTMLEditUtils::IsTableRow(rowNode)) |
|
261 { |
|
262 nsCOMPtr<nsIDOMNode> nextNode; |
|
263 res = rowNode->GetNextSibling(getter_AddRefs(nextNode)); |
|
264 NS_ENSURE_SUCCESS(res, res); |
|
265 |
|
266 rowNode = nextNode; |
|
267 } |
|
268 if(rowNode) |
|
269 { |
|
270 *aRowNode = rowNode.get(); |
|
271 NS_ADDREF(*aRowNode); |
|
272 return NS_OK; |
|
273 } |
|
274 } |
|
275 } |
|
276 // Here if table child was a CAPTION or COLGROUP |
|
277 // or child of a row parent wasn't a row (bad HTML?), |
|
278 // or first child was a textnode |
|
279 // Look in next table child |
|
280 nsCOMPtr<nsIDOMNode> nextChild; |
|
281 res = tableChild->GetNextSibling(getter_AddRefs(nextChild)); |
|
282 NS_ENSURE_SUCCESS(res, res); |
|
283 |
|
284 tableChild = nextChild; |
|
285 }; |
|
286 // If here, row was not found |
|
287 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
288 } |
|
289 |
|
290 NS_IMETHODIMP |
|
291 nsHTMLEditor::GetNextRow(nsIDOMNode* aCurrentRowNode, nsIDOMNode **aRowNode) |
|
292 { |
|
293 NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER); |
|
294 |
|
295 *aRowNode = nullptr; |
|
296 |
|
297 NS_ENSURE_TRUE(aCurrentRowNode, NS_ERROR_NULL_POINTER); |
|
298 |
|
299 if (!nsHTMLEditUtils::IsTableRow(aCurrentRowNode)) |
|
300 return NS_ERROR_FAILURE; |
|
301 |
|
302 nsCOMPtr<nsIDOMNode> nextRow; |
|
303 nsresult res = aCurrentRowNode->GetNextSibling(getter_AddRefs(nextRow)); |
|
304 NS_ENSURE_SUCCESS(res, res); |
|
305 |
|
306 nsCOMPtr<nsIDOMNode> nextNode; |
|
307 |
|
308 // Skip over any textnodes here |
|
309 while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow)) |
|
310 { |
|
311 res = nextRow->GetNextSibling(getter_AddRefs(nextNode)); |
|
312 NS_ENSURE_SUCCESS(res, res); |
|
313 |
|
314 nextRow = nextNode; |
|
315 } |
|
316 if(nextRow) |
|
317 { |
|
318 *aRowNode = nextRow.get(); |
|
319 NS_ADDREF(*aRowNode); |
|
320 return NS_OK; |
|
321 } |
|
322 |
|
323 // No row found, search for rows in other table sections |
|
324 nsCOMPtr<nsIDOMNode> rowParent; |
|
325 res = aCurrentRowNode->GetParentNode(getter_AddRefs(rowParent)); |
|
326 NS_ENSURE_SUCCESS(res, res); |
|
327 NS_ENSURE_TRUE(rowParent, NS_ERROR_NULL_POINTER); |
|
328 |
|
329 nsCOMPtr<nsIDOMNode> parentSibling; |
|
330 res = rowParent->GetNextSibling(getter_AddRefs(parentSibling)); |
|
331 NS_ENSURE_SUCCESS(res, res); |
|
332 |
|
333 while (parentSibling) |
|
334 { |
|
335 res = parentSibling->GetFirstChild(getter_AddRefs(nextRow)); |
|
336 NS_ENSURE_SUCCESS(res, res); |
|
337 |
|
338 // We can encounter textnodes here -- must find a row |
|
339 while (nextRow && !nsHTMLEditUtils::IsTableRow(nextRow)) |
|
340 { |
|
341 res = nextRow->GetNextSibling(getter_AddRefs(nextNode)); |
|
342 NS_ENSURE_SUCCESS(res, res); |
|
343 |
|
344 nextRow = nextNode; |
|
345 } |
|
346 if(nextRow) |
|
347 { |
|
348 *aRowNode = nextRow.get(); |
|
349 NS_ADDREF(*aRowNode); |
|
350 return NS_OK; |
|
351 } |
|
352 #ifdef DEBUG_cmanske |
|
353 printf("GetNextRow: firstChild of row's parent's sibling is not a TR!\n"); |
|
354 #endif |
|
355 // We arrive here only if a table section has no children |
|
356 // or first child of section is not a row (bad HTML or more "_moz_text" nodes!) |
|
357 // So look for another section sibling |
|
358 res = parentSibling->GetNextSibling(getter_AddRefs(nextNode)); |
|
359 NS_ENSURE_SUCCESS(res, res); |
|
360 |
|
361 parentSibling = nextNode; |
|
362 } |
|
363 // If here, row was not found |
|
364 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
365 } |
|
366 |
|
367 NS_IMETHODIMP |
|
368 nsHTMLEditor::GetLastCellInRow(nsIDOMNode* aRowNode, nsIDOMNode** aCellNode) |
|
369 { |
|
370 NS_ENSURE_TRUE(aCellNode, NS_ERROR_NULL_POINTER); |
|
371 |
|
372 *aCellNode = nullptr; |
|
373 |
|
374 NS_ENSURE_TRUE(aRowNode, NS_ERROR_NULL_POINTER); |
|
375 |
|
376 nsCOMPtr<nsIDOMNode> rowChild; |
|
377 nsresult res = aRowNode->GetLastChild(getter_AddRefs(rowChild)); |
|
378 NS_ENSURE_SUCCESS(res, res); |
|
379 |
|
380 while (rowChild && !nsHTMLEditUtils::IsTableCell(rowChild)) |
|
381 { |
|
382 // Skip over textnodes |
|
383 nsCOMPtr<nsIDOMNode> previousChild; |
|
384 res = rowChild->GetPreviousSibling(getter_AddRefs(previousChild)); |
|
385 NS_ENSURE_SUCCESS(res, res); |
|
386 |
|
387 rowChild = previousChild; |
|
388 }; |
|
389 if (rowChild) |
|
390 { |
|
391 *aCellNode = rowChild.get(); |
|
392 NS_ADDREF(*aCellNode); |
|
393 return NS_OK; |
|
394 } |
|
395 // If here, cell was not found |
|
396 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
397 } |
|
398 |
|
399 NS_IMETHODIMP |
|
400 nsHTMLEditor::InsertTableColumn(int32_t aNumber, bool aAfter) |
|
401 { |
|
402 nsCOMPtr<nsISelection> selection; |
|
403 nsCOMPtr<nsIDOMElement> table; |
|
404 nsCOMPtr<nsIDOMElement> curCell; |
|
405 int32_t startRowIndex, startColIndex; |
|
406 nsresult res = GetCellContext(getter_AddRefs(selection), |
|
407 getter_AddRefs(table), |
|
408 getter_AddRefs(curCell), |
|
409 nullptr, nullptr, |
|
410 &startRowIndex, &startColIndex); |
|
411 NS_ENSURE_SUCCESS(res, res); |
|
412 // Don't fail if no cell found |
|
413 NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
414 |
|
415 // Get more data for current cell (we need ROWSPAN) |
|
416 int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
417 bool isSelected; |
|
418 res = GetCellDataAt(table, startRowIndex, startColIndex, |
|
419 getter_AddRefs(curCell), |
|
420 &curStartRowIndex, &curStartColIndex, |
|
421 &rowSpan, &colSpan, |
|
422 &actualRowSpan, &actualColSpan, &isSelected); |
|
423 NS_ENSURE_SUCCESS(res, res); |
|
424 NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE); |
|
425 |
|
426 nsAutoEditBatch beginBatching(this); |
|
427 // Prevent auto insertion of BR in new cell until we're done |
|
428 nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); |
|
429 |
|
430 // Use column after current cell if requested |
|
431 if (aAfter) |
|
432 { |
|
433 startColIndex += actualColSpan; |
|
434 //Detect when user is adding after a COLSPAN=0 case |
|
435 // Assume they want to stop the "0" behavior and |
|
436 // really add a new column. Thus we set the |
|
437 // colspan to its true value |
|
438 if (colSpan == 0) |
|
439 SetColSpan(curCell, actualColSpan); |
|
440 } |
|
441 |
|
442 int32_t rowCount, colCount, rowIndex; |
|
443 res = GetTableSize(table, &rowCount, &colCount); |
|
444 NS_ENSURE_SUCCESS(res, res); |
|
445 |
|
446 //We reset caret in destructor... |
|
447 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, false); |
|
448 //.. so suppress Rules System selection munging |
|
449 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
450 |
|
451 // If we are inserting after all existing columns |
|
452 // Make sure table is "well formed" |
|
453 // before appending new column |
|
454 if (startColIndex >= colCount) |
|
455 NormalizeTable(table); |
|
456 |
|
457 nsCOMPtr<nsIDOMNode> rowNode; |
|
458 for ( rowIndex = 0; rowIndex < rowCount; rowIndex++) |
|
459 { |
|
460 #ifdef DEBUG_cmanske |
|
461 if (rowIndex == rowCount-1) |
|
462 printf(" ***InsertTableColumn: Inserting cell at last row: %d\n", rowIndex); |
|
463 #endif |
|
464 |
|
465 if (startColIndex < colCount) |
|
466 { |
|
467 // We are inserting before an existing column |
|
468 res = GetCellDataAt(table, rowIndex, startColIndex, |
|
469 getter_AddRefs(curCell), |
|
470 &curStartRowIndex, &curStartColIndex, |
|
471 &rowSpan, &colSpan, |
|
472 &actualRowSpan, &actualColSpan, &isSelected); |
|
473 NS_ENSURE_SUCCESS(res, res); |
|
474 |
|
475 // Don't fail entire process if we fail to find a cell |
|
476 // (may fail just in particular rows with < adequate cells per row) |
|
477 if (curCell) |
|
478 { |
|
479 if (curStartColIndex < startColIndex) |
|
480 { |
|
481 // We have a cell spanning this location |
|
482 // Simply increase its colspan to keep table rectangular |
|
483 // Note: we do nothing if colsSpan=0, |
|
484 // since it should automatically span the new column |
|
485 if (colSpan > 0) |
|
486 SetColSpan(curCell, colSpan+aNumber); |
|
487 } else { |
|
488 // Simply set selection to the current cell |
|
489 // so we can let InsertTableCell() do the work |
|
490 // Insert a new cell before current one |
|
491 selection->Collapse(curCell, 0); |
|
492 res = InsertTableCell(aNumber, false); |
|
493 } |
|
494 } |
|
495 } else { |
|
496 // Get current row and append new cells after last cell in row |
|
497 if(rowIndex == 0) |
|
498 res = GetFirstRow(table.get(), getter_AddRefs(rowNode)); |
|
499 else |
|
500 { |
|
501 nsCOMPtr<nsIDOMNode> nextRow; |
|
502 res = GetNextRow(rowNode.get(), getter_AddRefs(nextRow)); |
|
503 rowNode = nextRow; |
|
504 } |
|
505 NS_ENSURE_SUCCESS(res, res); |
|
506 |
|
507 if (rowNode) |
|
508 { |
|
509 nsCOMPtr<nsIDOMNode> lastCell; |
|
510 res = GetLastCellInRow(rowNode, getter_AddRefs(lastCell)); |
|
511 NS_ENSURE_SUCCESS(res, res); |
|
512 NS_ENSURE_TRUE(lastCell, NS_ERROR_FAILURE); |
|
513 |
|
514 curCell = do_QueryInterface(lastCell); |
|
515 if (curCell) |
|
516 { |
|
517 // Simply add same number of cells to each row |
|
518 // Although tempted to check cell indexes for curCell, |
|
519 // the effects of COLSPAN>1 in some cells makes this futile! |
|
520 // We must use NormalizeTable first to assure |
|
521 // that there are cells in each cellmap location |
|
522 selection->Collapse(curCell, 0); |
|
523 res = InsertTableCell(aNumber, true); |
|
524 } |
|
525 } |
|
526 } |
|
527 } |
|
528 return res; |
|
529 } |
|
530 |
|
531 NS_IMETHODIMP |
|
532 nsHTMLEditor::InsertTableRow(int32_t aNumber, bool aAfter) |
|
533 { |
|
534 nsCOMPtr<nsISelection> selection; |
|
535 nsCOMPtr<nsIDOMElement> table; |
|
536 nsCOMPtr<nsIDOMElement> curCell; |
|
537 |
|
538 int32_t startRowIndex, startColIndex; |
|
539 nsresult res = GetCellContext(nullptr, |
|
540 getter_AddRefs(table), |
|
541 getter_AddRefs(curCell), |
|
542 nullptr, nullptr, |
|
543 &startRowIndex, &startColIndex); |
|
544 NS_ENSURE_SUCCESS(res, res); |
|
545 // Don't fail if no cell found |
|
546 NS_ENSURE_TRUE(curCell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
547 |
|
548 // Get more data for current cell in row we are inserting at (we need COLSPAN) |
|
549 int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
550 bool isSelected; |
|
551 res = GetCellDataAt(table, startRowIndex, startColIndex, |
|
552 getter_AddRefs(curCell), |
|
553 &curStartRowIndex, &curStartColIndex, |
|
554 &rowSpan, &colSpan, |
|
555 &actualRowSpan, &actualColSpan, &isSelected); |
|
556 NS_ENSURE_SUCCESS(res, res); |
|
557 NS_ENSURE_TRUE(curCell, NS_ERROR_FAILURE); |
|
558 |
|
559 int32_t rowCount, colCount; |
|
560 res = GetTableSize(table, &rowCount, &colCount); |
|
561 NS_ENSURE_SUCCESS(res, res); |
|
562 |
|
563 nsAutoEditBatch beginBatching(this); |
|
564 // Prevent auto insertion of BR in new cell until we're done |
|
565 nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); |
|
566 |
|
567 if (aAfter) |
|
568 { |
|
569 // Use row after current cell |
|
570 startRowIndex += actualRowSpan; |
|
571 |
|
572 //Detect when user is adding after a ROWSPAN=0 case |
|
573 // Assume they want to stop the "0" behavior and |
|
574 // really add a new row. Thus we set the |
|
575 // rowspan to its true value |
|
576 if (rowSpan == 0) |
|
577 SetRowSpan(curCell, actualRowSpan); |
|
578 } |
|
579 |
|
580 //We control selection resetting after the insert... |
|
581 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); |
|
582 //...so suppress Rules System selection munging |
|
583 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
584 |
|
585 nsCOMPtr<nsIDOMElement> cellForRowParent; |
|
586 int32_t cellsInRow = 0; |
|
587 if (startRowIndex < rowCount) |
|
588 { |
|
589 // We are inserting above an existing row |
|
590 // Get each cell in the insert row to adjust for COLSPAN effects while we |
|
591 // count how many cells are needed |
|
592 int32_t colIndex = 0; |
|
593 // This returns NS_TABLELAYOUT_CELL_NOT_FOUND when we run past end of row, |
|
594 // which passes the NS_SUCCEEDED macro |
|
595 while ( NS_OK == GetCellDataAt(table, startRowIndex, colIndex, |
|
596 getter_AddRefs(curCell), |
|
597 &curStartRowIndex, &curStartColIndex, |
|
598 &rowSpan, &colSpan, |
|
599 &actualRowSpan, &actualColSpan, |
|
600 &isSelected) ) |
|
601 { |
|
602 if (curCell) |
|
603 { |
|
604 if (curStartRowIndex < startRowIndex) |
|
605 { |
|
606 // We have a cell spanning this location |
|
607 // Simply increase its rowspan |
|
608 //Note that if rowSpan == 0, we do nothing, |
|
609 // since that cell should automatically extend into the new row |
|
610 if (rowSpan > 0) |
|
611 SetRowSpan(curCell, rowSpan+aNumber); |
|
612 } else { |
|
613 // We have a cell in the insert row |
|
614 |
|
615 // Count the number of cells we need to add to the new row |
|
616 cellsInRow += actualColSpan; |
|
617 |
|
618 // Save cell we will use below |
|
619 if (!cellForRowParent) |
|
620 cellForRowParent = curCell; |
|
621 } |
|
622 // Next cell in row |
|
623 colIndex += actualColSpan; |
|
624 } |
|
625 else |
|
626 colIndex++; |
|
627 } |
|
628 } else { |
|
629 // We are adding a new row after all others |
|
630 // If it weren't for colspan=0 effect, |
|
631 // we could simply use colCount for number of new cells... |
|
632 cellsInRow = colCount; |
|
633 |
|
634 // ...but we must compensate for all cells with rowSpan = 0 in the last row |
|
635 int32_t lastRow = rowCount-1; |
|
636 int32_t tempColIndex = 0; |
|
637 while ( NS_OK == GetCellDataAt(table, lastRow, tempColIndex, |
|
638 getter_AddRefs(curCell), |
|
639 &curStartRowIndex, &curStartColIndex, |
|
640 &rowSpan, &colSpan, |
|
641 &actualRowSpan, &actualColSpan, |
|
642 &isSelected) ) |
|
643 { |
|
644 if (rowSpan == 0) |
|
645 cellsInRow -= actualColSpan; |
|
646 |
|
647 tempColIndex += actualColSpan; |
|
648 |
|
649 // Save cell from the last row that we will use below |
|
650 if (!cellForRowParent && curStartRowIndex == lastRow) |
|
651 cellForRowParent = curCell; |
|
652 } |
|
653 } |
|
654 |
|
655 if (cellsInRow > 0) |
|
656 { |
|
657 // The row parent and offset where we will insert new row |
|
658 nsCOMPtr<nsIDOMNode> parentOfRow; |
|
659 int32_t newRowOffset; |
|
660 |
|
661 NS_NAMED_LITERAL_STRING(trStr, "tr"); |
|
662 if (cellForRowParent) |
|
663 { |
|
664 nsCOMPtr<nsIDOMElement> parentRow; |
|
665 res = GetElementOrParentByTagName(trStr, cellForRowParent, getter_AddRefs(parentRow)); |
|
666 NS_ENSURE_SUCCESS(res, res); |
|
667 NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER); |
|
668 |
|
669 parentRow->GetParentNode(getter_AddRefs(parentOfRow)); |
|
670 NS_ENSURE_TRUE(parentOfRow, NS_ERROR_NULL_POINTER); |
|
671 |
|
672 newRowOffset = GetChildOffset(parentRow, parentOfRow); |
|
673 |
|
674 // Adjust for when adding past the end |
|
675 if (aAfter && startRowIndex >= rowCount) |
|
676 newRowOffset++; |
|
677 } |
|
678 else |
|
679 return NS_ERROR_FAILURE; |
|
680 |
|
681 for (int32_t row = 0; row < aNumber; row++) |
|
682 { |
|
683 // Create a new row |
|
684 nsCOMPtr<nsIDOMElement> newRow; |
|
685 res = CreateElementWithDefaults(trStr, getter_AddRefs(newRow)); |
|
686 if (NS_SUCCEEDED(res)) |
|
687 { |
|
688 NS_ENSURE_TRUE(newRow, NS_ERROR_FAILURE); |
|
689 |
|
690 for (int32_t i = 0; i < cellsInRow; i++) |
|
691 { |
|
692 nsCOMPtr<nsIDOMElement> newCell; |
|
693 res = CreateElementWithDefaults(NS_LITERAL_STRING("td"), getter_AddRefs(newCell)); |
|
694 NS_ENSURE_SUCCESS(res, res); |
|
695 NS_ENSURE_TRUE(newCell, NS_ERROR_FAILURE); |
|
696 |
|
697 // Don't use transaction system yet! (not until entire row is inserted) |
|
698 nsCOMPtr<nsIDOMNode>resultNode; |
|
699 res = newRow->AppendChild(newCell, getter_AddRefs(resultNode)); |
|
700 NS_ENSURE_SUCCESS(res, res); |
|
701 } |
|
702 // Use transaction system to insert the entire row+cells |
|
703 // (Note that rows are inserted at same childoffset each time) |
|
704 res = InsertNode(newRow, parentOfRow, newRowOffset); |
|
705 NS_ENSURE_SUCCESS(res, res); |
|
706 } |
|
707 } |
|
708 } |
|
709 return res; |
|
710 } |
|
711 |
|
712 // Editor helper only |
|
713 // XXX Code changed for bug 217717 and now we don't need aSelection param |
|
714 // TODO: Remove aSelection param |
|
715 NS_IMETHODIMP |
|
716 nsHTMLEditor::DeleteTable2(nsIDOMElement *aTable, nsISelection *aSelection) |
|
717 { |
|
718 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); |
|
719 |
|
720 // Select the table |
|
721 nsresult res = ClearSelection(); |
|
722 if (NS_SUCCEEDED(res)) |
|
723 res = AppendNodeToSelectionAsRange(aTable); |
|
724 NS_ENSURE_SUCCESS(res, res); |
|
725 |
|
726 return DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip); |
|
727 } |
|
728 |
|
729 NS_IMETHODIMP |
|
730 nsHTMLEditor::DeleteTable() |
|
731 { |
|
732 nsCOMPtr<nsISelection> selection; |
|
733 nsCOMPtr<nsIDOMElement> table; |
|
734 nsresult res = GetCellContext(getter_AddRefs(selection), |
|
735 getter_AddRefs(table), |
|
736 nullptr, nullptr, nullptr, nullptr, nullptr); |
|
737 |
|
738 NS_ENSURE_SUCCESS(res, res); |
|
739 |
|
740 nsAutoEditBatch beginBatching(this); |
|
741 return DeleteTable2(table, selection); |
|
742 } |
|
743 |
|
744 NS_IMETHODIMP |
|
745 nsHTMLEditor::DeleteTableCell(int32_t aNumber) |
|
746 { |
|
747 nsCOMPtr<nsISelection> selection; |
|
748 nsCOMPtr<nsIDOMElement> table; |
|
749 nsCOMPtr<nsIDOMElement> cell; |
|
750 int32_t startRowIndex, startColIndex; |
|
751 |
|
752 |
|
753 nsresult res = GetCellContext(getter_AddRefs(selection), |
|
754 getter_AddRefs(table), |
|
755 getter_AddRefs(cell), |
|
756 nullptr, nullptr, |
|
757 &startRowIndex, &startColIndex); |
|
758 |
|
759 NS_ENSURE_SUCCESS(res, res); |
|
760 // Don't fail if we didn't find a table or cell |
|
761 NS_ENSURE_TRUE(table && cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
762 |
|
763 nsAutoEditBatch beginBatching(this); |
|
764 // Prevent rules testing until we're done |
|
765 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
766 |
|
767 nsCOMPtr<nsIDOMElement> firstCell; |
|
768 nsCOMPtr<nsIDOMRange> range; |
|
769 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); |
|
770 NS_ENSURE_SUCCESS(res, res); |
|
771 |
|
772 int32_t rangeCount; |
|
773 res = selection->GetRangeCount(&rangeCount); |
|
774 NS_ENSURE_SUCCESS(res, res); |
|
775 |
|
776 if (firstCell && rangeCount > 1) |
|
777 { |
|
778 // When > 1 selected cell, |
|
779 // ignore aNumber and use selected cells |
|
780 cell = firstCell; |
|
781 |
|
782 int32_t rowCount, colCount; |
|
783 res = GetTableSize(table, &rowCount, &colCount); |
|
784 NS_ENSURE_SUCCESS(res, res); |
|
785 |
|
786 // Get indexes -- may be different than original cell |
|
787 res = GetCellIndexes(cell, &startRowIndex, &startColIndex); |
|
788 NS_ENSURE_SUCCESS(res, res); |
|
789 |
|
790 // The setCaret object will call SetSelectionAfterTableEdit in its destructor |
|
791 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); |
|
792 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
793 |
|
794 bool checkToDeleteRow = true; |
|
795 bool checkToDeleteColumn = true; |
|
796 while (cell) |
|
797 { |
|
798 bool deleteRow = false; |
|
799 bool deleteCol = false; |
|
800 |
|
801 if (checkToDeleteRow) |
|
802 { |
|
803 // Optimize to delete an entire row |
|
804 // Clear so we don't repeat AllCellsInRowSelected within the same row |
|
805 checkToDeleteRow = false; |
|
806 |
|
807 deleteRow = AllCellsInRowSelected(table, startRowIndex, colCount); |
|
808 if (deleteRow) |
|
809 { |
|
810 // First, find the next cell in a different row |
|
811 // to continue after we delete this row |
|
812 int32_t nextRow = startRowIndex; |
|
813 while (nextRow == startRowIndex) |
|
814 { |
|
815 res = GetNextSelectedCell(nullptr, getter_AddRefs(cell)); |
|
816 NS_ENSURE_SUCCESS(res, res); |
|
817 if (!cell) break; |
|
818 res = GetCellIndexes(cell, &nextRow, &startColIndex); |
|
819 NS_ENSURE_SUCCESS(res, res); |
|
820 } |
|
821 // Delete entire row |
|
822 res = DeleteRow(table, startRowIndex); |
|
823 NS_ENSURE_SUCCESS(res, res); |
|
824 |
|
825 if (cell) |
|
826 { |
|
827 // For the next cell: Subtract 1 for row we deleted |
|
828 startRowIndex = nextRow - 1; |
|
829 // Set true since we know we will look at a new row next |
|
830 checkToDeleteRow = true; |
|
831 } |
|
832 } |
|
833 } |
|
834 if (!deleteRow) |
|
835 { |
|
836 if (checkToDeleteColumn) |
|
837 { |
|
838 // Optimize to delete an entire column |
|
839 // Clear this so we don't repeat AllCellsInColSelected within the same Col |
|
840 checkToDeleteColumn = false; |
|
841 |
|
842 deleteCol = AllCellsInColumnSelected(table, startColIndex, colCount); |
|
843 if (deleteCol) |
|
844 { |
|
845 // First, find the next cell in a different column |
|
846 // to continue after we delete this column |
|
847 int32_t nextCol = startColIndex; |
|
848 while (nextCol == startColIndex) |
|
849 { |
|
850 res = GetNextSelectedCell(nullptr, getter_AddRefs(cell)); |
|
851 NS_ENSURE_SUCCESS(res, res); |
|
852 if (!cell) break; |
|
853 res = GetCellIndexes(cell, &startRowIndex, &nextCol); |
|
854 NS_ENSURE_SUCCESS(res, res); |
|
855 } |
|
856 // Delete entire Col |
|
857 res = DeleteColumn(table, startColIndex); |
|
858 NS_ENSURE_SUCCESS(res, res); |
|
859 if (cell) |
|
860 { |
|
861 // For the next cell, subtract 1 for col. deleted |
|
862 startColIndex = nextCol - 1; |
|
863 // Set true since we know we will look at a new column next |
|
864 checkToDeleteColumn = true; |
|
865 } |
|
866 } |
|
867 } |
|
868 if (!deleteCol) |
|
869 { |
|
870 // First get the next cell to delete |
|
871 nsCOMPtr<nsIDOMElement> nextCell; |
|
872 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(nextCell)); |
|
873 NS_ENSURE_SUCCESS(res, res); |
|
874 |
|
875 // Then delete the cell |
|
876 res = DeleteNode(cell); |
|
877 NS_ENSURE_SUCCESS(res, res); |
|
878 |
|
879 // The next cell to delete |
|
880 cell = nextCell; |
|
881 if (cell) |
|
882 { |
|
883 res = GetCellIndexes(cell, &startRowIndex, &startColIndex); |
|
884 NS_ENSURE_SUCCESS(res, res); |
|
885 } |
|
886 } |
|
887 } |
|
888 } |
|
889 } |
|
890 else for (int32_t i = 0; i < aNumber; i++) |
|
891 { |
|
892 res = GetCellContext(getter_AddRefs(selection), |
|
893 getter_AddRefs(table), |
|
894 getter_AddRefs(cell), |
|
895 nullptr, nullptr, |
|
896 &startRowIndex, &startColIndex); |
|
897 NS_ENSURE_SUCCESS(res, res); |
|
898 // Don't fail if no cell found |
|
899 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
900 |
|
901 if (1 == GetNumberOfCellsInRow(table, startRowIndex)) |
|
902 { |
|
903 nsCOMPtr<nsIDOMElement> parentRow; |
|
904 res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow)); |
|
905 NS_ENSURE_SUCCESS(res, res); |
|
906 NS_ENSURE_TRUE(parentRow, NS_ERROR_NULL_POINTER); |
|
907 |
|
908 // We should delete the row instead, |
|
909 // but first check if its the only row left |
|
910 // so we can delete the entire table |
|
911 int32_t rowCount, colCount; |
|
912 res = GetTableSize(table, &rowCount, &colCount); |
|
913 NS_ENSURE_SUCCESS(res, res); |
|
914 |
|
915 if (rowCount == 1) |
|
916 return DeleteTable2(table, selection); |
|
917 |
|
918 // We need to call DeleteTableRow to handle cells with rowspan |
|
919 res = DeleteTableRow(1); |
|
920 NS_ENSURE_SUCCESS(res, res); |
|
921 } |
|
922 else |
|
923 { |
|
924 // More than 1 cell in the row |
|
925 |
|
926 // The setCaret object will call SetSelectionAfterTableEdit in its destructor |
|
927 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); |
|
928 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
929 |
|
930 res = DeleteNode(cell); |
|
931 // If we fail, don't try to delete any more cells??? |
|
932 NS_ENSURE_SUCCESS(res, res); |
|
933 } |
|
934 } |
|
935 return NS_OK; |
|
936 } |
|
937 |
|
938 NS_IMETHODIMP |
|
939 nsHTMLEditor::DeleteTableCellContents() |
|
940 { |
|
941 nsCOMPtr<nsISelection> selection; |
|
942 nsCOMPtr<nsIDOMElement> table; |
|
943 nsCOMPtr<nsIDOMElement> cell; |
|
944 int32_t startRowIndex, startColIndex; |
|
945 nsresult res; |
|
946 res = GetCellContext(getter_AddRefs(selection), |
|
947 getter_AddRefs(table), |
|
948 getter_AddRefs(cell), |
|
949 nullptr, nullptr, |
|
950 &startRowIndex, &startColIndex); |
|
951 NS_ENSURE_SUCCESS(res, res); |
|
952 // Don't fail if no cell found |
|
953 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
954 |
|
955 |
|
956 nsAutoEditBatch beginBatching(this); |
|
957 // Prevent rules testing until we're done |
|
958 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
959 //Don't let Rules System change the selection |
|
960 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
961 |
|
962 |
|
963 nsCOMPtr<nsIDOMElement> firstCell; |
|
964 nsCOMPtr<nsIDOMRange> range; |
|
965 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); |
|
966 NS_ENSURE_SUCCESS(res, res); |
|
967 |
|
968 |
|
969 if (firstCell) |
|
970 { |
|
971 cell = firstCell; |
|
972 res = GetCellIndexes(cell, &startRowIndex, &startColIndex); |
|
973 NS_ENSURE_SUCCESS(res, res); |
|
974 } |
|
975 |
|
976 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); |
|
977 |
|
978 while (cell) |
|
979 { |
|
980 DeleteCellContents(cell); |
|
981 if (firstCell) |
|
982 { |
|
983 // We doing a selected cells, so do all of them |
|
984 res = GetNextSelectedCell(nullptr, getter_AddRefs(cell)); |
|
985 NS_ENSURE_SUCCESS(res, res); |
|
986 } |
|
987 else |
|
988 cell = nullptr; |
|
989 } |
|
990 return NS_OK; |
|
991 } |
|
992 |
|
993 NS_IMETHODIMP |
|
994 nsHTMLEditor::DeleteCellContents(nsIDOMElement *aCell) |
|
995 { |
|
996 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
997 |
|
998 // Prevent rules testing until we're done |
|
999 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
1000 |
|
1001 nsCOMPtr<nsIDOMNode> child; |
|
1002 bool hasChild; |
|
1003 aCell->HasChildNodes(&hasChild); |
|
1004 |
|
1005 while (hasChild) |
|
1006 { |
|
1007 aCell->GetLastChild(getter_AddRefs(child)); |
|
1008 nsresult res = DeleteNode(child); |
|
1009 NS_ENSURE_SUCCESS(res, res); |
|
1010 aCell->HasChildNodes(&hasChild); |
|
1011 } |
|
1012 return NS_OK; |
|
1013 } |
|
1014 |
|
1015 NS_IMETHODIMP |
|
1016 nsHTMLEditor::DeleteTableColumn(int32_t aNumber) |
|
1017 { |
|
1018 nsCOMPtr<nsISelection> selection; |
|
1019 nsCOMPtr<nsIDOMElement> table; |
|
1020 nsCOMPtr<nsIDOMElement> cell; |
|
1021 int32_t startRowIndex, startColIndex, rowCount, colCount; |
|
1022 nsresult res = GetCellContext(getter_AddRefs(selection), |
|
1023 getter_AddRefs(table), |
|
1024 getter_AddRefs(cell), |
|
1025 nullptr, nullptr, |
|
1026 &startRowIndex, &startColIndex); |
|
1027 NS_ENSURE_SUCCESS(res, res); |
|
1028 // Don't fail if no cell found |
|
1029 NS_ENSURE_TRUE(table && cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
1030 |
|
1031 res = GetTableSize(table, &rowCount, &colCount); |
|
1032 NS_ENSURE_SUCCESS(res, res); |
|
1033 |
|
1034 // Shortcut the case of deleting all columns in table |
|
1035 if(startColIndex == 0 && aNumber >= colCount) |
|
1036 return DeleteTable2(table, selection); |
|
1037 |
|
1038 // Check for counts too high |
|
1039 aNumber = std::min(aNumber,(colCount-startColIndex)); |
|
1040 |
|
1041 nsAutoEditBatch beginBatching(this); |
|
1042 // Prevent rules testing until we're done |
|
1043 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
1044 |
|
1045 // Test if deletion is controlled by selected cells |
|
1046 nsCOMPtr<nsIDOMElement> firstCell; |
|
1047 nsCOMPtr<nsIDOMRange> range; |
|
1048 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); |
|
1049 NS_ENSURE_SUCCESS(res, res); |
|
1050 |
|
1051 int32_t rangeCount; |
|
1052 res = selection->GetRangeCount(&rangeCount); |
|
1053 NS_ENSURE_SUCCESS(res, res); |
|
1054 |
|
1055 if (firstCell && rangeCount > 1) |
|
1056 { |
|
1057 // Fetch indexes again - may be different for selected cells |
|
1058 res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex); |
|
1059 NS_ENSURE_SUCCESS(res, res); |
|
1060 } |
|
1061 //We control selection resetting after the insert... |
|
1062 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, false); |
|
1063 |
|
1064 if (firstCell && rangeCount > 1) |
|
1065 { |
|
1066 // Use selected cells to determine what rows to delete |
|
1067 cell = firstCell; |
|
1068 |
|
1069 while (cell) |
|
1070 { |
|
1071 if (cell != firstCell) |
|
1072 { |
|
1073 res = GetCellIndexes(cell, &startRowIndex, &startColIndex); |
|
1074 NS_ENSURE_SUCCESS(res, res); |
|
1075 } |
|
1076 // Find the next cell in a different column |
|
1077 // to continue after we delete this column |
|
1078 int32_t nextCol = startColIndex; |
|
1079 while (nextCol == startColIndex) |
|
1080 { |
|
1081 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); |
|
1082 NS_ENSURE_SUCCESS(res, res); |
|
1083 if (!cell) break; |
|
1084 res = GetCellIndexes(cell, &startRowIndex, &nextCol); |
|
1085 NS_ENSURE_SUCCESS(res, res); |
|
1086 } |
|
1087 res = DeleteColumn(table, startColIndex); |
|
1088 NS_ENSURE_SUCCESS(res, res); |
|
1089 } |
|
1090 } |
|
1091 else for (int32_t i = 0; i < aNumber; i++) |
|
1092 { |
|
1093 res = DeleteColumn(table, startColIndex); |
|
1094 NS_ENSURE_SUCCESS(res, res); |
|
1095 } |
|
1096 return NS_OK; |
|
1097 } |
|
1098 |
|
1099 NS_IMETHODIMP |
|
1100 nsHTMLEditor::DeleteColumn(nsIDOMElement *aTable, int32_t aColIndex) |
|
1101 { |
|
1102 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); |
|
1103 |
|
1104 nsCOMPtr<nsIDOMElement> cell; |
|
1105 nsCOMPtr<nsIDOMElement> cellInDeleteCol; |
|
1106 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
1107 bool isSelected; |
|
1108 int32_t rowIndex = 0; |
|
1109 nsresult res = NS_OK; |
|
1110 |
|
1111 do { |
|
1112 res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell), |
|
1113 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
1114 &actualRowSpan, &actualColSpan, &isSelected); |
|
1115 |
|
1116 NS_ENSURE_SUCCESS(res, res); |
|
1117 |
|
1118 if (cell) |
|
1119 { |
|
1120 // Find cells that don't start in column we are deleting |
|
1121 if (startColIndex < aColIndex || colSpan > 1 || colSpan == 0) |
|
1122 { |
|
1123 // We have a cell spanning this location |
|
1124 // Decrease its colspan to keep table rectangular, |
|
1125 // but if colSpan=0, it will adjust automatically |
|
1126 if (colSpan > 0) |
|
1127 { |
|
1128 NS_ASSERTION((colSpan > 1),"Bad COLSPAN in DeleteTableColumn"); |
|
1129 SetColSpan(cell, colSpan-1); |
|
1130 } |
|
1131 if (startColIndex == aColIndex) |
|
1132 { |
|
1133 // Cell is in column to be deleted, but must have colspan > 1, |
|
1134 // so delete contents of cell instead of cell itself |
|
1135 // (We must have reset colspan above) |
|
1136 DeleteCellContents(cell); |
|
1137 } |
|
1138 // To next cell in column |
|
1139 rowIndex += actualRowSpan; |
|
1140 } |
|
1141 else |
|
1142 { |
|
1143 // Delete the cell |
|
1144 if (1 == GetNumberOfCellsInRow(aTable, rowIndex)) |
|
1145 { |
|
1146 // Only 1 cell in row - delete the row |
|
1147 nsCOMPtr<nsIDOMElement> parentRow; |
|
1148 res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cell, getter_AddRefs(parentRow)); |
|
1149 NS_ENSURE_SUCCESS(res, res); |
|
1150 if(!parentRow) return NS_ERROR_NULL_POINTER; |
|
1151 |
|
1152 // But first check if its the only row left |
|
1153 // so we can delete the entire table |
|
1154 // (This should never happen but it's the safe thing to do) |
|
1155 int32_t rowCount, colCount; |
|
1156 res = GetTableSize(aTable, &rowCount, &colCount); |
|
1157 NS_ENSURE_SUCCESS(res, res); |
|
1158 |
|
1159 if (rowCount == 1) |
|
1160 { |
|
1161 nsCOMPtr<nsISelection> selection; |
|
1162 res = GetSelection(getter_AddRefs(selection)); |
|
1163 NS_ENSURE_SUCCESS(res, res); |
|
1164 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
1165 return DeleteTable2(aTable, selection); |
|
1166 } |
|
1167 |
|
1168 // Delete the row by placing caret in cell we were to delete |
|
1169 // We need to call DeleteTableRow to handle cells with rowspan |
|
1170 res = DeleteRow(aTable, startRowIndex); |
|
1171 NS_ENSURE_SUCCESS(res, res); |
|
1172 |
|
1173 // Note that we don't incremenet rowIndex |
|
1174 // since a row was deleted and "next" |
|
1175 // row now has current rowIndex |
|
1176 } |
|
1177 else |
|
1178 { |
|
1179 // A more "normal" deletion |
|
1180 res = DeleteNode(cell); |
|
1181 NS_ENSURE_SUCCESS(res, res); |
|
1182 |
|
1183 //Skip over any rows spanned by this cell |
|
1184 rowIndex += actualRowSpan; |
|
1185 } |
|
1186 } |
|
1187 } |
|
1188 } while (cell); |
|
1189 |
|
1190 return NS_OK; |
|
1191 } |
|
1192 |
|
1193 NS_IMETHODIMP |
|
1194 nsHTMLEditor::DeleteTableRow(int32_t aNumber) |
|
1195 { |
|
1196 nsCOMPtr<nsISelection> selection; |
|
1197 nsCOMPtr<nsIDOMElement> table; |
|
1198 nsCOMPtr<nsIDOMElement> cell; |
|
1199 int32_t startRowIndex, startColIndex; |
|
1200 int32_t rowCount, colCount; |
|
1201 nsresult res = GetCellContext(getter_AddRefs(selection), |
|
1202 getter_AddRefs(table), |
|
1203 getter_AddRefs(cell), |
|
1204 nullptr, nullptr, |
|
1205 &startRowIndex, &startColIndex); |
|
1206 NS_ENSURE_SUCCESS(res, res); |
|
1207 // Don't fail if no cell found |
|
1208 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
1209 |
|
1210 res = GetTableSize(table, &rowCount, &colCount); |
|
1211 NS_ENSURE_SUCCESS(res, res); |
|
1212 |
|
1213 // Shortcut the case of deleting all rows in table |
|
1214 if(startRowIndex == 0 && aNumber >= rowCount) |
|
1215 return DeleteTable2(table, selection); |
|
1216 |
|
1217 nsAutoEditBatch beginBatching(this); |
|
1218 // Prevent rules testing until we're done |
|
1219 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
1220 |
|
1221 nsCOMPtr<nsIDOMElement> firstCell; |
|
1222 nsCOMPtr<nsIDOMRange> range; |
|
1223 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(firstCell)); |
|
1224 NS_ENSURE_SUCCESS(res, res); |
|
1225 |
|
1226 int32_t rangeCount; |
|
1227 res = selection->GetRangeCount(&rangeCount); |
|
1228 NS_ENSURE_SUCCESS(res, res); |
|
1229 |
|
1230 if (firstCell && rangeCount > 1) |
|
1231 { |
|
1232 // Fetch indexes again - may be different for selected cells |
|
1233 res = GetCellIndexes(firstCell, &startRowIndex, &startColIndex); |
|
1234 NS_ENSURE_SUCCESS(res, res); |
|
1235 } |
|
1236 |
|
1237 //We control selection resetting after the insert... |
|
1238 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousRow, false); |
|
1239 // Don't change selection during deletions |
|
1240 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
1241 |
|
1242 if (firstCell && rangeCount > 1) |
|
1243 { |
|
1244 // Use selected cells to determine what rows to delete |
|
1245 cell = firstCell; |
|
1246 |
|
1247 while (cell) |
|
1248 { |
|
1249 if (cell != firstCell) |
|
1250 { |
|
1251 res = GetCellIndexes(cell, &startRowIndex, &startColIndex); |
|
1252 NS_ENSURE_SUCCESS(res, res); |
|
1253 } |
|
1254 // Find the next cell in a different row |
|
1255 // to continue after we delete this row |
|
1256 int32_t nextRow = startRowIndex; |
|
1257 while (nextRow == startRowIndex) |
|
1258 { |
|
1259 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); |
|
1260 NS_ENSURE_SUCCESS(res, res); |
|
1261 if (!cell) break; |
|
1262 res = GetCellIndexes(cell, &nextRow, &startColIndex); |
|
1263 NS_ENSURE_SUCCESS(res, res); |
|
1264 } |
|
1265 // Delete entire row |
|
1266 res = DeleteRow(table, startRowIndex); |
|
1267 NS_ENSURE_SUCCESS(res, res); |
|
1268 } |
|
1269 } |
|
1270 else |
|
1271 { |
|
1272 // Check for counts too high |
|
1273 aNumber = std::min(aNumber,(rowCount-startRowIndex)); |
|
1274 |
|
1275 for (int32_t i = 0; i < aNumber; i++) |
|
1276 { |
|
1277 res = DeleteRow(table, startRowIndex); |
|
1278 // If failed in current row, try the next |
|
1279 if (NS_FAILED(res)) |
|
1280 startRowIndex++; |
|
1281 |
|
1282 // Check if there's a cell in the "next" row |
|
1283 res = GetCellAt(table, startRowIndex, startColIndex, getter_AddRefs(cell)); |
|
1284 NS_ENSURE_SUCCESS(res, res); |
|
1285 if(!cell) |
|
1286 break; |
|
1287 } |
|
1288 } |
|
1289 return NS_OK; |
|
1290 } |
|
1291 |
|
1292 // Helper that doesn't batch or change the selection |
|
1293 NS_IMETHODIMP |
|
1294 nsHTMLEditor::DeleteRow(nsIDOMElement *aTable, int32_t aRowIndex) |
|
1295 { |
|
1296 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); |
|
1297 |
|
1298 nsCOMPtr<nsIDOMElement> cell; |
|
1299 nsCOMPtr<nsIDOMElement> cellInDeleteRow; |
|
1300 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
1301 bool isSelected; |
|
1302 int32_t colIndex = 0; |
|
1303 nsresult res = NS_OK; |
|
1304 |
|
1305 // Prevent rules testing until we're done |
|
1306 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
1307 |
|
1308 // The list of cells we will change rowspan in |
|
1309 // and the new rowspan values for each |
|
1310 nsTArray<nsCOMPtr<nsIDOMElement> > spanCellList; |
|
1311 nsTArray<int32_t> newSpanList; |
|
1312 |
|
1313 int32_t rowCount, colCount; |
|
1314 res = GetTableSize(aTable, &rowCount, &colCount); |
|
1315 NS_ENSURE_SUCCESS(res, res); |
|
1316 |
|
1317 // Scan through cells in row to do rowspan adjustments |
|
1318 // Note that after we delete row, startRowIndex will point to the |
|
1319 // cells in the next row to be deleted |
|
1320 do { |
|
1321 if (aRowIndex >= rowCount || colIndex >= colCount) |
|
1322 break; |
|
1323 |
|
1324 res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell), |
|
1325 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
1326 &actualRowSpan, &actualColSpan, &isSelected); |
|
1327 |
|
1328 // We don't fail if we don't find a cell, so this must be real bad |
|
1329 if(NS_FAILED(res)) return res; |
|
1330 |
|
1331 // Compensate for cells that don't start or extend below the row we are deleting |
|
1332 if (cell) |
|
1333 { |
|
1334 if (startRowIndex < aRowIndex) |
|
1335 { |
|
1336 // Cell starts in row above us |
|
1337 // Decrease its rowspan to keep table rectangular |
|
1338 // but we don't need to do this if rowspan=0, |
|
1339 // since it will automatically adjust |
|
1340 if (rowSpan > 0) |
|
1341 { |
|
1342 // Build list of cells to change rowspan |
|
1343 // We can't do it now since it upsets cell map, |
|
1344 // so we will do it after deleting the row |
|
1345 spanCellList.AppendElement(cell); |
|
1346 newSpanList.AppendElement(std::max((aRowIndex - startRowIndex), actualRowSpan-1)); |
|
1347 } |
|
1348 } |
|
1349 else |
|
1350 { |
|
1351 if (rowSpan > 1) |
|
1352 { |
|
1353 //Cell spans below row to delete, |
|
1354 // so we must insert new cells to keep rows below even |
|
1355 // Note that we test "rowSpan" so we don't do this if rowSpan = 0 (automatic readjustment) |
|
1356 res = SplitCellIntoRows(aTable, startRowIndex, startColIndex, |
|
1357 aRowIndex - startRowIndex + 1, // The row above the row to insert new cell into |
|
1358 actualRowSpan - 1, nullptr); // Span remaining below |
|
1359 NS_ENSURE_SUCCESS(res, res); |
|
1360 } |
|
1361 if (!cellInDeleteRow) |
|
1362 cellInDeleteRow = cell; // Reference cell to find row to delete |
|
1363 } |
|
1364 // Skip over other columns spanned by this cell |
|
1365 colIndex += actualColSpan; |
|
1366 } |
|
1367 } while (cell); |
|
1368 |
|
1369 // Things are messed up if we didn't find a cell in the row! |
|
1370 NS_ENSURE_TRUE(cellInDeleteRow, NS_ERROR_FAILURE); |
|
1371 |
|
1372 // Delete the entire row |
|
1373 nsCOMPtr<nsIDOMElement> parentRow; |
|
1374 res = GetElementOrParentByTagName(NS_LITERAL_STRING("tr"), cellInDeleteRow, getter_AddRefs(parentRow)); |
|
1375 NS_ENSURE_SUCCESS(res, res); |
|
1376 |
|
1377 if (parentRow) |
|
1378 { |
|
1379 res = DeleteNode(parentRow); |
|
1380 NS_ENSURE_SUCCESS(res, res); |
|
1381 } |
|
1382 |
|
1383 // Now we can set new rowspans for cells stored above |
|
1384 for (uint32_t i = 0, n = spanCellList.Length(); i < n; i++) |
|
1385 { |
|
1386 nsIDOMElement *cellPtr = spanCellList[i]; |
|
1387 if (cellPtr) |
|
1388 { |
|
1389 res = SetRowSpan(cellPtr, newSpanList[i]); |
|
1390 NS_ENSURE_SUCCESS(res, res); |
|
1391 } |
|
1392 } |
|
1393 return NS_OK; |
|
1394 } |
|
1395 |
|
1396 |
|
1397 NS_IMETHODIMP |
|
1398 nsHTMLEditor::SelectTable() |
|
1399 { |
|
1400 nsCOMPtr<nsIDOMElement> table; |
|
1401 nsresult res = NS_ERROR_FAILURE; |
|
1402 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr, getter_AddRefs(table)); |
|
1403 NS_ENSURE_SUCCESS(res, res); |
|
1404 // Don't fail if we didn't find a table |
|
1405 NS_ENSURE_TRUE(table, NS_OK); |
|
1406 |
|
1407 res = ClearSelection(); |
|
1408 if (NS_SUCCEEDED(res)) |
|
1409 res = AppendNodeToSelectionAsRange(table); |
|
1410 |
|
1411 return res; |
|
1412 } |
|
1413 |
|
1414 NS_IMETHODIMP |
|
1415 nsHTMLEditor::SelectTableCell() |
|
1416 { |
|
1417 nsCOMPtr<nsIDOMElement> cell; |
|
1418 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); |
|
1419 NS_ENSURE_SUCCESS(res, res); |
|
1420 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
1421 |
|
1422 res = ClearSelection(); |
|
1423 if (NS_SUCCEEDED(res)) |
|
1424 res = AppendNodeToSelectionAsRange(cell); |
|
1425 |
|
1426 return res; |
|
1427 } |
|
1428 |
|
1429 NS_IMETHODIMP |
|
1430 nsHTMLEditor::SelectBlockOfCells(nsIDOMElement *aStartCell, nsIDOMElement *aEndCell) |
|
1431 { |
|
1432 NS_ENSURE_TRUE(aStartCell && aEndCell, NS_ERROR_NULL_POINTER); |
|
1433 |
|
1434 nsCOMPtr<nsISelection> selection; |
|
1435 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
1436 NS_ENSURE_SUCCESS(res, res); |
|
1437 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
1438 |
|
1439 NS_NAMED_LITERAL_STRING(tableStr, "table"); |
|
1440 nsCOMPtr<nsIDOMElement> table; |
|
1441 res = GetElementOrParentByTagName(tableStr, aStartCell, getter_AddRefs(table)); |
|
1442 NS_ENSURE_SUCCESS(res, res); |
|
1443 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); |
|
1444 |
|
1445 nsCOMPtr<nsIDOMElement> endTable; |
|
1446 res = GetElementOrParentByTagName(tableStr, aEndCell, getter_AddRefs(endTable)); |
|
1447 NS_ENSURE_SUCCESS(res, res); |
|
1448 NS_ENSURE_TRUE(endTable, NS_ERROR_FAILURE); |
|
1449 |
|
1450 // We can only select a block if within the same table, |
|
1451 // so do nothing if not within one table |
|
1452 if (table != endTable) return NS_OK; |
|
1453 |
|
1454 int32_t startRowIndex, startColIndex, endRowIndex, endColIndex; |
|
1455 |
|
1456 // Get starting and ending cells' location in the cellmap |
|
1457 res = GetCellIndexes(aStartCell, &startRowIndex, &startColIndex); |
|
1458 if(NS_FAILED(res)) return res; |
|
1459 |
|
1460 res = GetCellIndexes(aEndCell, &endRowIndex, &endColIndex); |
|
1461 if(NS_FAILED(res)) return res; |
|
1462 |
|
1463 // Suppress nsISelectionListener notification |
|
1464 // until all selection changes are finished |
|
1465 nsSelectionBatcherForTable selectionBatcher(selection); |
|
1466 |
|
1467 // Examine all cell nodes in current selection and |
|
1468 // remove those outside the new block cell region |
|
1469 int32_t minColumn = std::min(startColIndex, endColIndex); |
|
1470 int32_t minRow = std::min(startRowIndex, endRowIndex); |
|
1471 int32_t maxColumn = std::max(startColIndex, endColIndex); |
|
1472 int32_t maxRow = std::max(startRowIndex, endRowIndex); |
|
1473 |
|
1474 nsCOMPtr<nsIDOMElement> cell; |
|
1475 int32_t currentRowIndex, currentColIndex; |
|
1476 nsCOMPtr<nsIDOMRange> range; |
|
1477 res = GetFirstSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); |
|
1478 NS_ENSURE_SUCCESS(res, res); |
|
1479 if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK; |
|
1480 |
|
1481 while (cell) |
|
1482 { |
|
1483 res = GetCellIndexes(cell, ¤tRowIndex, ¤tColIndex); |
|
1484 NS_ENSURE_SUCCESS(res, res); |
|
1485 |
|
1486 if (currentRowIndex < maxRow || currentRowIndex > maxRow || |
|
1487 currentColIndex < maxColumn || currentColIndex > maxColumn) |
|
1488 { |
|
1489 selection->RemoveRange(range); |
|
1490 // Since we've removed the range, decrement pointer to next range |
|
1491 mSelectedCellIndex--; |
|
1492 } |
|
1493 res = GetNextSelectedCell(getter_AddRefs(range), getter_AddRefs(cell)); |
|
1494 NS_ENSURE_SUCCESS(res, res); |
|
1495 } |
|
1496 |
|
1497 int32_t rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
1498 bool isSelected; |
|
1499 for (int32_t row = minRow; row <= maxRow; row++) |
|
1500 { |
|
1501 for(int32_t col = minColumn; col <= maxColumn; col += std::max(actualColSpan, 1)) |
|
1502 { |
|
1503 res = GetCellDataAt(table, row, col, getter_AddRefs(cell), |
|
1504 ¤tRowIndex, ¤tColIndex, |
|
1505 &rowSpan, &colSpan, |
|
1506 &actualRowSpan, &actualColSpan, &isSelected); |
|
1507 if (NS_FAILED(res)) break; |
|
1508 // Skip cells that already selected or are spanned from previous locations |
|
1509 if (!isSelected && cell && row == currentRowIndex && col == currentColIndex) |
|
1510 { |
|
1511 res = AppendNodeToSelectionAsRange(cell); |
|
1512 if (NS_FAILED(res)) break; |
|
1513 } |
|
1514 } |
|
1515 } |
|
1516 return res; |
|
1517 } |
|
1518 |
|
1519 NS_IMETHODIMP |
|
1520 nsHTMLEditor::SelectAllTableCells() |
|
1521 { |
|
1522 nsCOMPtr<nsIDOMElement> cell; |
|
1523 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); |
|
1524 NS_ENSURE_SUCCESS(res, res); |
|
1525 |
|
1526 // Don't fail if we didn't find a cell |
|
1527 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
1528 |
|
1529 nsCOMPtr<nsIDOMElement> startCell = cell; |
|
1530 |
|
1531 // Get parent table |
|
1532 nsCOMPtr<nsIDOMElement> table; |
|
1533 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table)); |
|
1534 NS_ENSURE_SUCCESS(res, res); |
|
1535 if(!table) return NS_ERROR_NULL_POINTER; |
|
1536 |
|
1537 int32_t rowCount, colCount; |
|
1538 res = GetTableSize(table, &rowCount, &colCount); |
|
1539 NS_ENSURE_SUCCESS(res, res); |
|
1540 |
|
1541 nsCOMPtr<nsISelection> selection; |
|
1542 res = GetSelection(getter_AddRefs(selection)); |
|
1543 NS_ENSURE_SUCCESS(res, res); |
|
1544 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
1545 |
|
1546 // Suppress nsISelectionListener notification |
|
1547 // until all selection changes are finished |
|
1548 nsSelectionBatcherForTable selectionBatcher(selection); |
|
1549 |
|
1550 // It is now safe to clear the selection |
|
1551 // BE SURE TO RESET IT BEFORE LEAVING! |
|
1552 res = ClearSelection(); |
|
1553 |
|
1554 // Select all cells in the same column as current cell |
|
1555 bool cellSelected = false; |
|
1556 int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; |
|
1557 bool isSelected; |
|
1558 for(int32_t row = 0; row < rowCount; row++) |
|
1559 { |
|
1560 for(int32_t col = 0; col < colCount; col += std::max(actualColSpan, 1)) |
|
1561 { |
|
1562 res = GetCellDataAt(table, row, col, getter_AddRefs(cell), |
|
1563 ¤tRowIndex, ¤tColIndex, |
|
1564 &rowSpan, &colSpan, |
|
1565 &actualRowSpan, &actualColSpan, &isSelected); |
|
1566 if (NS_FAILED(res)) break; |
|
1567 // Skip cells that are spanned from previous rows or columns |
|
1568 if (cell && row == currentRowIndex && col == currentColIndex) |
|
1569 { |
|
1570 res = AppendNodeToSelectionAsRange(cell); |
|
1571 if (NS_FAILED(res)) break; |
|
1572 cellSelected = true; |
|
1573 } |
|
1574 } |
|
1575 } |
|
1576 // Safety code to select starting cell if nothing else was selected |
|
1577 if (!cellSelected) |
|
1578 { |
|
1579 return AppendNodeToSelectionAsRange(startCell); |
|
1580 } |
|
1581 return res; |
|
1582 } |
|
1583 |
|
1584 NS_IMETHODIMP |
|
1585 nsHTMLEditor::SelectTableRow() |
|
1586 { |
|
1587 nsCOMPtr<nsIDOMElement> cell; |
|
1588 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); |
|
1589 NS_ENSURE_SUCCESS(res, res); |
|
1590 |
|
1591 // Don't fail if we didn't find a cell |
|
1592 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
1593 nsCOMPtr<nsIDOMElement> startCell = cell; |
|
1594 |
|
1595 // Get table and location of cell: |
|
1596 nsCOMPtr<nsISelection> selection; |
|
1597 nsCOMPtr<nsIDOMElement> table; |
|
1598 int32_t startRowIndex, startColIndex; |
|
1599 |
|
1600 res = GetCellContext(getter_AddRefs(selection), |
|
1601 getter_AddRefs(table), |
|
1602 getter_AddRefs(cell), |
|
1603 nullptr, nullptr, |
|
1604 &startRowIndex, &startColIndex); |
|
1605 NS_ENSURE_SUCCESS(res, res); |
|
1606 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); |
|
1607 |
|
1608 int32_t rowCount, colCount; |
|
1609 res = GetTableSize(table, &rowCount, &colCount); |
|
1610 NS_ENSURE_SUCCESS(res, res); |
|
1611 |
|
1612 //Note: At this point, we could get first and last cells in row, |
|
1613 // then call SelectBlockOfCells, but that would take just |
|
1614 // a little less code, so the following is more efficient |
|
1615 |
|
1616 // Suppress nsISelectionListener notification |
|
1617 // until all selection changes are finished |
|
1618 nsSelectionBatcherForTable selectionBatcher(selection); |
|
1619 |
|
1620 // It is now safe to clear the selection |
|
1621 // BE SURE TO RESET IT BEFORE LEAVING! |
|
1622 res = ClearSelection(); |
|
1623 |
|
1624 // Select all cells in the same row as current cell |
|
1625 bool cellSelected = false; |
|
1626 int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; |
|
1627 bool isSelected; |
|
1628 for(int32_t col = 0; col < colCount; col += std::max(actualColSpan, 1)) |
|
1629 { |
|
1630 res = GetCellDataAt(table, startRowIndex, col, getter_AddRefs(cell), |
|
1631 ¤tRowIndex, ¤tColIndex, &rowSpan, &colSpan, |
|
1632 &actualRowSpan, &actualColSpan, &isSelected); |
|
1633 if (NS_FAILED(res)) break; |
|
1634 // Skip cells that are spanned from previous rows or columns |
|
1635 if (cell && currentRowIndex == startRowIndex && currentColIndex == col) |
|
1636 { |
|
1637 res = AppendNodeToSelectionAsRange(cell); |
|
1638 if (NS_FAILED(res)) break; |
|
1639 cellSelected = true; |
|
1640 } |
|
1641 } |
|
1642 // Safety code to select starting cell if nothing else was selected |
|
1643 if (!cellSelected) |
|
1644 { |
|
1645 return AppendNodeToSelectionAsRange(startCell); |
|
1646 } |
|
1647 return res; |
|
1648 } |
|
1649 |
|
1650 NS_IMETHODIMP |
|
1651 nsHTMLEditor::SelectTableColumn() |
|
1652 { |
|
1653 nsCOMPtr<nsIDOMElement> cell; |
|
1654 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); |
|
1655 NS_ENSURE_SUCCESS(res, res); |
|
1656 |
|
1657 // Don't fail if we didn't find a cell |
|
1658 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
1659 |
|
1660 nsCOMPtr<nsIDOMElement> startCell = cell; |
|
1661 |
|
1662 // Get location of cell: |
|
1663 nsCOMPtr<nsISelection> selection; |
|
1664 nsCOMPtr<nsIDOMElement> table; |
|
1665 int32_t startRowIndex, startColIndex; |
|
1666 |
|
1667 res = GetCellContext(getter_AddRefs(selection), |
|
1668 getter_AddRefs(table), |
|
1669 getter_AddRefs(cell), |
|
1670 nullptr, nullptr, |
|
1671 &startRowIndex, &startColIndex); |
|
1672 NS_ENSURE_SUCCESS(res, res); |
|
1673 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); |
|
1674 |
|
1675 int32_t rowCount, colCount; |
|
1676 res = GetTableSize(table, &rowCount, &colCount); |
|
1677 NS_ENSURE_SUCCESS(res, res); |
|
1678 |
|
1679 // Suppress nsISelectionListener notification |
|
1680 // until all selection changes are finished |
|
1681 nsSelectionBatcherForTable selectionBatcher(selection); |
|
1682 |
|
1683 // It is now safe to clear the selection |
|
1684 // BE SURE TO RESET IT BEFORE LEAVING! |
|
1685 res = ClearSelection(); |
|
1686 |
|
1687 // Select all cells in the same column as current cell |
|
1688 bool cellSelected = false; |
|
1689 int32_t rowSpan, colSpan, actualRowSpan, actualColSpan, currentRowIndex, currentColIndex; |
|
1690 bool isSelected; |
|
1691 for(int32_t row = 0; row < rowCount; row += std::max(actualRowSpan, 1)) |
|
1692 { |
|
1693 res = GetCellDataAt(table, row, startColIndex, getter_AddRefs(cell), |
|
1694 ¤tRowIndex, ¤tColIndex, &rowSpan, &colSpan, |
|
1695 &actualRowSpan, &actualColSpan, &isSelected); |
|
1696 if (NS_FAILED(res)) break; |
|
1697 // Skip cells that are spanned from previous rows or columns |
|
1698 if (cell && currentRowIndex == row && currentColIndex == startColIndex) |
|
1699 { |
|
1700 res = AppendNodeToSelectionAsRange(cell); |
|
1701 if (NS_FAILED(res)) break; |
|
1702 cellSelected = true; |
|
1703 } |
|
1704 } |
|
1705 // Safety code to select starting cell if nothing else was selected |
|
1706 if (!cellSelected) |
|
1707 { |
|
1708 return AppendNodeToSelectionAsRange(startCell); |
|
1709 } |
|
1710 return res; |
|
1711 } |
|
1712 |
|
1713 NS_IMETHODIMP |
|
1714 nsHTMLEditor::SplitTableCell() |
|
1715 { |
|
1716 nsCOMPtr<nsIDOMElement> table; |
|
1717 nsCOMPtr<nsIDOMElement> cell; |
|
1718 int32_t startRowIndex, startColIndex, actualRowSpan, actualColSpan; |
|
1719 // Get cell, table, etc. at selection anchor node |
|
1720 nsresult res = GetCellContext(nullptr, |
|
1721 getter_AddRefs(table), |
|
1722 getter_AddRefs(cell), |
|
1723 nullptr, nullptr, |
|
1724 &startRowIndex, &startColIndex); |
|
1725 NS_ENSURE_SUCCESS(res, res); |
|
1726 if(!table || !cell) return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
1727 |
|
1728 // We need rowspan and colspan data |
|
1729 res = GetCellSpansAt(table, startRowIndex, startColIndex, actualRowSpan, actualColSpan); |
|
1730 NS_ENSURE_SUCCESS(res, res); |
|
1731 |
|
1732 // Must have some span to split |
|
1733 if (actualRowSpan <= 1 && actualColSpan <= 1) |
|
1734 return NS_OK; |
|
1735 |
|
1736 nsAutoEditBatch beginBatching(this); |
|
1737 // Prevent auto insertion of BR in new cell until we're done |
|
1738 nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); |
|
1739 |
|
1740 // We reset selection |
|
1741 nsSetSelectionAfterTableEdit setCaret(this, table, startRowIndex, startColIndex, ePreviousColumn, false); |
|
1742 //...so suppress Rules System selection munging |
|
1743 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
1744 |
|
1745 nsCOMPtr<nsIDOMElement> newCell; |
|
1746 int32_t rowIndex = startRowIndex; |
|
1747 int32_t rowSpanBelow, colSpanAfter; |
|
1748 |
|
1749 // Split up cell row-wise first into rowspan=1 above, and the rest below, |
|
1750 // whittling away at the cell below until no more extra span |
|
1751 for (rowSpanBelow = actualRowSpan-1; rowSpanBelow >= 0; rowSpanBelow--) |
|
1752 { |
|
1753 // We really split row-wise only if we had rowspan > 1 |
|
1754 if (rowSpanBelow > 0) |
|
1755 { |
|
1756 res = SplitCellIntoRows(table, rowIndex, startColIndex, 1, rowSpanBelow, getter_AddRefs(newCell)); |
|
1757 NS_ENSURE_SUCCESS(res, res); |
|
1758 CopyCellBackgroundColor(newCell, cell); |
|
1759 } |
|
1760 int32_t colIndex = startColIndex; |
|
1761 // Now split the cell with rowspan = 1 into cells if it has colSpan > 1 |
|
1762 for (colSpanAfter = actualColSpan-1; colSpanAfter > 0; colSpanAfter--) |
|
1763 { |
|
1764 res = SplitCellIntoColumns(table, rowIndex, colIndex, 1, colSpanAfter, getter_AddRefs(newCell)); |
|
1765 NS_ENSURE_SUCCESS(res, res); |
|
1766 CopyCellBackgroundColor(newCell, cell); |
|
1767 colIndex++; |
|
1768 } |
|
1769 // Point to the new cell and repeat |
|
1770 rowIndex++; |
|
1771 } |
|
1772 return res; |
|
1773 } |
|
1774 |
|
1775 nsresult |
|
1776 nsHTMLEditor::CopyCellBackgroundColor(nsIDOMElement *destCell, nsIDOMElement *sourceCell) |
|
1777 { |
|
1778 NS_ENSURE_TRUE(destCell && sourceCell, NS_ERROR_NULL_POINTER); |
|
1779 |
|
1780 // Copy backgournd color to new cell |
|
1781 NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor"); |
|
1782 nsAutoString color; |
|
1783 bool isSet; |
|
1784 nsresult res = GetAttributeValue(sourceCell, bgcolor, color, &isSet); |
|
1785 |
|
1786 if (NS_SUCCEEDED(res) && isSet) |
|
1787 res = SetAttribute(destCell, bgcolor, color); |
|
1788 |
|
1789 return res; |
|
1790 } |
|
1791 |
|
1792 NS_IMETHODIMP |
|
1793 nsHTMLEditor::SplitCellIntoColumns(nsIDOMElement *aTable, int32_t aRowIndex, int32_t aColIndex, |
|
1794 int32_t aColSpanLeft, int32_t aColSpanRight, |
|
1795 nsIDOMElement **aNewCell) |
|
1796 { |
|
1797 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); |
|
1798 if (aNewCell) *aNewCell = nullptr; |
|
1799 |
|
1800 nsCOMPtr<nsIDOMElement> cell; |
|
1801 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
1802 bool isSelected; |
|
1803 nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell), |
|
1804 &startRowIndex, &startColIndex, |
|
1805 &rowSpan, &colSpan, |
|
1806 &actualRowSpan, &actualColSpan, &isSelected); |
|
1807 NS_ENSURE_SUCCESS(res, res); |
|
1808 NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER); |
|
1809 |
|
1810 // We can't split! |
|
1811 if (actualColSpan <= 1 || (aColSpanLeft + aColSpanRight) > actualColSpan) |
|
1812 return NS_OK; |
|
1813 |
|
1814 // Reduce colspan of cell to split |
|
1815 res = SetColSpan(cell, aColSpanLeft); |
|
1816 NS_ENSURE_SUCCESS(res, res); |
|
1817 |
|
1818 // Insert new cell after using the remaining span |
|
1819 // and always get the new cell so we can copy the background color; |
|
1820 nsCOMPtr<nsIDOMElement> newCell; |
|
1821 res = InsertCell(cell, actualRowSpan, aColSpanRight, true, false, getter_AddRefs(newCell)); |
|
1822 NS_ENSURE_SUCCESS(res, res); |
|
1823 if (newCell) |
|
1824 { |
|
1825 if (aNewCell) |
|
1826 { |
|
1827 *aNewCell = newCell.get(); |
|
1828 NS_ADDREF(*aNewCell); |
|
1829 } |
|
1830 res = CopyCellBackgroundColor(newCell, cell); |
|
1831 } |
|
1832 return res; |
|
1833 } |
|
1834 |
|
1835 NS_IMETHODIMP |
|
1836 nsHTMLEditor::SplitCellIntoRows(nsIDOMElement *aTable, int32_t aRowIndex, int32_t aColIndex, |
|
1837 int32_t aRowSpanAbove, int32_t aRowSpanBelow, |
|
1838 nsIDOMElement **aNewCell) |
|
1839 { |
|
1840 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); |
|
1841 if (aNewCell) *aNewCell = nullptr; |
|
1842 |
|
1843 nsCOMPtr<nsIDOMElement> cell; |
|
1844 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
1845 bool isSelected; |
|
1846 nsresult res = GetCellDataAt(aTable, aRowIndex, aColIndex, getter_AddRefs(cell), |
|
1847 &startRowIndex, &startColIndex, |
|
1848 &rowSpan, &colSpan, |
|
1849 &actualRowSpan, &actualColSpan, &isSelected); |
|
1850 NS_ENSURE_SUCCESS(res, res); |
|
1851 NS_ENSURE_TRUE(cell, NS_ERROR_NULL_POINTER); |
|
1852 |
|
1853 // We can't split! |
|
1854 if (actualRowSpan <= 1 || (aRowSpanAbove + aRowSpanBelow) > actualRowSpan) |
|
1855 return NS_OK; |
|
1856 |
|
1857 int32_t rowCount, colCount; |
|
1858 res = GetTableSize(aTable, &rowCount, &colCount); |
|
1859 NS_ENSURE_SUCCESS(res, res); |
|
1860 |
|
1861 nsCOMPtr<nsIDOMElement> cell2; |
|
1862 nsCOMPtr<nsIDOMElement> lastCellFound; |
|
1863 int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; |
|
1864 bool isSelected2; |
|
1865 int32_t colIndex = 0; |
|
1866 bool insertAfter = (startColIndex > 0); |
|
1867 // This is the row we will insert new cell into |
|
1868 int32_t rowBelowIndex = startRowIndex+aRowSpanAbove; |
|
1869 |
|
1870 // Find a cell to insert before or after |
|
1871 do |
|
1872 { |
|
1873 // Search for a cell to insert before |
|
1874 res = GetCellDataAt(aTable, rowBelowIndex, |
|
1875 colIndex, getter_AddRefs(cell2), |
|
1876 &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2, |
|
1877 &actualRowSpan2, &actualColSpan2, &isSelected2); |
|
1878 // If we fail here, it could be because row has bad rowspan values, |
|
1879 // such as all cells having rowspan > 1 (Call FixRowSpan first!) |
|
1880 if (NS_FAILED(res) || !cell) return NS_ERROR_FAILURE; |
|
1881 |
|
1882 // Skip over cells spanned from above (like the one we are splitting!) |
|
1883 if (cell2 && startRowIndex2 == rowBelowIndex) |
|
1884 { |
|
1885 if (insertAfter) |
|
1886 { |
|
1887 // New cell isn't first in row, |
|
1888 // so stop after we find the cell just before new cell's column |
|
1889 if ((startColIndex2 + actualColSpan2) == startColIndex) |
|
1890 break; |
|
1891 |
|
1892 // If cell found is AFTER desired new cell colum, |
|
1893 // we have multiple cells with rowspan > 1 that |
|
1894 // prevented us from finding a cell to insert after... |
|
1895 if (startColIndex2 > startColIndex) |
|
1896 { |
|
1897 // ... so instead insert before the cell we found |
|
1898 insertAfter = false; |
|
1899 break; |
|
1900 } |
|
1901 } |
|
1902 else |
|
1903 { |
|
1904 break; // Inserting before, so stop at first cell in row we want to insert into |
|
1905 } |
|
1906 lastCellFound = cell2; |
|
1907 } |
|
1908 // Skip to next available cellmap location |
|
1909 colIndex += std::max(actualColSpan2, 1); |
|
1910 |
|
1911 // Done when past end of total number of columns |
|
1912 if (colIndex > colCount) |
|
1913 break; |
|
1914 |
|
1915 } while(true); |
|
1916 |
|
1917 if (!cell2 && lastCellFound) |
|
1918 { |
|
1919 // Edge case where we didn't find a cell to insert after |
|
1920 // or before because column(s) before desired column |
|
1921 // and all columns after it are spanned from above. |
|
1922 // We can insert after the last cell we found |
|
1923 cell2 = lastCellFound; |
|
1924 insertAfter = true; // Should always be true, but let's be sure |
|
1925 } |
|
1926 |
|
1927 // Reduce rowspan of cell to split |
|
1928 res = SetRowSpan(cell, aRowSpanAbove); |
|
1929 NS_ENSURE_SUCCESS(res, res); |
|
1930 |
|
1931 |
|
1932 // Insert new cell after using the remaining span |
|
1933 // and always get the new cell so we can copy the background color; |
|
1934 nsCOMPtr<nsIDOMElement> newCell; |
|
1935 res = InsertCell(cell2, aRowSpanBelow, actualColSpan, insertAfter, false, getter_AddRefs(newCell)); |
|
1936 NS_ENSURE_SUCCESS(res, res); |
|
1937 if (newCell) |
|
1938 { |
|
1939 if (aNewCell) |
|
1940 { |
|
1941 *aNewCell = newCell.get(); |
|
1942 NS_ADDREF(*aNewCell); |
|
1943 } |
|
1944 res = CopyCellBackgroundColor(newCell, cell2); |
|
1945 } |
|
1946 return res; |
|
1947 } |
|
1948 |
|
1949 NS_IMETHODIMP |
|
1950 nsHTMLEditor::SwitchTableCellHeaderType(nsIDOMElement *aSourceCell, nsIDOMElement **aNewCell) |
|
1951 { |
|
1952 NS_ENSURE_TRUE(aSourceCell, NS_ERROR_NULL_POINTER); |
|
1953 |
|
1954 nsAutoEditBatch beginBatching(this); |
|
1955 // Prevent auto insertion of BR in new cell created by ReplaceContainer |
|
1956 nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); |
|
1957 |
|
1958 nsCOMPtr<nsIDOMNode> newNode; |
|
1959 |
|
1960 // Save current selection to restore when done |
|
1961 // This is needed so ReplaceContainer can monitor selection |
|
1962 // when replacing nodes |
|
1963 nsRefPtr<Selection> selection = GetSelection(); |
|
1964 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
1965 nsAutoSelectionReset selectionResetter(selection, this); |
|
1966 |
|
1967 // Set to the opposite of current type |
|
1968 nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(aSourceCell); |
|
1969 nsString newCellType( (atom == nsEditProperty::td) ? NS_LITERAL_STRING("th") : NS_LITERAL_STRING("td")); |
|
1970 |
|
1971 // This creates new node, moves children, copies attributes (true) |
|
1972 // and manages the selection! |
|
1973 nsresult res = ReplaceContainer(aSourceCell, address_of(newNode), |
|
1974 newCellType, nullptr, nullptr, true); |
|
1975 NS_ENSURE_SUCCESS(res, res); |
|
1976 NS_ENSURE_TRUE(newNode, NS_ERROR_FAILURE); |
|
1977 |
|
1978 // Return the new cell |
|
1979 if (aNewCell) |
|
1980 { |
|
1981 nsCOMPtr<nsIDOMElement> newElement = do_QueryInterface(newNode); |
|
1982 *aNewCell = newElement.get(); |
|
1983 NS_ADDREF(*aNewCell); |
|
1984 } |
|
1985 |
|
1986 return NS_OK; |
|
1987 } |
|
1988 |
|
1989 NS_IMETHODIMP |
|
1990 nsHTMLEditor::JoinTableCells(bool aMergeNonContiguousContents) |
|
1991 { |
|
1992 nsCOMPtr<nsIDOMElement> table; |
|
1993 nsCOMPtr<nsIDOMElement> targetCell; |
|
1994 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
1995 bool isSelected; |
|
1996 nsCOMPtr<nsIDOMElement> cell2; |
|
1997 int32_t startRowIndex2, startColIndex2, rowSpan2, colSpan2, actualRowSpan2, actualColSpan2; |
|
1998 bool isSelected2; |
|
1999 |
|
2000 // Get cell, table, etc. at selection anchor node |
|
2001 nsresult res = GetCellContext(nullptr, |
|
2002 getter_AddRefs(table), |
|
2003 getter_AddRefs(targetCell), |
|
2004 nullptr, nullptr, |
|
2005 &startRowIndex, &startColIndex); |
|
2006 NS_ENSURE_SUCCESS(res, res); |
|
2007 if(!table || !targetCell) return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
2008 |
|
2009 nsAutoEditBatch beginBatching(this); |
|
2010 //Don't let Rules System change the selection |
|
2011 nsAutoTxnsConserveSelection dontChangeSelection(this); |
|
2012 |
|
2013 // Note: We dont' use nsSetSelectionAfterTableEdit here so the selection |
|
2014 // is retained after joining. This leaves the target cell selected |
|
2015 // as well as the "non-contiguous" cells, so user can see what happened. |
|
2016 |
|
2017 nsCOMPtr<nsIDOMElement> firstCell; |
|
2018 int32_t firstRowIndex, firstColIndex; |
|
2019 res = GetFirstSelectedCellInTable(&firstRowIndex, &firstColIndex, getter_AddRefs(firstCell)); |
|
2020 NS_ENSURE_SUCCESS(res, res); |
|
2021 |
|
2022 bool joinSelectedCells = false; |
|
2023 if (firstCell) |
|
2024 { |
|
2025 nsCOMPtr<nsIDOMElement> secondCell; |
|
2026 res = GetNextSelectedCell(nullptr, getter_AddRefs(secondCell)); |
|
2027 NS_ENSURE_SUCCESS(res, res); |
|
2028 |
|
2029 // If only one cell is selected, join with cell to the right |
|
2030 joinSelectedCells = (secondCell != nullptr); |
|
2031 } |
|
2032 |
|
2033 if (joinSelectedCells) |
|
2034 { |
|
2035 // We have selected cells: Join just contiguous cells |
|
2036 // and just merge contents if not contiguous |
|
2037 |
|
2038 int32_t rowCount, colCount; |
|
2039 res = GetTableSize(table, &rowCount, &colCount); |
|
2040 NS_ENSURE_SUCCESS(res, res); |
|
2041 |
|
2042 // Get spans for cell we will merge into |
|
2043 int32_t firstRowSpan, firstColSpan; |
|
2044 res = GetCellSpansAt( table, firstRowIndex, firstColIndex, firstRowSpan, firstColSpan); |
|
2045 NS_ENSURE_SUCCESS(res, res); |
|
2046 |
|
2047 // This defines the last indexes along the "edges" |
|
2048 // of the contiguous block of cells, telling us |
|
2049 // that we can join adjacent cells to the block |
|
2050 // Start with same as the first values, |
|
2051 // then expand as we find adjacent selected cells |
|
2052 int32_t lastRowIndex = firstRowIndex; |
|
2053 int32_t lastColIndex = firstColIndex; |
|
2054 int32_t rowIndex, colIndex; |
|
2055 |
|
2056 // First pass: Determine boundaries of contiguous rectangular block |
|
2057 // that we will join into one cell, |
|
2058 // favoring adjacent cells in the same row |
|
2059 for (rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++) |
|
2060 { |
|
2061 int32_t currentRowCount = rowCount; |
|
2062 // Be sure each row doesn't have rowspan errors |
|
2063 res = FixBadRowSpan(table, rowIndex, rowCount); |
|
2064 NS_ENSURE_SUCCESS(res, res); |
|
2065 // Adjust rowcount by number of rows we removed |
|
2066 lastRowIndex -= (currentRowCount-rowCount); |
|
2067 |
|
2068 bool cellFoundInRow = false; |
|
2069 bool lastRowIsSet = false; |
|
2070 int32_t lastColInRow = 0; |
|
2071 int32_t firstColInRow = firstColIndex; |
|
2072 for (colIndex = firstColIndex; colIndex < colCount; colIndex += std::max(actualColSpan2, 1)) |
|
2073 { |
|
2074 res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2), |
|
2075 &startRowIndex2, &startColIndex2, |
|
2076 &rowSpan2, &colSpan2, |
|
2077 &actualRowSpan2, &actualColSpan2, &isSelected2); |
|
2078 NS_ENSURE_SUCCESS(res, res); |
|
2079 |
|
2080 if (isSelected2) |
|
2081 { |
|
2082 if (!cellFoundInRow) |
|
2083 // We've just found the first selected cell in this row |
|
2084 firstColInRow = colIndex; |
|
2085 |
|
2086 if (rowIndex > firstRowIndex && firstColInRow != firstColIndex) |
|
2087 { |
|
2088 // We're in at least the second row, |
|
2089 // but left boundary is "ragged" (not the same as 1st row's start) |
|
2090 //Let's just end block on previous row |
|
2091 // and keep previous lastColIndex |
|
2092 //TODO: We could try to find the Maximum firstColInRow |
|
2093 // so our block can still extend down more rows? |
|
2094 lastRowIndex = std::max(0,rowIndex - 1); |
|
2095 lastRowIsSet = true; |
|
2096 break; |
|
2097 } |
|
2098 // Save max selected column in this row, including extra colspan |
|
2099 lastColInRow = colIndex + (actualColSpan2-1); |
|
2100 cellFoundInRow = true; |
|
2101 } |
|
2102 else if (cellFoundInRow) |
|
2103 { |
|
2104 // No cell or not selected, but at least one cell in row was found |
|
2105 |
|
2106 if (rowIndex > (firstRowIndex+1) && colIndex <= lastColIndex) |
|
2107 { |
|
2108 // Cell is in a column less than current right border in |
|
2109 // the third or higher selected row, so stop block at the previous row |
|
2110 lastRowIndex = std::max(0,rowIndex - 1); |
|
2111 lastRowIsSet = true; |
|
2112 } |
|
2113 // We're done with this row |
|
2114 break; |
|
2115 } |
|
2116 } // End of column loop |
|
2117 |
|
2118 // Done with this row |
|
2119 if (cellFoundInRow) |
|
2120 { |
|
2121 if (rowIndex == firstRowIndex) |
|
2122 { |
|
2123 // First row always initializes the right boundary |
|
2124 lastColIndex = lastColInRow; |
|
2125 } |
|
2126 |
|
2127 // If we didn't determine last row above... |
|
2128 if (!lastRowIsSet) |
|
2129 { |
|
2130 if (colIndex < lastColIndex) |
|
2131 { |
|
2132 // (don't think we ever get here?) |
|
2133 // Cell is in a column less than current right boundary, |
|
2134 // so stop block at the previous row |
|
2135 lastRowIndex = std::max(0,rowIndex - 1); |
|
2136 } |
|
2137 else |
|
2138 { |
|
2139 // Go on to examine next row |
|
2140 lastRowIndex = rowIndex+1; |
|
2141 } |
|
2142 } |
|
2143 // Use the minimum col we found so far for right boundary |
|
2144 lastColIndex = std::min(lastColIndex, lastColInRow); |
|
2145 } |
|
2146 else |
|
2147 { |
|
2148 // No selected cells in this row -- stop at row above |
|
2149 // and leave last column at its previous value |
|
2150 lastRowIndex = std::max(0,rowIndex - 1); |
|
2151 } |
|
2152 } |
|
2153 |
|
2154 // The list of cells we will delete after joining |
|
2155 nsTArray<nsCOMPtr<nsIDOMElement> > deleteList; |
|
2156 |
|
2157 // 2nd pass: Do the joining and merging |
|
2158 for (rowIndex = 0; rowIndex < rowCount; rowIndex++) |
|
2159 { |
|
2160 for (colIndex = 0; colIndex < colCount; colIndex += std::max(actualColSpan2, 1)) |
|
2161 { |
|
2162 res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell2), |
|
2163 &startRowIndex2, &startColIndex2, |
|
2164 &rowSpan2, &colSpan2, |
|
2165 &actualRowSpan2, &actualColSpan2, &isSelected2); |
|
2166 NS_ENSURE_SUCCESS(res, res); |
|
2167 |
|
2168 // If this is 0, we are past last cell in row, so exit the loop |
|
2169 if (actualColSpan2 == 0) |
|
2170 break; |
|
2171 |
|
2172 // Merge only selected cells (skip cell we're merging into, of course) |
|
2173 if (isSelected2 && cell2 != firstCell) |
|
2174 { |
|
2175 if (rowIndex >= firstRowIndex && rowIndex <= lastRowIndex && |
|
2176 colIndex >= firstColIndex && colIndex <= lastColIndex) |
|
2177 { |
|
2178 // We are within the join region |
|
2179 // Problem: It is very tricky to delete cells as we merge, |
|
2180 // since that will upset the cellmap |
|
2181 // Instead, build a list of cells to delete and do it later |
|
2182 NS_ASSERTION(startRowIndex2 == rowIndex, "JoinTableCells: StartRowIndex is in row above"); |
|
2183 |
|
2184 if (actualColSpan2 > 1) |
|
2185 { |
|
2186 //Check if cell "hangs" off the boundary because of colspan > 1 |
|
2187 // Use split methods to chop off excess |
|
2188 int32_t extraColSpan = (startColIndex2 + actualColSpan2) - (lastColIndex+1); |
|
2189 if ( extraColSpan > 0) |
|
2190 { |
|
2191 res = SplitCellIntoColumns(table, startRowIndex2, startColIndex2, |
|
2192 actualColSpan2-extraColSpan, extraColSpan, nullptr); |
|
2193 NS_ENSURE_SUCCESS(res, res); |
|
2194 } |
|
2195 } |
|
2196 |
|
2197 res = MergeCells(firstCell, cell2, false); |
|
2198 NS_ENSURE_SUCCESS(res, res); |
|
2199 |
|
2200 // Add cell to list to delete |
|
2201 deleteList.AppendElement(cell2.get()); |
|
2202 } |
|
2203 else if (aMergeNonContiguousContents) |
|
2204 { |
|
2205 // Cell is outside join region -- just merge the contents |
|
2206 res = MergeCells(firstCell, cell2, false); |
|
2207 NS_ENSURE_SUCCESS(res, res); |
|
2208 } |
|
2209 } |
|
2210 } |
|
2211 } |
|
2212 |
|
2213 // All cell contents are merged. Delete the empty cells we accumulated |
|
2214 // Prevent rules testing until we're done |
|
2215 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
2216 |
|
2217 for (uint32_t i = 0, n = deleteList.Length(); i < n; i++) |
|
2218 { |
|
2219 nsIDOMElement *elementPtr = deleteList[i]; |
|
2220 if (elementPtr) |
|
2221 { |
|
2222 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(elementPtr); |
|
2223 res = DeleteNode(node); |
|
2224 NS_ENSURE_SUCCESS(res, res); |
|
2225 } |
|
2226 } |
|
2227 // Cleanup selection: remove ranges where cells were deleted |
|
2228 nsCOMPtr<nsISelection> selection; |
|
2229 res = GetSelection(getter_AddRefs(selection)); |
|
2230 NS_ENSURE_SUCCESS(res, res); |
|
2231 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
2232 |
|
2233 int32_t rangeCount; |
|
2234 res = selection->GetRangeCount(&rangeCount); |
|
2235 NS_ENSURE_SUCCESS(res, res); |
|
2236 |
|
2237 nsCOMPtr<nsIDOMRange> range; |
|
2238 int32_t i; |
|
2239 for (i = 0; i < rangeCount; i++) |
|
2240 { |
|
2241 res = selection->GetRangeAt(i, getter_AddRefs(range)); |
|
2242 NS_ENSURE_SUCCESS(res, res); |
|
2243 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); |
|
2244 |
|
2245 nsCOMPtr<nsIDOMElement> deletedCell; |
|
2246 res = GetCellFromRange(range, getter_AddRefs(deletedCell)); |
|
2247 if (!deletedCell) |
|
2248 { |
|
2249 selection->RemoveRange(range); |
|
2250 rangeCount--; |
|
2251 i--; |
|
2252 } |
|
2253 } |
|
2254 |
|
2255 // Set spans for the cell everthing merged into |
|
2256 res = SetRowSpan(firstCell, lastRowIndex-firstRowIndex+1); |
|
2257 NS_ENSURE_SUCCESS(res, res); |
|
2258 res = SetColSpan(firstCell, lastColIndex-firstColIndex+1); |
|
2259 NS_ENSURE_SUCCESS(res, res); |
|
2260 |
|
2261 |
|
2262 // Fixup disturbances in table layout |
|
2263 NormalizeTable(table); |
|
2264 } |
|
2265 else |
|
2266 { |
|
2267 // Joining with cell to the right -- get rowspan and colspan data of target cell |
|
2268 res = GetCellDataAt(table, startRowIndex, startColIndex, getter_AddRefs(targetCell), |
|
2269 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
2270 &actualRowSpan, &actualColSpan, &isSelected); |
|
2271 NS_ENSURE_SUCCESS(res, res); |
|
2272 NS_ENSURE_TRUE(targetCell, NS_ERROR_NULL_POINTER); |
|
2273 |
|
2274 // Get data for cell to the right |
|
2275 res = GetCellDataAt(table, startRowIndex, startColIndex+actualColSpan, getter_AddRefs(cell2), |
|
2276 &startRowIndex2, &startColIndex2, &rowSpan2, &colSpan2, |
|
2277 &actualRowSpan2, &actualColSpan2, &isSelected2); |
|
2278 NS_ENSURE_SUCCESS(res, res); |
|
2279 if(!cell2) return NS_OK; // Don't fail if there's no cell |
|
2280 |
|
2281 // sanity check |
|
2282 NS_ASSERTION((startRowIndex >= startRowIndex2),"JoinCells: startRowIndex < startRowIndex2"); |
|
2283 |
|
2284 // Figure out span of merged cell starting from target's starting row |
|
2285 // to handle case of merged cell starting in a row above |
|
2286 int32_t spanAboveMergedCell = startRowIndex - startRowIndex2; |
|
2287 int32_t effectiveRowSpan2 = actualRowSpan2 - spanAboveMergedCell; |
|
2288 |
|
2289 if (effectiveRowSpan2 > actualRowSpan) |
|
2290 { |
|
2291 // Cell to the right spans into row below target |
|
2292 // Split off portion below target cell's bottom-most row |
|
2293 res = SplitCellIntoRows(table, startRowIndex2, startColIndex2, |
|
2294 spanAboveMergedCell+actualRowSpan, |
|
2295 effectiveRowSpan2-actualRowSpan, nullptr); |
|
2296 NS_ENSURE_SUCCESS(res, res); |
|
2297 } |
|
2298 |
|
2299 // Move contents from cell to the right |
|
2300 // Delete the cell now only if it starts in the same row |
|
2301 // and has enough row "height" |
|
2302 res = MergeCells(targetCell, cell2, |
|
2303 (startRowIndex2 == startRowIndex) && |
|
2304 (effectiveRowSpan2 >= actualRowSpan)); |
|
2305 NS_ENSURE_SUCCESS(res, res); |
|
2306 |
|
2307 if (effectiveRowSpan2 < actualRowSpan) |
|
2308 { |
|
2309 // Merged cell is "shorter" |
|
2310 // (there are cells(s) below it that are row-spanned by target cell) |
|
2311 // We could try splitting those cells, but that's REAL messy, |
|
2312 // so the safest thing to do is NOT really join the cells |
|
2313 return NS_OK; |
|
2314 } |
|
2315 |
|
2316 if( spanAboveMergedCell > 0 ) |
|
2317 { |
|
2318 // Cell we merged started in a row above the target cell |
|
2319 // Reduce rowspan to give room where target cell will extend its colspan |
|
2320 res = SetRowSpan(cell2, spanAboveMergedCell); |
|
2321 NS_ENSURE_SUCCESS(res, res); |
|
2322 } |
|
2323 |
|
2324 // Reset target cell's colspan to encompass cell to the right |
|
2325 res = SetColSpan(targetCell, actualColSpan+actualColSpan2); |
|
2326 NS_ENSURE_SUCCESS(res, res); |
|
2327 } |
|
2328 return res; |
|
2329 } |
|
2330 |
|
2331 NS_IMETHODIMP |
|
2332 nsHTMLEditor::MergeCells(nsCOMPtr<nsIDOMElement> aTargetCell, |
|
2333 nsCOMPtr<nsIDOMElement> aCellToMerge, |
|
2334 bool aDeleteCellToMerge) |
|
2335 { |
|
2336 nsCOMPtr<dom::Element> targetCell = do_QueryInterface(aTargetCell); |
|
2337 nsCOMPtr<dom::Element> cellToMerge = do_QueryInterface(aCellToMerge); |
|
2338 NS_ENSURE_TRUE(targetCell && cellToMerge, NS_ERROR_NULL_POINTER); |
|
2339 |
|
2340 // Prevent rules testing until we're done |
|
2341 nsAutoRules beginRulesSniffing(this, EditAction::deleteNode, nsIEditor::eNext); |
|
2342 |
|
2343 // Don't need to merge if cell is empty |
|
2344 if (!IsEmptyCell(cellToMerge)) { |
|
2345 // Get index of last child in target cell |
|
2346 // If we fail or don't have children, |
|
2347 // we insert at index 0 |
|
2348 int32_t insertIndex = 0; |
|
2349 |
|
2350 // Start inserting just after last child |
|
2351 uint32_t len = targetCell->GetChildCount(); |
|
2352 if (len == 1 && IsEmptyCell(targetCell)) { |
|
2353 // Delete the empty node |
|
2354 nsIContent* cellChild = targetCell->GetFirstChild(); |
|
2355 nsresult res = DeleteNode(cellChild->AsDOMNode()); |
|
2356 NS_ENSURE_SUCCESS(res, res); |
|
2357 insertIndex = 0; |
|
2358 } else { |
|
2359 insertIndex = (int32_t)len; |
|
2360 } |
|
2361 |
|
2362 // Move the contents |
|
2363 while (cellToMerge->HasChildren()) { |
|
2364 nsCOMPtr<nsIDOMNode> cellChild = cellToMerge->GetLastChild()->AsDOMNode(); |
|
2365 nsresult res = DeleteNode(cellChild); |
|
2366 NS_ENSURE_SUCCESS(res, res); |
|
2367 |
|
2368 res = InsertNode(cellChild, aTargetCell, insertIndex); |
|
2369 NS_ENSURE_SUCCESS(res, res); |
|
2370 } |
|
2371 } |
|
2372 |
|
2373 // Delete cells whose contents were moved |
|
2374 if (aDeleteCellToMerge) |
|
2375 return DeleteNode(aCellToMerge); |
|
2376 |
|
2377 return NS_OK; |
|
2378 } |
|
2379 |
|
2380 |
|
2381 NS_IMETHODIMP |
|
2382 nsHTMLEditor::FixBadRowSpan(nsIDOMElement *aTable, int32_t aRowIndex, int32_t& aNewRowCount) |
|
2383 { |
|
2384 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); |
|
2385 |
|
2386 int32_t rowCount, colCount; |
|
2387 nsresult res = GetTableSize(aTable, &rowCount, &colCount); |
|
2388 NS_ENSURE_SUCCESS(res, res); |
|
2389 |
|
2390 nsCOMPtr<nsIDOMElement>cell; |
|
2391 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
2392 bool isSelected; |
|
2393 |
|
2394 int32_t minRowSpan = -1; |
|
2395 int32_t colIndex; |
|
2396 |
|
2397 for( colIndex = 0; colIndex < colCount; colIndex += std::max(actualColSpan, 1)) |
|
2398 { |
|
2399 res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell), |
|
2400 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
2401 &actualRowSpan, &actualColSpan, &isSelected); |
|
2402 // NOTE: This is a *real* failure. |
|
2403 // GetCellDataAt passes if cell is missing from cellmap |
|
2404 if(NS_FAILED(res)) return res; |
|
2405 if (!cell) break; |
|
2406 if(rowSpan > 0 && |
|
2407 startRowIndex == aRowIndex && |
|
2408 (rowSpan < minRowSpan || minRowSpan == -1)) |
|
2409 { |
|
2410 minRowSpan = rowSpan; |
|
2411 } |
|
2412 NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan"); |
|
2413 } |
|
2414 if(minRowSpan > 1) |
|
2415 { |
|
2416 // The amount to reduce everyone's rowspan |
|
2417 // so at least one cell has rowspan = 1 |
|
2418 int32_t rowsReduced = minRowSpan - 1; |
|
2419 for(colIndex = 0; colIndex < colCount; colIndex += std::max(actualColSpan, 1)) |
|
2420 { |
|
2421 res = GetCellDataAt(aTable, aRowIndex, colIndex, getter_AddRefs(cell), |
|
2422 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
2423 &actualRowSpan, &actualColSpan, &isSelected); |
|
2424 if(NS_FAILED(res)) return res; |
|
2425 // Fixup rowspans only for cells starting in current row |
|
2426 if(cell && rowSpan > 0 && |
|
2427 startRowIndex == aRowIndex && |
|
2428 startColIndex == colIndex ) |
|
2429 { |
|
2430 res = SetRowSpan(cell, rowSpan-rowsReduced); |
|
2431 if(NS_FAILED(res)) return res; |
|
2432 } |
|
2433 NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in FixBadRowSpan"); |
|
2434 } |
|
2435 } |
|
2436 return GetTableSize(aTable, &aNewRowCount, &colCount); |
|
2437 } |
|
2438 |
|
2439 NS_IMETHODIMP |
|
2440 nsHTMLEditor::FixBadColSpan(nsIDOMElement *aTable, int32_t aColIndex, int32_t& aNewColCount) |
|
2441 { |
|
2442 NS_ENSURE_TRUE(aTable, NS_ERROR_NULL_POINTER); |
|
2443 |
|
2444 int32_t rowCount, colCount; |
|
2445 nsresult res = GetTableSize(aTable, &rowCount, &colCount); |
|
2446 NS_ENSURE_SUCCESS(res, res); |
|
2447 |
|
2448 nsCOMPtr<nsIDOMElement> cell; |
|
2449 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
2450 bool isSelected; |
|
2451 |
|
2452 int32_t minColSpan = -1; |
|
2453 int32_t rowIndex; |
|
2454 |
|
2455 for( rowIndex = 0; rowIndex < rowCount; rowIndex += std::max(actualRowSpan, 1)) |
|
2456 { |
|
2457 res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell), |
|
2458 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
2459 &actualRowSpan, &actualColSpan, &isSelected); |
|
2460 // NOTE: This is a *real* failure. |
|
2461 // GetCellDataAt passes if cell is missing from cellmap |
|
2462 if(NS_FAILED(res)) return res; |
|
2463 if (!cell) break; |
|
2464 if(colSpan > 0 && |
|
2465 startColIndex == aColIndex && |
|
2466 (colSpan < minColSpan || minColSpan == -1)) |
|
2467 { |
|
2468 minColSpan = colSpan; |
|
2469 } |
|
2470 NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan"); |
|
2471 } |
|
2472 if(minColSpan > 1) |
|
2473 { |
|
2474 // The amount to reduce everyone's colspan |
|
2475 // so at least one cell has colspan = 1 |
|
2476 int32_t colsReduced = minColSpan - 1; |
|
2477 for(rowIndex = 0; rowIndex < rowCount; rowIndex += std::max(actualRowSpan, 1)) |
|
2478 { |
|
2479 res = GetCellDataAt(aTable, rowIndex, aColIndex, getter_AddRefs(cell), |
|
2480 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
2481 &actualRowSpan, &actualColSpan, &isSelected); |
|
2482 if(NS_FAILED(res)) return res; |
|
2483 // Fixup colspans only for cells starting in current column |
|
2484 if(cell && colSpan > 0 && |
|
2485 startColIndex == aColIndex && |
|
2486 startRowIndex == rowIndex ) |
|
2487 { |
|
2488 res = SetColSpan(cell, colSpan-colsReduced); |
|
2489 if(NS_FAILED(res)) return res; |
|
2490 } |
|
2491 NS_ASSERTION((actualRowSpan > 0),"ActualRowSpan = 0 in FixBadColSpan"); |
|
2492 } |
|
2493 } |
|
2494 return GetTableSize(aTable, &rowCount, &aNewColCount); |
|
2495 } |
|
2496 |
|
2497 NS_IMETHODIMP |
|
2498 nsHTMLEditor::NormalizeTable(nsIDOMElement *aTable) |
|
2499 { |
|
2500 nsRefPtr<Selection> selection = GetSelection(); |
|
2501 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
2502 |
|
2503 nsCOMPtr<nsIDOMElement> table; |
|
2504 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), |
|
2505 aTable, getter_AddRefs(table)); |
|
2506 NS_ENSURE_SUCCESS(res, res); |
|
2507 // Don't fail if we didn't find a table |
|
2508 NS_ENSURE_TRUE(table, NS_OK); |
|
2509 |
|
2510 int32_t rowCount, colCount, rowIndex, colIndex; |
|
2511 res = GetTableSize(table, &rowCount, &colCount); |
|
2512 NS_ENSURE_SUCCESS(res, res); |
|
2513 |
|
2514 // Save current selection |
|
2515 nsAutoSelectionReset selectionResetter(selection, this); |
|
2516 |
|
2517 nsAutoEditBatch beginBatching(this); |
|
2518 // Prevent auto insertion of BR in new cell until we're done |
|
2519 nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext); |
|
2520 |
|
2521 nsCOMPtr<nsIDOMElement> cell; |
|
2522 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
2523 bool isSelected; |
|
2524 |
|
2525 // Scan all cells in each row to detect bad rowspan values |
|
2526 for(rowIndex = 0; rowIndex < rowCount; rowIndex++) |
|
2527 { |
|
2528 res = FixBadRowSpan(table, rowIndex, rowCount); |
|
2529 NS_ENSURE_SUCCESS(res, res); |
|
2530 } |
|
2531 // and same for colspans |
|
2532 for(colIndex = 0; colIndex < colCount; colIndex++) |
|
2533 { |
|
2534 res = FixBadColSpan(table, colIndex, colCount); |
|
2535 NS_ENSURE_SUCCESS(res, res); |
|
2536 } |
|
2537 |
|
2538 // Fill in missing cellmap locations with empty cells |
|
2539 for(rowIndex = 0; rowIndex < rowCount; rowIndex++) |
|
2540 { |
|
2541 nsCOMPtr<nsIDOMElement> previousCellInRow; |
|
2542 |
|
2543 for(colIndex = 0; colIndex < colCount; colIndex++) |
|
2544 { |
|
2545 res = GetCellDataAt(table, rowIndex, colIndex, getter_AddRefs(cell), |
|
2546 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
2547 &actualRowSpan, &actualColSpan, &isSelected); |
|
2548 // NOTE: This is a *real* failure. |
|
2549 // GetCellDataAt passes if cell is missing from cellmap |
|
2550 if(NS_FAILED(res)) return res; |
|
2551 if (!cell) |
|
2552 { |
|
2553 //We are missing a cell at a cellmap location |
|
2554 #ifdef DEBUG |
|
2555 printf("NormalizeTable found missing cell at row=%d, col=%d\n", rowIndex, colIndex); |
|
2556 #endif |
|
2557 // Add a cell after the previous Cell in the current row |
|
2558 if(previousCellInRow) |
|
2559 { |
|
2560 // Insert a new cell after (true), and return the new cell to us |
|
2561 res = InsertCell(previousCellInRow, 1, 1, true, false, getter_AddRefs(cell)); |
|
2562 NS_ENSURE_SUCCESS(res, res); |
|
2563 |
|
2564 // Set this so we use returned new "cell" to set previousCellInRow below |
|
2565 if(cell) |
|
2566 startRowIndex = rowIndex; |
|
2567 } else { |
|
2568 // We don't have any cells in this row -- We are really messed up! |
|
2569 #ifdef DEBUG |
|
2570 printf("NormalizeTable found no cells in row=%d, col=%d\n", rowIndex, colIndex); |
|
2571 #endif |
|
2572 return NS_ERROR_FAILURE; |
|
2573 } |
|
2574 } |
|
2575 // Save the last cell found in the same row we are scanning |
|
2576 if(startRowIndex == rowIndex) |
|
2577 { |
|
2578 previousCellInRow = cell; |
|
2579 } |
|
2580 } |
|
2581 } |
|
2582 return res; |
|
2583 } |
|
2584 |
|
2585 NS_IMETHODIMP |
|
2586 nsHTMLEditor::GetCellIndexes(nsIDOMElement *aCell, |
|
2587 int32_t *aRowIndex, int32_t *aColIndex) |
|
2588 { |
|
2589 NS_ENSURE_ARG_POINTER(aRowIndex); |
|
2590 *aColIndex=0; // initialize out params |
|
2591 NS_ENSURE_ARG_POINTER(aColIndex); |
|
2592 *aRowIndex=0; |
|
2593 nsresult res=NS_ERROR_NOT_INITIALIZED; |
|
2594 if (!aCell) |
|
2595 { |
|
2596 // Get the selected cell or the cell enclosing the selection anchor |
|
2597 nsCOMPtr<nsIDOMElement> cell; |
|
2598 res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nullptr, getter_AddRefs(cell)); |
|
2599 if (NS_SUCCEEDED(res) && cell) |
|
2600 aCell = cell; |
|
2601 else |
|
2602 return NS_ERROR_FAILURE; |
|
2603 } |
|
2604 |
|
2605 NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED); |
|
2606 nsCOMPtr<nsIPresShell> ps = GetPresShell(); |
|
2607 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED); |
|
2608 |
|
2609 nsCOMPtr<nsIContent> nodeAsContent( do_QueryInterface(aCell) ); |
|
2610 NS_ENSURE_TRUE(nodeAsContent, NS_ERROR_FAILURE); |
|
2611 // frames are not ref counted, so don't use an nsCOMPtr |
|
2612 nsIFrame *layoutObject = nodeAsContent->GetPrimaryFrame(); |
|
2613 NS_ENSURE_TRUE(layoutObject, NS_ERROR_FAILURE); |
|
2614 |
|
2615 nsITableCellLayout *cellLayoutObject = do_QueryFrame(layoutObject); |
|
2616 NS_ENSURE_TRUE(cellLayoutObject, NS_ERROR_FAILURE); |
|
2617 return cellLayoutObject->GetCellIndexes(*aRowIndex, *aColIndex); |
|
2618 } |
|
2619 |
|
2620 nsTableOuterFrame* |
|
2621 nsHTMLEditor::GetTableFrame(nsIDOMElement* aTable) |
|
2622 { |
|
2623 NS_ENSURE_TRUE(aTable, nullptr); |
|
2624 |
|
2625 nsCOMPtr<nsIContent> nodeAsContent( do_QueryInterface(aTable) ); |
|
2626 NS_ENSURE_TRUE(nodeAsContent, nullptr); |
|
2627 return do_QueryFrame(nodeAsContent->GetPrimaryFrame()); |
|
2628 } |
|
2629 |
|
2630 //Return actual number of cells (a cell with colspan > 1 counts as just 1) |
|
2631 int32_t nsHTMLEditor::GetNumberOfCellsInRow(nsIDOMElement* aTable, int32_t rowIndex) |
|
2632 { |
|
2633 int32_t cellCount = 0; |
|
2634 nsCOMPtr<nsIDOMElement> cell; |
|
2635 int32_t colIndex = 0; |
|
2636 nsresult res; |
|
2637 do { |
|
2638 int32_t startRowIndex, startColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
2639 bool isSelected; |
|
2640 res = GetCellDataAt(aTable, rowIndex, colIndex, getter_AddRefs(cell), |
|
2641 &startRowIndex, &startColIndex, &rowSpan, &colSpan, |
|
2642 &actualRowSpan, &actualColSpan, &isSelected); |
|
2643 NS_ENSURE_SUCCESS(res, 0); |
|
2644 if (cell) |
|
2645 { |
|
2646 // Only count cells that start in row we are working with |
|
2647 if (startRowIndex == rowIndex) |
|
2648 cellCount++; |
|
2649 |
|
2650 //Next possible location for a cell |
|
2651 colIndex += actualColSpan; |
|
2652 } |
|
2653 else |
|
2654 colIndex++; |
|
2655 |
|
2656 } while (cell); |
|
2657 |
|
2658 return cellCount; |
|
2659 } |
|
2660 |
|
2661 /* Not scriptable: For convenience in C++ |
|
2662 Use GetTableRowCount and GetTableColumnCount from JavaScript |
|
2663 */ |
|
2664 NS_IMETHODIMP |
|
2665 nsHTMLEditor::GetTableSize(nsIDOMElement *aTable, |
|
2666 int32_t* aRowCount, int32_t* aColCount) |
|
2667 { |
|
2668 NS_ENSURE_ARG_POINTER(aRowCount); |
|
2669 NS_ENSURE_ARG_POINTER(aColCount); |
|
2670 nsresult res; |
|
2671 *aRowCount = 0; |
|
2672 *aColCount = 0; |
|
2673 nsCOMPtr<nsIDOMElement> table; |
|
2674 // Get the selected talbe or the table enclosing the selection anchor |
|
2675 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aTable, getter_AddRefs(table)); |
|
2676 NS_ENSURE_SUCCESS(res, res); |
|
2677 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); |
|
2678 |
|
2679 nsTableOuterFrame* tableFrame = GetTableFrame(table.get()); |
|
2680 NS_ENSURE_TRUE(tableFrame, NS_ERROR_FAILURE); |
|
2681 |
|
2682 *aRowCount = tableFrame->GetRowCount(); |
|
2683 *aColCount = tableFrame->GetColCount(); |
|
2684 |
|
2685 return NS_OK; |
|
2686 } |
|
2687 |
|
2688 NS_IMETHODIMP |
|
2689 nsHTMLEditor::GetCellDataAt(nsIDOMElement* aTable, int32_t aRowIndex, |
|
2690 int32_t aColIndex, nsIDOMElement **aCell, |
|
2691 int32_t* aStartRowIndex, int32_t* aStartColIndex, |
|
2692 int32_t* aRowSpan, int32_t* aColSpan, |
|
2693 int32_t* aActualRowSpan, int32_t* aActualColSpan, |
|
2694 bool* aIsSelected) |
|
2695 { |
|
2696 NS_ENSURE_ARG_POINTER(aStartRowIndex); |
|
2697 NS_ENSURE_ARG_POINTER(aStartColIndex); |
|
2698 NS_ENSURE_ARG_POINTER(aRowSpan); |
|
2699 NS_ENSURE_ARG_POINTER(aColSpan); |
|
2700 NS_ENSURE_ARG_POINTER(aActualRowSpan); |
|
2701 NS_ENSURE_ARG_POINTER(aActualColSpan); |
|
2702 NS_ENSURE_ARG_POINTER(aIsSelected); |
|
2703 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
2704 |
|
2705 nsresult res=NS_ERROR_FAILURE; |
|
2706 *aStartRowIndex = 0; |
|
2707 *aStartColIndex = 0; |
|
2708 *aRowSpan = 0; |
|
2709 *aColSpan = 0; |
|
2710 *aActualRowSpan = 0; |
|
2711 *aActualColSpan = 0; |
|
2712 *aIsSelected = false; |
|
2713 |
|
2714 *aCell = nullptr; |
|
2715 |
|
2716 if (!aTable) |
|
2717 { |
|
2718 // Get the selected table or the table enclosing the selection anchor |
|
2719 nsCOMPtr<nsIDOMElement> table; |
|
2720 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr, getter_AddRefs(table)); |
|
2721 NS_ENSURE_SUCCESS(res, res); |
|
2722 if (table) |
|
2723 aTable = table; |
|
2724 else |
|
2725 return NS_ERROR_FAILURE; |
|
2726 } |
|
2727 |
|
2728 nsTableOuterFrame* tableFrame = GetTableFrame(aTable); |
|
2729 NS_ENSURE_TRUE(tableFrame, NS_ERROR_FAILURE); |
|
2730 |
|
2731 nsTableCellFrame* cellFrame = |
|
2732 tableFrame->GetCellFrameAt(aRowIndex, aColIndex); |
|
2733 if (!cellFrame) |
|
2734 return NS_ERROR_FAILURE; |
|
2735 |
|
2736 *aIsSelected = cellFrame->IsSelected(); |
|
2737 cellFrame->GetRowIndex(*aStartRowIndex); |
|
2738 cellFrame->GetColIndex(*aStartColIndex); |
|
2739 *aRowSpan = cellFrame->GetRowSpan(); |
|
2740 *aColSpan = cellFrame->GetColSpan(); |
|
2741 *aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex); |
|
2742 *aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex); |
|
2743 nsCOMPtr<nsIDOMElement> domCell = do_QueryInterface(cellFrame->GetContent()); |
|
2744 domCell.forget(aCell); |
|
2745 |
|
2746 return NS_OK; |
|
2747 } |
|
2748 |
|
2749 // When all you want is the cell |
|
2750 NS_IMETHODIMP |
|
2751 nsHTMLEditor::GetCellAt(nsIDOMElement* aTable, int32_t aRowIndex, int32_t aColIndex, nsIDOMElement **aCell) |
|
2752 { |
|
2753 NS_ENSURE_ARG_POINTER(aCell); |
|
2754 *aCell = nullptr; |
|
2755 |
|
2756 if (!aTable) |
|
2757 { |
|
2758 // Get the selected table or the table enclosing the selection anchor |
|
2759 nsCOMPtr<nsIDOMElement> table; |
|
2760 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), nullptr, getter_AddRefs(table)); |
|
2761 NS_ENSURE_SUCCESS(res, res); |
|
2762 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); |
|
2763 aTable = table; |
|
2764 } |
|
2765 |
|
2766 nsTableOuterFrame* tableFrame = GetTableFrame(aTable); |
|
2767 if (!tableFrame) |
|
2768 return NS_ERROR_FAILURE; |
|
2769 |
|
2770 nsCOMPtr<nsIDOMElement> domCell = |
|
2771 do_QueryInterface(tableFrame->GetCellAt(aRowIndex, aColIndex)); |
|
2772 domCell.forget(aCell); |
|
2773 |
|
2774 return NS_OK; |
|
2775 } |
|
2776 |
|
2777 // When all you want are the rowspan and colspan (not exposed in nsITableEditor) |
|
2778 NS_IMETHODIMP |
|
2779 nsHTMLEditor::GetCellSpansAt(nsIDOMElement* aTable, int32_t aRowIndex, int32_t aColIndex, |
|
2780 int32_t& aActualRowSpan, int32_t& aActualColSpan) |
|
2781 { |
|
2782 nsTableOuterFrame* tableFrame = GetTableFrame(aTable); |
|
2783 if (!tableFrame) |
|
2784 return NS_ERROR_FAILURE; |
|
2785 |
|
2786 aActualRowSpan = tableFrame->GetEffectiveRowSpanAt(aRowIndex, aColIndex); |
|
2787 aActualColSpan = tableFrame->GetEffectiveColSpanAt(aRowIndex, aColIndex); |
|
2788 |
|
2789 return NS_OK; |
|
2790 } |
|
2791 |
|
2792 NS_IMETHODIMP |
|
2793 nsHTMLEditor::GetCellContext(nsISelection **aSelection, |
|
2794 nsIDOMElement **aTable, |
|
2795 nsIDOMElement **aCell, |
|
2796 nsIDOMNode **aCellParent, int32_t *aCellOffset, |
|
2797 int32_t *aRowIndex, int32_t *aColIndex) |
|
2798 { |
|
2799 // Initialize return pointers |
|
2800 if (aSelection) *aSelection = nullptr; |
|
2801 if (aTable) *aTable = nullptr; |
|
2802 if (aCell) *aCell = nullptr; |
|
2803 if (aCellParent) *aCellParent = nullptr; |
|
2804 if (aCellOffset) *aCellOffset = 0; |
|
2805 if (aRowIndex) *aRowIndex = 0; |
|
2806 if (aColIndex) *aColIndex = 0; |
|
2807 |
|
2808 nsCOMPtr <nsISelection> selection; |
|
2809 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
2810 NS_ENSURE_SUCCESS(res, res); |
|
2811 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
2812 |
|
2813 if (aSelection) |
|
2814 { |
|
2815 *aSelection = selection.get(); |
|
2816 NS_ADDREF(*aSelection); |
|
2817 } |
|
2818 nsCOMPtr <nsIDOMElement> table; |
|
2819 nsCOMPtr <nsIDOMElement> cell; |
|
2820 |
|
2821 // Caller may supply the cell... |
|
2822 if (aCell && *aCell) |
|
2823 cell = *aCell; |
|
2824 |
|
2825 // ...but if not supplied, |
|
2826 // get cell if it's the child of selection anchor node, |
|
2827 // or get the enclosing by a cell |
|
2828 if (!cell) |
|
2829 { |
|
2830 // Find a selected or enclosing table element |
|
2831 nsCOMPtr<nsIDOMElement> cellOrTableElement; |
|
2832 int32_t selectedCount; |
|
2833 nsAutoString tagName; |
|
2834 res = GetSelectedOrParentTableElement(tagName, &selectedCount, |
|
2835 getter_AddRefs(cellOrTableElement)); |
|
2836 NS_ENSURE_SUCCESS(res, res); |
|
2837 if (tagName.EqualsLiteral("table")) |
|
2838 { |
|
2839 // We have a selected table, not a cell |
|
2840 if (aTable) |
|
2841 { |
|
2842 *aTable = cellOrTableElement.get(); |
|
2843 NS_ADDREF(*aTable); |
|
2844 } |
|
2845 return NS_OK; |
|
2846 } |
|
2847 if (!tagName.EqualsLiteral("td")) |
|
2848 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
2849 |
|
2850 // We found a cell |
|
2851 cell = cellOrTableElement; |
|
2852 } |
|
2853 if (aCell) |
|
2854 { |
|
2855 *aCell = cell.get(); |
|
2856 NS_ADDREF(*aCell); |
|
2857 } |
|
2858 |
|
2859 // Get containing table |
|
2860 res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), cell, getter_AddRefs(table)); |
|
2861 NS_ENSURE_SUCCESS(res, res); |
|
2862 // Cell must be in a table, so fail if not found |
|
2863 NS_ENSURE_TRUE(table, NS_ERROR_FAILURE); |
|
2864 if (aTable) |
|
2865 { |
|
2866 *aTable = table.get(); |
|
2867 NS_ADDREF(*aTable); |
|
2868 } |
|
2869 |
|
2870 // Get the rest of the related data only if requested |
|
2871 if (aRowIndex || aColIndex) |
|
2872 { |
|
2873 int32_t rowIndex, colIndex; |
|
2874 // Get current cell location so we can put caret back there when done |
|
2875 res = GetCellIndexes(cell, &rowIndex, &colIndex); |
|
2876 if(NS_FAILED(res)) return res; |
|
2877 if (aRowIndex) *aRowIndex = rowIndex; |
|
2878 if (aColIndex) *aColIndex = colIndex; |
|
2879 } |
|
2880 if (aCellParent) |
|
2881 { |
|
2882 nsCOMPtr <nsIDOMNode> cellParent; |
|
2883 // Get the immediate parent of the cell |
|
2884 res = cell->GetParentNode(getter_AddRefs(cellParent)); |
|
2885 NS_ENSURE_SUCCESS(res, res); |
|
2886 // Cell has to have a parent, so fail if not found |
|
2887 NS_ENSURE_TRUE(cellParent, NS_ERROR_FAILURE); |
|
2888 |
|
2889 *aCellParent = cellParent.get(); |
|
2890 NS_ADDREF(*aCellParent); |
|
2891 |
|
2892 if (aCellOffset) { |
|
2893 *aCellOffset = GetChildOffset(cell, cellParent); |
|
2894 } |
|
2895 } |
|
2896 |
|
2897 return res; |
|
2898 } |
|
2899 |
|
2900 nsresult |
|
2901 nsHTMLEditor::GetCellFromRange(nsIDOMRange *aRange, nsIDOMElement **aCell) |
|
2902 { |
|
2903 // Note: this might return a node that is outside of the range. |
|
2904 // Use carefully. |
|
2905 NS_ENSURE_TRUE(aRange && aCell, NS_ERROR_NULL_POINTER); |
|
2906 |
|
2907 *aCell = nullptr; |
|
2908 |
|
2909 nsCOMPtr<nsIDOMNode> startParent; |
|
2910 nsresult res = aRange->GetStartContainer(getter_AddRefs(startParent)); |
|
2911 NS_ENSURE_SUCCESS(res, res); |
|
2912 NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE); |
|
2913 |
|
2914 int32_t startOffset; |
|
2915 res = aRange->GetStartOffset(&startOffset); |
|
2916 NS_ENSURE_SUCCESS(res, res); |
|
2917 |
|
2918 nsCOMPtr<nsIDOMNode> childNode = GetChildAt(startParent, startOffset); |
|
2919 // This means selection is probably at a text node (or end of doc?) |
|
2920 if (!childNode) { |
|
2921 return NS_ERROR_FAILURE; |
|
2922 } |
|
2923 |
|
2924 nsCOMPtr<nsIDOMNode> endParent; |
|
2925 res = aRange->GetEndContainer(getter_AddRefs(endParent)); |
|
2926 NS_ENSURE_SUCCESS(res, res); |
|
2927 NS_ENSURE_TRUE(startParent, NS_ERROR_FAILURE); |
|
2928 |
|
2929 int32_t endOffset; |
|
2930 res = aRange->GetEndOffset(&endOffset); |
|
2931 NS_ENSURE_SUCCESS(res, res); |
|
2932 |
|
2933 // If a cell is deleted, the range is collapse |
|
2934 // (startOffset == endOffset) |
|
2935 // so tell caller the cell wasn't found |
|
2936 if (startParent == endParent && |
|
2937 endOffset == startOffset+1 && |
|
2938 nsHTMLEditUtils::IsTableCell(childNode)) |
|
2939 { |
|
2940 // Should we also test if frame is selected? (Use GetCellDataAt()) |
|
2941 // (Let's not for now -- more efficient) |
|
2942 nsCOMPtr<nsIDOMElement> cellElement = do_QueryInterface(childNode); |
|
2943 *aCell = cellElement.get(); |
|
2944 NS_ADDREF(*aCell); |
|
2945 return NS_OK; |
|
2946 } |
|
2947 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
2948 } |
|
2949 |
|
2950 NS_IMETHODIMP |
|
2951 nsHTMLEditor::GetFirstSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell) |
|
2952 { |
|
2953 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
2954 *aCell = nullptr; |
|
2955 if (aRange) *aRange = nullptr; |
|
2956 |
|
2957 nsCOMPtr<nsISelection> selection; |
|
2958 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
2959 NS_ENSURE_SUCCESS(res, res); |
|
2960 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
2961 |
|
2962 nsCOMPtr<nsIDOMRange> range; |
|
2963 res = selection->GetRangeAt(0, getter_AddRefs(range)); |
|
2964 NS_ENSURE_SUCCESS(res, res); |
|
2965 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); |
|
2966 |
|
2967 mSelectedCellIndex = 0; |
|
2968 |
|
2969 res = GetCellFromRange(range, aCell); |
|
2970 // Failure here probably means selection is in a text node, |
|
2971 // so there's no selected cell |
|
2972 if (NS_FAILED(res)) { |
|
2973 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
2974 } |
|
2975 // No cell means range was collapsed (cell was deleted) |
|
2976 if (!*aCell) { |
|
2977 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
2978 } |
|
2979 |
|
2980 if (aRange) |
|
2981 { |
|
2982 *aRange = range.get(); |
|
2983 NS_ADDREF(*aRange); |
|
2984 } |
|
2985 |
|
2986 // Setup for next cell |
|
2987 mSelectedCellIndex = 1; |
|
2988 |
|
2989 return res; |
|
2990 } |
|
2991 |
|
2992 NS_IMETHODIMP |
|
2993 nsHTMLEditor::GetNextSelectedCell(nsIDOMRange **aRange, nsIDOMElement **aCell) |
|
2994 { |
|
2995 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
2996 *aCell = nullptr; |
|
2997 if (aRange) *aRange = nullptr; |
|
2998 |
|
2999 nsCOMPtr<nsISelection> selection; |
|
3000 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
3001 NS_ENSURE_SUCCESS(res, res); |
|
3002 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
3003 |
|
3004 int32_t rangeCount; |
|
3005 res = selection->GetRangeCount(&rangeCount); |
|
3006 NS_ENSURE_SUCCESS(res, res); |
|
3007 |
|
3008 // Don't even try if index exceeds range count |
|
3009 if (mSelectedCellIndex >= rangeCount) |
|
3010 return NS_EDITOR_ELEMENT_NOT_FOUND; |
|
3011 |
|
3012 // Scan through ranges to find next valid selected cell |
|
3013 nsCOMPtr<nsIDOMRange> range; |
|
3014 for (; mSelectedCellIndex < rangeCount; mSelectedCellIndex++) |
|
3015 { |
|
3016 res = selection->GetRangeAt(mSelectedCellIndex, getter_AddRefs(range)); |
|
3017 NS_ENSURE_SUCCESS(res, res); |
|
3018 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE); |
|
3019 |
|
3020 res = GetCellFromRange(range, aCell); |
|
3021 // Failure here means the range doesn't contain a cell |
|
3022 NS_ENSURE_SUCCESS(res, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
3023 |
|
3024 // We found a selected cell |
|
3025 if (*aCell) break; |
|
3026 #ifdef DEBUG_cmanske |
|
3027 else |
|
3028 printf("GetNextSelectedCell: Collapsed range found\n"); |
|
3029 #endif |
|
3030 |
|
3031 // If we didn't find a cell, continue to next range in selection |
|
3032 } |
|
3033 // No cell means all remaining ranges were collapsed (cells were deleted) |
|
3034 NS_ENSURE_TRUE(*aCell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
3035 |
|
3036 if (aRange) |
|
3037 { |
|
3038 *aRange = range.get(); |
|
3039 NS_ADDREF(*aRange); |
|
3040 } |
|
3041 |
|
3042 // Setup for next cell |
|
3043 mSelectedCellIndex++; |
|
3044 |
|
3045 return res; |
|
3046 } |
|
3047 |
|
3048 NS_IMETHODIMP |
|
3049 nsHTMLEditor::GetFirstSelectedCellInTable(int32_t *aRowIndex, int32_t *aColIndex, nsIDOMElement **aCell) |
|
3050 { |
|
3051 NS_ENSURE_TRUE(aCell, NS_ERROR_NULL_POINTER); |
|
3052 *aCell = nullptr; |
|
3053 if (aRowIndex) |
|
3054 *aRowIndex = 0; |
|
3055 if (aColIndex) |
|
3056 *aColIndex = 0; |
|
3057 |
|
3058 nsCOMPtr<nsIDOMElement> cell; |
|
3059 nsresult res = GetFirstSelectedCell(nullptr, getter_AddRefs(cell)); |
|
3060 NS_ENSURE_SUCCESS(res, res); |
|
3061 NS_ENSURE_TRUE(cell, NS_EDITOR_ELEMENT_NOT_FOUND); |
|
3062 |
|
3063 *aCell = cell.get(); |
|
3064 NS_ADDREF(*aCell); |
|
3065 |
|
3066 // Also return the row and/or column if requested |
|
3067 if (aRowIndex || aColIndex) |
|
3068 { |
|
3069 int32_t startRowIndex, startColIndex; |
|
3070 res = GetCellIndexes(cell, &startRowIndex, &startColIndex); |
|
3071 if(NS_FAILED(res)) return res; |
|
3072 |
|
3073 if (aRowIndex) |
|
3074 *aRowIndex = startRowIndex; |
|
3075 |
|
3076 if (aColIndex) |
|
3077 *aColIndex = startColIndex; |
|
3078 } |
|
3079 |
|
3080 return res; |
|
3081 } |
|
3082 |
|
3083 NS_IMETHODIMP |
|
3084 nsHTMLEditor::SetSelectionAfterTableEdit(nsIDOMElement* aTable, int32_t aRow, int32_t aCol, |
|
3085 int32_t aDirection, bool aSelected) |
|
3086 { |
|
3087 NS_ENSURE_TRUE(aTable, NS_ERROR_NOT_INITIALIZED); |
|
3088 |
|
3089 nsCOMPtr<nsISelection>selection; |
|
3090 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
3091 NS_ENSURE_SUCCESS(res, res); |
|
3092 |
|
3093 if (!selection) |
|
3094 { |
|
3095 #ifdef DEBUG_cmanske |
|
3096 printf("Selection not found after table manipulation!\n"); |
|
3097 #endif |
|
3098 return NS_ERROR_FAILURE; |
|
3099 } |
|
3100 |
|
3101 nsCOMPtr<nsIDOMElement> cell; |
|
3102 bool done = false; |
|
3103 do { |
|
3104 res = GetCellAt(aTable, aRow, aCol, getter_AddRefs(cell)); |
|
3105 if (NS_SUCCEEDED(res)) |
|
3106 { |
|
3107 if (cell) |
|
3108 { |
|
3109 if (aSelected) |
|
3110 { |
|
3111 // Reselect the cell |
|
3112 return SelectElement(cell); |
|
3113 } |
|
3114 else |
|
3115 { |
|
3116 // Set the caret to deepest first child |
|
3117 // but don't go into nested tables |
|
3118 // TODO: Should we really be placing the caret at the END |
|
3119 // of the cell content? |
|
3120 return CollapseSelectionToDeepestNonTableFirstChild(selection, cell); |
|
3121 } |
|
3122 } else { |
|
3123 // Setup index to find another cell in the |
|
3124 // direction requested, but move in |
|
3125 // other direction if already at beginning of row or column |
|
3126 switch (aDirection) |
|
3127 { |
|
3128 case ePreviousColumn: |
|
3129 if (aCol == 0) |
|
3130 { |
|
3131 if (aRow > 0) |
|
3132 aRow--; |
|
3133 else |
|
3134 done = true; |
|
3135 } |
|
3136 else |
|
3137 aCol--; |
|
3138 break; |
|
3139 case ePreviousRow: |
|
3140 if (aRow == 0) |
|
3141 { |
|
3142 if (aCol > 0) |
|
3143 aCol--; |
|
3144 else |
|
3145 done = true; |
|
3146 } |
|
3147 else |
|
3148 aRow--; |
|
3149 break; |
|
3150 default: |
|
3151 done = true; |
|
3152 } |
|
3153 } |
|
3154 } |
|
3155 else |
|
3156 break; |
|
3157 } while (!done); |
|
3158 |
|
3159 // We didn't find a cell |
|
3160 // Set selection to just before the table |
|
3161 nsCOMPtr<nsIDOMNode> tableParent; |
|
3162 res = aTable->GetParentNode(getter_AddRefs(tableParent)); |
|
3163 if(NS_SUCCEEDED(res) && tableParent) |
|
3164 { |
|
3165 int32_t tableOffset = GetChildOffset(aTable, tableParent); |
|
3166 return selection->Collapse(tableParent, tableOffset); |
|
3167 } |
|
3168 // Last resort: Set selection to start of doc |
|
3169 // (it's very bad to not have a valid selection!) |
|
3170 return SetSelectionAtDocumentStart(selection); |
|
3171 } |
|
3172 |
|
3173 NS_IMETHODIMP |
|
3174 nsHTMLEditor::GetSelectedOrParentTableElement(nsAString& aTagName, |
|
3175 int32_t *aSelectedCount, |
|
3176 nsIDOMElement** aTableElement) |
|
3177 { |
|
3178 NS_ENSURE_ARG_POINTER(aTableElement); |
|
3179 NS_ENSURE_ARG_POINTER(aSelectedCount); |
|
3180 *aTableElement = nullptr; |
|
3181 aTagName.Truncate(); |
|
3182 *aSelectedCount = 0; |
|
3183 |
|
3184 nsCOMPtr<nsISelection> selection; |
|
3185 nsresult res = GetSelection(getter_AddRefs(selection)); |
|
3186 NS_ENSURE_SUCCESS(res, res); |
|
3187 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE); |
|
3188 |
|
3189 // Try to get the first selected cell |
|
3190 nsCOMPtr<nsIDOMElement> tableOrCellElement; |
|
3191 res = GetFirstSelectedCell(nullptr, getter_AddRefs(tableOrCellElement)); |
|
3192 NS_ENSURE_SUCCESS(res, res); |
|
3193 |
|
3194 NS_NAMED_LITERAL_STRING(tdName, "td"); |
|
3195 |
|
3196 if (tableOrCellElement) |
|
3197 { |
|
3198 // Each cell is in its own selection range, |
|
3199 // so count signals multiple-cell selection |
|
3200 res = selection->GetRangeCount(aSelectedCount); |
|
3201 NS_ENSURE_SUCCESS(res, res); |
|
3202 aTagName = tdName; |
|
3203 } |
|
3204 else |
|
3205 { |
|
3206 nsCOMPtr<nsIDOMNode> anchorNode; |
|
3207 res = selection->GetAnchorNode(getter_AddRefs(anchorNode)); |
|
3208 if(NS_FAILED(res)) return res; |
|
3209 NS_ENSURE_TRUE(anchorNode, NS_ERROR_FAILURE); |
|
3210 |
|
3211 nsCOMPtr<nsIDOMNode> selectedNode; |
|
3212 |
|
3213 // Get child of anchor node, if exists |
|
3214 bool hasChildren; |
|
3215 anchorNode->HasChildNodes(&hasChildren); |
|
3216 |
|
3217 if (hasChildren) |
|
3218 { |
|
3219 int32_t anchorOffset; |
|
3220 res = selection->GetAnchorOffset(&anchorOffset); |
|
3221 NS_ENSURE_SUCCESS(res, res); |
|
3222 selectedNode = GetChildAt(anchorNode, anchorOffset); |
|
3223 if (!selectedNode) |
|
3224 { |
|
3225 selectedNode = anchorNode; |
|
3226 // If anchor doesn't have a child, we can't be selecting a table element, |
|
3227 // so don't do the following: |
|
3228 } |
|
3229 else |
|
3230 { |
|
3231 nsCOMPtr<nsIAtom> atom = nsEditor::GetTag(selectedNode); |
|
3232 |
|
3233 if (atom == nsEditProperty::td) |
|
3234 { |
|
3235 tableOrCellElement = do_QueryInterface(selectedNode); |
|
3236 aTagName = tdName; |
|
3237 // Each cell is in its own selection range, |
|
3238 // so count signals multiple-cell selection |
|
3239 res = selection->GetRangeCount(aSelectedCount); |
|
3240 NS_ENSURE_SUCCESS(res, res); |
|
3241 } |
|
3242 else if (atom == nsEditProperty::table) |
|
3243 { |
|
3244 tableOrCellElement = do_QueryInterface(selectedNode); |
|
3245 aTagName.AssignLiteral("table"); |
|
3246 *aSelectedCount = 1; |
|
3247 } |
|
3248 else if (atom == nsEditProperty::tr) |
|
3249 { |
|
3250 tableOrCellElement = do_QueryInterface(selectedNode); |
|
3251 aTagName.AssignLiteral("tr"); |
|
3252 *aSelectedCount = 1; |
|
3253 } |
|
3254 } |
|
3255 } |
|
3256 if (!tableOrCellElement) |
|
3257 { |
|
3258 // Didn't find a table element -- find a cell parent |
|
3259 res = GetElementOrParentByTagName(tdName, anchorNode, getter_AddRefs(tableOrCellElement)); |
|
3260 if(NS_FAILED(res)) return res; |
|
3261 if (tableOrCellElement) |
|
3262 aTagName = tdName; |
|
3263 } |
|
3264 } |
|
3265 if (tableOrCellElement) |
|
3266 { |
|
3267 *aTableElement = tableOrCellElement.get(); |
|
3268 NS_ADDREF(*aTableElement); |
|
3269 } |
|
3270 return res; |
|
3271 } |
|
3272 |
|
3273 NS_IMETHODIMP |
|
3274 nsHTMLEditor::GetSelectedCellsType(nsIDOMElement *aElement, uint32_t *aSelectionType) |
|
3275 { |
|
3276 NS_ENSURE_ARG_POINTER(aSelectionType); |
|
3277 *aSelectionType = 0; |
|
3278 |
|
3279 // Be sure we have a table element |
|
3280 // (if aElement is null, this uses selection's anchor node) |
|
3281 nsCOMPtr<nsIDOMElement> table; |
|
3282 |
|
3283 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("table"), aElement, getter_AddRefs(table)); |
|
3284 NS_ENSURE_SUCCESS(res, res); |
|
3285 |
|
3286 int32_t rowCount, colCount; |
|
3287 res = GetTableSize(table, &rowCount, &colCount); |
|
3288 NS_ENSURE_SUCCESS(res, res); |
|
3289 |
|
3290 // Traverse all selected cells |
|
3291 nsCOMPtr<nsIDOMElement> selectedCell; |
|
3292 res = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell)); |
|
3293 NS_ENSURE_SUCCESS(res, res); |
|
3294 if (res == NS_EDITOR_ELEMENT_NOT_FOUND) return NS_OK; |
|
3295 |
|
3296 // We have at least one selected cell, so set return value |
|
3297 *aSelectionType = nsISelectionPrivate::TABLESELECTION_CELL; |
|
3298 |
|
3299 // Store indexes of each row/col to avoid duplication of searches |
|
3300 nsTArray<int32_t> indexArray; |
|
3301 |
|
3302 bool allCellsInRowAreSelected = false; |
|
3303 bool allCellsInColAreSelected = false; |
|
3304 while (NS_SUCCEEDED(res) && selectedCell) |
|
3305 { |
|
3306 // Get the cell's location in the cellmap |
|
3307 int32_t startRowIndex, startColIndex; |
|
3308 res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex); |
|
3309 if(NS_FAILED(res)) return res; |
|
3310 |
|
3311 if (!indexArray.Contains(startColIndex)) |
|
3312 { |
|
3313 indexArray.AppendElement(startColIndex); |
|
3314 allCellsInRowAreSelected = AllCellsInRowSelected(table, startRowIndex, colCount); |
|
3315 // We're done as soon as we fail for any row |
|
3316 if (!allCellsInRowAreSelected) break; |
|
3317 } |
|
3318 res = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell)); |
|
3319 } |
|
3320 |
|
3321 if (allCellsInRowAreSelected) |
|
3322 { |
|
3323 *aSelectionType = nsISelectionPrivate::TABLESELECTION_ROW; |
|
3324 return NS_OK; |
|
3325 } |
|
3326 // Test for columns |
|
3327 |
|
3328 // Empty the indexArray |
|
3329 indexArray.Clear(); |
|
3330 |
|
3331 // Start at first cell again |
|
3332 res = GetFirstSelectedCell(nullptr, getter_AddRefs(selectedCell)); |
|
3333 while (NS_SUCCEEDED(res) && selectedCell) |
|
3334 { |
|
3335 // Get the cell's location in the cellmap |
|
3336 int32_t startRowIndex, startColIndex; |
|
3337 res = GetCellIndexes(selectedCell, &startRowIndex, &startColIndex); |
|
3338 if(NS_FAILED(res)) return res; |
|
3339 |
|
3340 if (!indexArray.Contains(startRowIndex)) |
|
3341 { |
|
3342 indexArray.AppendElement(startColIndex); |
|
3343 allCellsInColAreSelected = AllCellsInColumnSelected(table, startColIndex, rowCount); |
|
3344 // We're done as soon as we fail for any column |
|
3345 if (!allCellsInRowAreSelected) break; |
|
3346 } |
|
3347 res = GetNextSelectedCell(nullptr, getter_AddRefs(selectedCell)); |
|
3348 } |
|
3349 if (allCellsInColAreSelected) |
|
3350 *aSelectionType = nsISelectionPrivate::TABLESELECTION_COLUMN; |
|
3351 |
|
3352 return NS_OK; |
|
3353 } |
|
3354 |
|
3355 bool |
|
3356 nsHTMLEditor::AllCellsInRowSelected(nsIDOMElement *aTable, int32_t aRowIndex, int32_t aNumberOfColumns) |
|
3357 { |
|
3358 NS_ENSURE_TRUE(aTable, false); |
|
3359 |
|
3360 int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
3361 bool isSelected; |
|
3362 |
|
3363 for( int32_t col = 0; col < aNumberOfColumns; col += std::max(actualColSpan, 1)) |
|
3364 { |
|
3365 nsCOMPtr<nsIDOMElement> cell; |
|
3366 nsresult res = GetCellDataAt(aTable, aRowIndex, col, getter_AddRefs(cell), |
|
3367 &curStartRowIndex, &curStartColIndex, |
|
3368 &rowSpan, &colSpan, |
|
3369 &actualRowSpan, &actualColSpan, &isSelected); |
|
3370 |
|
3371 NS_ENSURE_SUCCESS(res, false); |
|
3372 // If no cell, we may have a "ragged" right edge, |
|
3373 // so return TRUE only if we already found a cell in the row |
|
3374 NS_ENSURE_TRUE(cell, (col > 0) ? true : false); |
|
3375 |
|
3376 // Return as soon as a non-selected cell is found |
|
3377 NS_ENSURE_TRUE(isSelected, false); |
|
3378 |
|
3379 NS_ASSERTION((actualColSpan > 0),"ActualColSpan = 0 in AllCellsInRowSelected"); |
|
3380 } |
|
3381 return true; |
|
3382 } |
|
3383 |
|
3384 bool |
|
3385 nsHTMLEditor::AllCellsInColumnSelected(nsIDOMElement *aTable, int32_t aColIndex, int32_t aNumberOfRows) |
|
3386 { |
|
3387 NS_ENSURE_TRUE(aTable, false); |
|
3388 |
|
3389 int32_t curStartRowIndex, curStartColIndex, rowSpan, colSpan, actualRowSpan, actualColSpan; |
|
3390 bool isSelected; |
|
3391 |
|
3392 for( int32_t row = 0; row < aNumberOfRows; row += std::max(actualRowSpan, 1)) |
|
3393 { |
|
3394 nsCOMPtr<nsIDOMElement> cell; |
|
3395 nsresult res = GetCellDataAt(aTable, row, aColIndex, getter_AddRefs(cell), |
|
3396 &curStartRowIndex, &curStartColIndex, |
|
3397 &rowSpan, &colSpan, |
|
3398 &actualRowSpan, &actualColSpan, &isSelected); |
|
3399 |
|
3400 NS_ENSURE_SUCCESS(res, false); |
|
3401 // If no cell, we must have a "ragged" right edge on the last column |
|
3402 // so return TRUE only if we already found a cell in the row |
|
3403 NS_ENSURE_TRUE(cell, (row > 0) ? true : false); |
|
3404 |
|
3405 // Return as soon as a non-selected cell is found |
|
3406 NS_ENSURE_TRUE(isSelected, false); |
|
3407 } |
|
3408 return true; |
|
3409 } |
|
3410 |
|
3411 bool |
|
3412 nsHTMLEditor::IsEmptyCell(dom::Element* aCell) |
|
3413 { |
|
3414 MOZ_ASSERT(aCell); |
|
3415 |
|
3416 // Check if target only contains empty text node or <br> |
|
3417 nsCOMPtr<nsINode> cellChild = aCell->GetFirstChild(); |
|
3418 if (!cellChild) { |
|
3419 return false; |
|
3420 } |
|
3421 |
|
3422 nsCOMPtr<nsINode> nextChild = cellChild->GetNextSibling(); |
|
3423 if (nextChild) { |
|
3424 return false; |
|
3425 } |
|
3426 |
|
3427 // We insert a single break into a cell by default |
|
3428 // to have some place to locate a cursor -- it is dispensable |
|
3429 if (cellChild->IsElement() && cellChild->AsElement()->IsHTML(nsGkAtoms::br)) { |
|
3430 return true; |
|
3431 } |
|
3432 |
|
3433 bool isEmpty; |
|
3434 // Or check if no real content |
|
3435 nsresult rv = IsEmptyNode(cellChild, &isEmpty, false, false); |
|
3436 NS_ENSURE_SUCCESS(rv, false); |
|
3437 return isEmpty; |
|
3438 } |