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 }