File indexing completed on 2024-05-12 16:35:12
0001 /* This file is part of the KDE project 0002 Copyright 2007,2009 Stefan Nikolaus <stefan.nikolaus@kdemail.net> 0003 Copyright 1999-2007 The KSpread Team <calligra-devel@kde.org> 0004 Copyright 1998,1999 Torben Weis <weis@kde.org> 0005 0006 This library is free software; you can redistribute it and/or 0007 modify it under the terms of the GNU Library General Public 0008 License as published by the Free Software Foundation; either 0009 version 2 of the License, or (at your option) any later version. 0010 0011 This library is distributed in the hope that it will be useful, 0012 but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 Library General Public License for more details. 0015 0016 You should have received a copy of the GNU Library General Public License 0017 along with this library; see the file COPYING.LIB. If not, write to 0018 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0019 Boston, MA 02110-1301, USA. 0020 */ 0021 0022 #include "PasteCommand.h" 0023 0024 #include <QApplication> 0025 #include <QMimeData> 0026 0027 #include "CellStorage.h" 0028 #include "commands/DataManipulators.h" 0029 #include "commands/DeleteCommand.h" 0030 #include "commands/PageBreakCommand.h" 0031 #include "commands/RowColumnManipulators.h" 0032 #include "DependencyManager.h" 0033 #include "Map.h" 0034 #include "RowColumnFormat.h" 0035 #include "Sheet.h" 0036 0037 // TODO 0038 // - Extract the pasting code from Cell. 0039 // - Get plain text pasting right. 0040 0041 0042 using namespace Calligra::Sheets; 0043 0044 class PasteCellCommand : public AbstractRegionCommand 0045 { 0046 public: 0047 PasteCellCommand(KUndo2Command *parent = 0) 0048 : AbstractRegionCommand(parent) 0049 , m_pasteMode(Paste::Normal) 0050 , m_pasteOperation(Paste::OverWrite) 0051 , m_pasteFC(false) { 0052 } 0053 ~PasteCellCommand() override {} 0054 0055 void addXmlElement(const Cell &cell, const KoXmlElement &element) { 0056 add(cell.cellPosition(), m_sheet); 0057 m_elements.insert(cell, element); 0058 } 0059 0060 Paste::Mode m_pasteMode; 0061 Paste::Operation m_pasteOperation; 0062 bool m_pasteFC; // FIXME What's that? ForceConditions? 0063 0064 protected: 0065 bool process(Element *element) override { 0066 // Destination cell: 0067 Cell cell(m_sheet, element->rect().topLeft()); 0068 const int xOffset = cell.column() - m_elements[cell].attribute("column").toInt(); 0069 const int yOffset = cell.row() - m_elements[cell].attribute("row").toInt(); 0070 return cell.load(m_elements[cell], xOffset, yOffset, 0071 m_pasteMode, m_pasteOperation, m_pasteFC); 0072 } 0073 0074 bool preProcessing() override { 0075 if (m_firstrun) { 0076 m_sheet->cellStorage()->startUndoRecording(); 0077 } 0078 return true; 0079 } 0080 0081 bool mainProcessing() override { 0082 if (m_reverse) { 0083 KUndo2Command::undo(); // undo child commands 0084 return true; 0085 } 0086 return AbstractRegionCommand::mainProcessing(); 0087 } 0088 0089 bool postProcessing() override { 0090 if (m_firstrun) { 0091 m_sheet->cellStorage()->stopUndoRecording(this); 0092 } 0093 return true; 0094 } 0095 0096 private: 0097 QHash<Cell, KoXmlElement> m_elements; 0098 }; 0099 0100 0101 0102 PasteCommand::PasteCommand(KUndo2Command *parent) 0103 : AbstractRegionCommand(parent) 0104 , m_mimeData(0) 0105 , m_xmlDocument(0) 0106 , m_insertMode(NoInsertion) 0107 , m_pasteMode(Paste::Normal) 0108 , m_operation(Paste::OverWrite) 0109 , m_pasteFC(false) 0110 { 0111 } 0112 0113 PasteCommand::~PasteCommand() 0114 { 0115 delete m_xmlDocument; 0116 } 0117 0118 const QMimeData* PasteCommand::mimeData() const 0119 { 0120 return m_mimeData; 0121 } 0122 0123 bool PasteCommand::setMimeData(const QMimeData *mimeData) 0124 { 0125 if (!mimeData) { 0126 return false; 0127 } 0128 m_mimeData = mimeData; 0129 return true; 0130 } 0131 0132 void PasteCommand::setInsertionMode(InsertionMode mode) 0133 { 0134 m_insertMode = mode; 0135 } 0136 0137 void PasteCommand::setMode(Paste::Mode mode) 0138 { 0139 m_pasteMode = mode; 0140 } 0141 0142 void PasteCommand::setOperation(Paste::Operation operation) 0143 { 0144 m_operation = operation; 0145 } 0146 0147 void PasteCommand::setPasteFC(bool force) 0148 { 0149 m_pasteFC = force; 0150 } 0151 0152 bool PasteCommand::isApproved() const 0153 { 0154 if (supports(m_mimeData)) { 0155 return AbstractRegionCommand::isApproved(); 0156 } 0157 warnSheets << "Unrecognized MIME type(s):" << m_mimeData->formats().join(", "); 0158 return false; 0159 } 0160 0161 // static 0162 bool PasteCommand::supports(const QMimeData *mimeData) 0163 { 0164 if (mimeData->hasFormat("application/x-kspread-snippet")) { 0165 return true; 0166 } else if (mimeData->hasText()) { 0167 return true; 0168 } else if (mimeData->hasHtml()) { 0169 // TODO handle HTML tables 0170 return false; 0171 } else if (mimeData->hasFormat("text/csv")) { 0172 // TODO parse CSV data 0173 return false; 0174 } 0175 return false; 0176 } 0177 0178 // static 0179 bool PasteCommand::unknownShiftDirection(const QMimeData *mimeData) 0180 { 0181 if (!mimeData) { 0182 return false; 0183 } 0184 0185 QByteArray byteArray; 0186 0187 if (mimeData->hasFormat("application/x-kspread-snippet")) { 0188 byteArray = mimeData->data("application/x-kspread-snippet"); 0189 } else { 0190 return false; 0191 } 0192 0193 QString errorMsg; 0194 int errorLine; 0195 int errorColumn; 0196 KoXmlDocument d; 0197 if (!d.setContent(byteArray, false, &errorMsg, &errorLine, &errorColumn)) { 0198 // an error occurred 0199 debugSheets << "An error occurred." 0200 << "line:" << errorLine << "col:" << errorColumn << errorMsg; 0201 return false; 0202 } 0203 0204 KoXmlElement e = d.documentElement(); 0205 if (!e.namedItem("columns").toElement().isNull()) { 0206 return false; 0207 } 0208 0209 if (!e.namedItem("rows").toElement().isNull()) { 0210 return false; 0211 } 0212 0213 KoXmlElement c = e.firstChild().toElement(); 0214 for (; !c.isNull(); c = c.nextSibling().toElement()) { 0215 if (c.tagName() == "cell") { 0216 return true; 0217 } 0218 } 0219 return false; 0220 } 0221 0222 bool PasteCommand::preProcessing() 0223 { 0224 return AbstractRegionCommand::preProcessing(); 0225 } 0226 0227 bool PasteCommand::mainProcessing() 0228 { 0229 if (!m_reverse) { // apply/redo 0230 if (m_firstrun) { // apply 0231 // First, prepare the data ONCE for all region elements. 0232 if (m_mimeData->hasFormat("application/x-kspread-snippet")) { 0233 m_xmlDocument = new KoXmlDocument(true); 0234 const QByteArray data = m_mimeData->data("application/x-kspread-snippet"); 0235 debugSheetsUI << "Parsing" << data.size() << "bytes"; 0236 QString errorMsg; 0237 int errorLine; 0238 int errorColumn; 0239 if (!m_xmlDocument->setContent(data, false, &errorMsg, &errorLine, &errorColumn)) { 0240 // an error occurred 0241 debugSheetsUI << "An error occurred." << "line:" << errorLine 0242 << "col:" << errorColumn << errorMsg; 0243 return false; 0244 } 0245 } else if (m_mimeData->hasText()) { 0246 // TODO Maybe prepare the string list here?! 0247 } 0248 0249 // Iterate over all region elements and build the sub-commands. 0250 const QList<Element *> elements = cells(); 0251 const int begin = m_reverse ? elements.count() - 1 : 0; 0252 const int end = m_reverse ? -1 : elements.count(); 0253 for (int i = begin; i != end; m_reverse ? --i : ++i) { 0254 if (m_mimeData->hasFormat("application/x-kspread-snippet")) { 0255 processXmlData(elements[i], m_xmlDocument); 0256 } else if (m_mimeData->hasText()) { 0257 processTextPlain(elements[i]); 0258 } 0259 } 0260 } 0261 KUndo2Command::redo(); // redo the child commands 0262 } else { // undo 0263 KUndo2Command::undo(); // undo the child commands 0264 } 0265 return true; 0266 } 0267 0268 bool PasteCommand::postProcessing() 0269 { 0270 return AbstractRegionCommand::postProcessing(); 0271 } 0272 0273 bool PasteCommand::processXmlData(Element *element, KoXmlDocument *data) 0274 { 0275 const QRect pasteArea = element->rect(); 0276 Sheet *const sheet = element->sheet(); 0277 Q_ASSERT(sheet == m_sheet); 0278 Map *const map = sheet->map(); 0279 0280 const KoXmlElement root = data->documentElement(); // "spreadsheet-snippet" 0281 if (root.hasAttribute("cut")) { 0282 const Region cutRegion(root.attribute("cut"), map, sheet); 0283 if (cutRegion.isValid()) { 0284 const Cell destination(sheet, pasteArea.topLeft()); 0285 map->dependencyManager()->regionMoved(cutRegion, destination); 0286 } 0287 } 0288 0289 const int sourceHeight = root.attribute("rows").toInt(); 0290 const int sourceWidth = root.attribute("columns").toInt(); 0291 0292 // Find size of rectangle that we want to paste to (either clipboard size or current selection) 0293 const bool noRowsInClipboard = root.namedItem("rows").toElement().isNull(); 0294 const bool noColumnsInClipboard = root.namedItem("columns").toElement().isNull(); 0295 const bool noRowsSelected = !Region::Range(pasteArea).isRow(); 0296 const bool noColumnsSelected = !Region::Range(pasteArea).isColumn(); 0297 const bool biggerSelectedWidth = pasteArea.width() >= sourceWidth; 0298 const bool biggerSelectedHeight = pasteArea.height() >= sourceHeight; 0299 0300 const int pasteWidth = biggerSelectedWidth && noRowsSelected && noRowsInClipboard 0301 ? pasteArea.width() : sourceWidth; 0302 const int pasteHeight = biggerSelectedHeight && noColumnsSelected && noColumnsInClipboard 0303 ? pasteArea.height() : sourceHeight; 0304 0305 const int xOffset = noRowsInClipboard ? pasteArea.left() - 1 : 0; 0306 const int yOffset = noColumnsInClipboard ? pasteArea.top() - 1 : 0; 0307 0308 debugSheetsUI << "selected size (col x row):" << pasteArea.width() << 'x' << pasteArea.height(); 0309 debugSheetsUI << "source size (col x row):" << sourceWidth << 'x' << sourceHeight; 0310 debugSheetsUI << "paste area size (col x row):" << pasteWidth << 'x' << pasteHeight; 0311 debugSheetsUI << "xOffset:" << xOffset << "yOffset:" << yOffset; 0312 0313 // Determine the shift direction, if needed. 0314 if (m_insertMode == ShiftCells) { 0315 if (!noColumnsInClipboard && !noRowsInClipboard) { 0316 // There are columns and rows in the source data. 0317 m_insertMode = ShiftCellsRight; // faster than down 0318 } else if (!noColumnsInClipboard) { 0319 // There are columns in the source data. 0320 m_insertMode = ShiftCellsRight; 0321 } else if (!noRowsInClipboard) { 0322 // There are rows in the source data. 0323 m_insertMode = ShiftCellsDown; 0324 } else { 0325 // Should not happen. 0326 // ShiftCells should only be set, if the data contains columns/rows. 0327 // FIXME: 0328 // Commenting out this assert because: 0329 // We almost always get here (except when a whole row is selected) 0330 // The comment above indicates that this is not the way it was meant to work, 0331 // so there is probably a bug somewhere 0332 //Q_ASSERT(false); 0333 m_insertMode = ShiftCellsRight; // faster than down 0334 } 0335 } 0336 0337 const bool noColumns = noColumnsInClipboard && noColumnsSelected; 0338 const bool noRows = noRowsInClipboard && noRowsSelected; 0339 0340 // Shift cells down. 0341 if (m_insertMode == ShiftCellsDown) { 0342 // Cases: 0343 // 1. Columns AND rows are contained in either source or selection 0344 // 1.a Columns in source and rows in selection 0345 // I.e. yOffset=0 0346 // Clear everything. 0347 // Taking the column data/style and fill all columns. 0348 // 1.b Columns and rows in source, but not in selection 0349 // I.e. xOffset=0,yOffset=0 0350 // Leave everything as is? No, data from different sheet is possible! 0351 // Clear everything. 0352 // Fill with the source column/row data/style, 0353 // i.e. the sheet data becomes equal to the source data. 0354 // Same procedure as in 1.e 0355 // 1.c Columns and rows in selection, but not in source 0356 // Clear everything. 0357 // Fill with the source data. Tiling -> HUGE task! 0358 // 1.d Rows in source and columns in selection 0359 // I.e. xOffset=0 0360 // Clear everything. 0361 // Taking the row data/style and fill all rows. 0362 // 1.e Columns AND rows in both 0363 // I.e. xOffset=0,yOffset=0 0364 // Leave everything as is? No, data from different sheet is possible! 0365 // Clear everything. 0366 // Fill with the source column/row data/style, 0367 // i.e. the sheet data becomes equal to the source data. 0368 // Same procedure as in 1.b 0369 // 2. Columns are present in either source or selection, but no rows 0370 // 2a Columns in source 0371 // I.e. yOffset=0 0372 // Clear the appropriate columns in the paste area. 0373 // Fill them with the source data. 0374 // 2b Columns in selection 0375 // Clear the selected columns. 0376 // Fill them with the source data. Tiling -> HUGE task! 0377 // 2c Columns in both 0378 // I.e. yOffset=0 0379 // Clear the selected columns. 0380 // Fill them with the source column data/style. 0381 // 3. Rows are present in either source or selection, but no columns 0382 // 3a Rows in source 0383 // I.e. xOffset=0 0384 // Insert rows. 0385 // Fill in data. 0386 // 3b Rows in selection 0387 // Insert rows. 0388 // Fill in data. Tiling -> HUGE task! 0389 // 3c Rows in both 0390 // I.e. xOffset=0 0391 // Insert rows. 0392 // Fill in data/style from source rows. 0393 // 4. Neither column, nor rows are present 0394 // Shift the cell down. 0395 // Fill in data. 0396 if ((!noColumns && !noRows) || (!noColumns && noRows)) { 0397 // Everything or only columns present. 0398 DeleteCommand *const command = new DeleteCommand(this); 0399 command->setSheet(m_sheet); 0400 command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); 0401 command->setMode(DeleteCommand::OnlyCells); 0402 } else if (noColumns && !noRows) { 0403 // Rows present. 0404 InsertDeleteRowManipulator *const command = new InsertDeleteRowManipulator(this); 0405 command->setSheet(sheet); 0406 command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); 0407 } else { 0408 // Neither columns, nor rows present. 0409 ShiftManipulator *const command = new ShiftManipulator(this); 0410 command->setSheet(sheet); 0411 command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); 0412 command->setDirection(ShiftManipulator::ShiftBottom); 0413 } 0414 } 0415 // Shift cells right. 0416 if (m_insertMode == ShiftCellsRight) { 0417 // Cases: 0418 // Same as for ShiftCellsDown, 0419 // except that clearing and inserting are exchanged for cases 2 and 3. 0420 // Shifting a column to the right is the same as column insertion. 0421 // Shifting a row to the right is the same as clearing the row. 0422 if ((!noColumns && !noRows) || (noColumns && !noRows)) { 0423 // Everything or only rows present. 0424 DeleteCommand *const command = new DeleteCommand(this); 0425 command->setSheet(m_sheet); 0426 command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); 0427 command->setMode(DeleteCommand::OnlyCells); 0428 } else if (!noColumns && noRows) { 0429 // Columns present. 0430 InsertDeleteColumnManipulator *const command = new InsertDeleteColumnManipulator(this); 0431 command->setSheet(sheet); 0432 command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); 0433 } else { 0434 // Neither columns, nor rows present. 0435 ShiftManipulator *const command = new ShiftManipulator(this); 0436 command->setSheet(sheet); 0437 command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); 0438 command->setDirection(ShiftManipulator::ShiftRight); 0439 } 0440 } 0441 0442 // This command will collect as many cell loads as possible in the iteration. 0443 PasteCellCommand *pasteCellCommand = 0; 0444 0445 KoXmlElement e = root.firstChild().toElement(); // "columns", "rows" or "cell" 0446 for (; !e.isNull(); e = e.nextSibling().toElement()) { 0447 // If the element is not a cell, unset the pasteCellCommand pointer. 0448 // If existing, it is attached as child command, so no leaking here. 0449 if (e.tagName() != "cell") { 0450 pasteCellCommand = 0; 0451 } 0452 0453 // entire columns given 0454 if (e.tagName() == "columns" && !sheet->isProtected()) { 0455 const int number = e.attribute("count").toInt(); 0456 if (m_insertMode == NoInsertion) { 0457 // Clear the existing content; not the column style. 0458 DeleteCommand *const command = new DeleteCommand(this); 0459 command->setSheet(m_sheet); 0460 const int col = e.attribute("column").toInt(); 0461 const int cols = qMax(pasteArea.width(), number); 0462 const Region region(col + xOffset, 1, cols, KS_rowMax, m_sheet); 0463 command->add(region); 0464 command->setMode(DeleteCommand::OnlyCells); 0465 } 0466 0467 // Set the column style. 0468 ColumnFormat columnFormat; 0469 columnFormat.setSheet(sheet); 0470 KoXmlElement c = e.firstChild().toElement(); 0471 for (; !c.isNull(); c = c.nextSibling().toElement()) { 0472 if (c.tagName() != "column") { 0473 continue; 0474 } 0475 if (columnFormat.load(c, xOffset, m_pasteMode)) { 0476 const int col = columnFormat.column(); 0477 const int cols = qMax(pasteArea.width(), number); 0478 for (int coff = 0; col - xOffset + coff <= cols; coff += number) { 0479 ResizeColumnManipulator *const resize = new ResizeColumnManipulator(this); 0480 resize->setSheet(m_sheet); 0481 resize->add(Region(col + coff, 1, 1, 1, m_sheet)); 0482 resize->setSize(columnFormat.width()); 0483 HideShowManipulator *const hideShow = new HideShowManipulator(this); 0484 hideShow->setManipulateColumns(true); 0485 hideShow->setSheet(m_sheet); 0486 hideShow->add(Region(col + coff, 1, 1, 1, m_sheet)); 0487 hideShow->setReverse(!columnFormat.isHidden()); 0488 PageBreakCommand *const pageBreak = new PageBreakCommand(this); 0489 pageBreak->setMode(PageBreakCommand::BreakBeforeColumn); 0490 pageBreak->setSheet(m_sheet); 0491 pageBreak->add(Region(col + coff, 1, 1, 1, m_sheet)); 0492 pageBreak->setReverse(!columnFormat.hasPageBreak()); 0493 } 0494 } 0495 } 0496 } 0497 0498 // entire rows given 0499 if (e.tagName() == "rows" && !sheet->isProtected()) { 0500 const int number = e.attribute("count").toInt(); 0501 if (m_insertMode == NoInsertion) { 0502 // Clear the existing content; not the row style. 0503 DeleteCommand *const command = new DeleteCommand(this); 0504 command->setSheet(m_sheet); 0505 const int row = e.attribute("row").toInt(); 0506 const int rows = qMax(pasteArea.height(), number); 0507 const Region region(1, row + yOffset, KS_colMax, rows, m_sheet); 0508 command->add(region); 0509 command->setMode(DeleteCommand::OnlyCells); 0510 } 0511 0512 // Set the row style. 0513 RowFormat rowFormat; 0514 rowFormat.setSheet(sheet); 0515 KoXmlElement c = e.firstChild().toElement(); 0516 for (; !c.isNull(); c = c.nextSibling().toElement()) { 0517 if (c.tagName() != "row") { 0518 continue; 0519 } 0520 if (rowFormat.load(c, yOffset, m_pasteMode)) { 0521 const int row = rowFormat.row(); 0522 const int rows = qMax(pasteArea.height(), number); 0523 for (int roff = 0; row - yOffset + roff <= rows; roff += number) { 0524 ResizeRowManipulator *const resize = new ResizeRowManipulator(this); 0525 resize->setSheet(m_sheet); 0526 resize->add(Region(1, rowFormat.row(), 1, 1, m_sheet)); 0527 resize->setSize(rowFormat.height()); 0528 HideShowManipulator *const hideShow = new HideShowManipulator(this); 0529 hideShow->setManipulateColumns(false); 0530 hideShow->setSheet(m_sheet); 0531 hideShow->add(Region(1, rowFormat.row(), 1, 1, m_sheet)); 0532 hideShow->setReverse(!rowFormat.isHidden()); 0533 PageBreakCommand *const pageBreak = new PageBreakCommand(this); 0534 pageBreak->setMode(PageBreakCommand::BreakBeforeRow); 0535 pageBreak->setSheet(m_sheet); 0536 pageBreak->add(Region(1, rowFormat.row(), 1, 1, m_sheet)); 0537 pageBreak->setReverse(!rowFormat.hasPageBreak()); 0538 } 0539 } 0540 } 0541 } 0542 0543 if (e.tagName() == "cell") { 0544 // Create a new PasteCellCommand, if necessary. 0545 if (!pasteCellCommand) { 0546 pasteCellCommand = new PasteCellCommand(this); 0547 pasteCellCommand->setSheet(m_sheet); 0548 pasteCellCommand->m_pasteMode = m_pasteMode; 0549 pasteCellCommand->m_pasteOperation = m_operation; 0550 pasteCellCommand->m_pasteFC = m_pasteFC; 0551 } 0552 0553 // Source cell location: 0554 const int row = e.attribute("row").toInt(); 0555 const int col = e.attribute("column").toInt(); 0556 0557 // tile the selection with the clipboard contents 0558 for (int roff = 0; row + roff <= pasteHeight; roff += sourceHeight) { 0559 for (int coff = 0; col + coff <= pasteWidth; coff += sourceWidth) { 0560 debugSheetsUI << "cell at" << (col + xOffset + coff) << ',' << (row + yOffset + roff) 0561 << " with roff,coff=" << roff << ',' << coff 0562 << ", xOffset:" << xOffset << ", yOffset:" << yOffset << endl; 0563 0564 // Destination cell: 0565 const Cell cell(sheet, col + xOffset + coff, row + yOffset + roff); 0566 // Do nothing, if the sheet and the cell are protected. 0567 if (sheet->isProtected() && !cell.style().notProtected()) { 0568 continue; 0569 } 0570 // Add the destination cell and the XML element itself. 0571 pasteCellCommand->addXmlElement(cell, e); 0572 } 0573 } 0574 } 0575 } 0576 return true; 0577 } 0578 0579 bool PasteCommand::processTextPlain(Element *element) 0580 { 0581 const QString text = m_mimeData->text(); 0582 if (text.isEmpty()) { 0583 return false; 0584 } 0585 0586 // Split the text into lines. 0587 const QStringList list = text.split('\n'); 0588 0589 // const int mx = 1; // always one column 0590 const int my = list.count(); 0591 0592 // Put the lines into an array value. 0593 Value value(Value::Array); 0594 for (int i = 0; i < my; ++i) { 0595 value.setElement(0, i, Value(list[i])); 0596 } 0597 0598 // FIXME Determine and tile the destination area. 0599 // Region range(mx, my, 1, list.size()); 0600 0601 // create a command, configure it and execute it 0602 DataManipulator *command = new DataManipulator(this); 0603 command->setSheet(m_sheet); 0604 command->setParsing(false); 0605 command->setValue(value); 0606 command->add(element->rect(), m_sheet); 0607 return true; 0608 }