File indexing completed on 2024-05-12 16:35:11
0001 /* This file is part of the KDE project 0002 Copyright (C) 2006 Tomas Mecir <mecirt@gmail.com> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; only 0007 version 2 of the License. 0008 0009 This library is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include <algorithm> 0021 0022 #include "DataManipulators.h" 0023 0024 #include <KLocalizedString> 0025 0026 #include "Cell.h" 0027 #include "CellStorage.h" 0028 #include "Damages.h" 0029 #include "Formula.h" 0030 #include "Map.h" 0031 #include "Sheet.h" 0032 #include "ValueCalc.h" 0033 #include "ValueConverter.h" 0034 0035 #include <float.h> 0036 #include <math.h> 0037 0038 using namespace Calligra::Sheets; 0039 0040 AbstractDataManipulator::AbstractDataManipulator(KUndo2Command* parent) 0041 : AbstractRegionCommand(parent) 0042 { 0043 m_checkLock = true; 0044 } 0045 0046 AbstractDataManipulator::~AbstractDataManipulator() 0047 { 0048 } 0049 0050 bool AbstractDataManipulator::process(Element* element) 0051 { 0052 QRect range = element->rect(); 0053 for (int col = range.left(); col <= range.right(); ++col) 0054 for (int row = range.top(); row <= range.bottom(); ++row) { 0055 Value val; 0056 QString text; 0057 // int colidx = col - range.left(); 0058 // int rowidx = row - range.top(); 0059 bool parse = false; 0060 Format::Type fmtType = Format::None; 0061 0062 // do nothing if we don't want a change here 0063 if (!wantChange(element, col, row)) 0064 continue; 0065 0066 val = newValue(element, col, row, &parse, &fmtType); 0067 0068 Cell cell = Cell(m_sheet, col, row); 0069 if (cell.isPartOfMerged()) cell = cell.masterCell(); 0070 0071 // we have the data - set it ! 0072 if (parse) { 0073 if (fmtType != Format::None) { 0074 Style style; 0075 style.setFormatType(fmtType); 0076 cell.setStyle(style); 0077 } 0078 cell.parseUserInput(val.asString()); 0079 } else { 0080 cell.setValue(val); // val can be empty - that's fine 0081 cell.setUserInput(m_sheet->map()->converter()->asString(val).asString()); 0082 if (fmtType != Format::None) { 0083 Style style; 0084 style.setFormatType(fmtType); 0085 cell.setStyle(style); 0086 } 0087 } 0088 } 0089 return true; 0090 } 0091 0092 bool AbstractDataManipulator::preProcessing() 0093 { 0094 // not the first run - data already stored ... 0095 if (!m_firstrun) 0096 return true; 0097 m_sheet->cellStorage()->startUndoRecording(); 0098 return AbstractRegionCommand::preProcessing(); 0099 } 0100 0101 bool AbstractDataManipulator::mainProcessing() 0102 { 0103 if (m_reverse) { 0104 // reverse - use the stored value 0105 KUndo2Command::undo(); // undo child commands 0106 return true; 0107 } 0108 return AbstractRegionCommand::mainProcessing(); 0109 } 0110 0111 bool AbstractDataManipulator::postProcessing() 0112 { 0113 // not the first run - data already stored ... 0114 if (!m_firstrun) 0115 return true; 0116 m_sheet->cellStorage()->stopUndoRecording(this); 0117 return true; 0118 } 0119 0120 AbstractDFManipulator::AbstractDFManipulator(KUndo2Command *parent) 0121 : AbstractDataManipulator(parent) 0122 { 0123 m_changeformat = true; 0124 } 0125 0126 AbstractDFManipulator::~AbstractDFManipulator() 0127 { 0128 } 0129 0130 bool AbstractDFManipulator::process(Element* element) 0131 { 0132 // let parent class process it first 0133 AbstractDataManipulator::process(element); 0134 0135 // don't continue if we don't have to change formatting 0136 if (!m_changeformat) return true; 0137 if (m_reverse) return true; // undo done by AbstractDataManipulator 0138 0139 QRect range = element->rect(); 0140 for (int col = range.left(); col <= range.right(); ++col) { 0141 for (int row = range.top(); row <= range.bottom(); ++row) { 0142 Cell cell(m_sheet, col, row); 0143 // int colidx = col - range.left(); 0144 // int rowidx = row - range.top(); 0145 Style style = newFormat(element, col, row); 0146 cell.setStyle(style); 0147 } 0148 } 0149 return true; 0150 } 0151 0152 0153 DataManipulator::DataManipulator(KUndo2Command* parent) 0154 : AbstractDataManipulator(parent) 0155 , m_format(Format::None) 0156 , m_parsing(false) 0157 , m_expandMatrix(false) 0158 { 0159 // default name for DataManipulator, can be changed using setText 0160 setText(kundo2_i18n("Change Value")); 0161 } 0162 0163 DataManipulator::~DataManipulator() 0164 { 0165 } 0166 0167 bool DataManipulator::preProcessing() 0168 { 0169 // extend a singular region to the matrix size, if applicable 0170 if (m_firstrun && m_parsing && m_expandMatrix && Region::isSingular()) { 0171 const QString expression = m_data.asString(); 0172 if (!expression.isEmpty() && expression[0] == '=') { 0173 Formula formula(m_sheet); 0174 formula.setExpression(expression); 0175 if (formula.isValid()) { 0176 const Value result = formula.eval(); 0177 if (result.columns() > 1 || result.rows() > 1) { 0178 const QPoint point = cells()[0]->rect().topLeft(); 0179 Region::add(QRect(point.x(), point.y(), result.columns(), result.rows()), m_sheet); 0180 } 0181 } 0182 } else if (!m_data.isArray()) { 0183 // not a formula; not a matrix: unset m_expandMatrix 0184 m_expandMatrix = false; 0185 } 0186 } 0187 return AbstractDataManipulator::preProcessing(); 0188 } 0189 0190 bool DataManipulator::process(Element* element) 0191 { 0192 bool success = AbstractDataManipulator::process(element); 0193 if (!success) 0194 return false; 0195 if (!m_reverse) { 0196 // Only lock cells, if expansion is desired and the value is a formula. 0197 if (m_expandMatrix && (m_data.asString().isEmpty() || m_data.asString().at(0) == '=')) 0198 m_sheet->cellStorage()->lockCells(element->rect()); 0199 } 0200 return true; 0201 } 0202 0203 bool DataManipulator::wantChange(Element *element, int col, int row) 0204 { 0205 if (m_expandMatrix) { 0206 QRect range = element->rect(); 0207 int colidx = col - range.left(); 0208 int rowidx = row - range.top(); 0209 // don't set this value, RecalcManager already did it 0210 if (colidx || rowidx) return false; 0211 } 0212 return true; 0213 } 0214 0215 Value DataManipulator::newValue(Element *element, int col, int row, 0216 bool *parsing, Format::Type *formatType) 0217 { 0218 *parsing = m_parsing; 0219 if (m_format != Format::None) 0220 *formatType = m_format; 0221 QRect range = element->rect(); 0222 int colidx = col - range.left(); 0223 int rowidx = row - range.top(); 0224 return m_data.element(colidx, rowidx); 0225 } 0226 0227 0228 SeriesManipulator::SeriesManipulator() 0229 { 0230 setText(kundo2_i18n("Insert Series")); 0231 0232 m_type = Linear; 0233 m_last = -2; 0234 } 0235 0236 SeriesManipulator::~SeriesManipulator() 0237 { 0238 } 0239 0240 void SeriesManipulator::setupSeries(const QPoint &_marker, double start, 0241 double end, double step, Series mode, Series type) 0242 { 0243 m_type = type; 0244 m_start = Value(start); 0245 m_step = Value(step); 0246 // compute cell count 0247 int numberOfCells = 1; 0248 if (type == Linear) 0249 numberOfCells = (int)((end - start) / step + 1); 0250 if (type == Geometric) 0251 /* basically, A(n) = start * step ^ n 0252 * so when is end >= start * step ^ n ?? 0253 * when n = ln(end/start) / ln(step) 0254 */ 0255 // DBL_EPSILON is added to prevent rounding errors 0256 numberOfCells = (int)(::log(end / start) / ::log(step) + DBL_EPSILON) + 1; 0257 0258 // with this, generate range information 0259 Region range(_marker.x(), _marker.y(), (mode == Column) ? 1 : numberOfCells, 0260 (mode == Row) ? 1 : numberOfCells); 0261 0262 // and add the range to the manipulator 0263 add(range); 0264 } 0265 0266 Value SeriesManipulator::newValue(Element *element, int col, int row, 0267 bool *parse, Format::Type *) 0268 { 0269 *parse = false; 0270 ValueCalc *calc = m_sheet->map()->calc(); 0271 0272 // either colidx or rowidx is always zero 0273 QRect range = element->rect(); 0274 int colidx = col - range.left(); 0275 int rowidx = row - range.top(); 0276 int which = (colidx > 0) ? colidx : rowidx; 0277 Value val; 0278 if (which == m_last + 1) { 0279 // if we are requesting next item in the series, which should almost always 0280 // be the case, we can use the pre-computed value to speed up the process 0281 if (m_type == Linear) 0282 val = calc->add(m_prev, m_step); 0283 if (m_type == Geometric) 0284 val = calc->mul(m_prev, m_step); 0285 } else { 0286 // otherwise compute from scratch 0287 val = m_start; 0288 for (int i = 0; i < which; ++i) { 0289 if (m_type == Linear) 0290 val = calc->add(val, m_step); 0291 if (m_type == Geometric) 0292 val = calc->mul(val, m_step); 0293 } 0294 } 0295 // store last value 0296 m_prev = val; 0297 m_last = which; 0298 0299 // return the computed value 0300 return val; 0301 } 0302 0303 0304 FillManipulator::FillManipulator() 0305 { 0306 m_dir = Down; 0307 m_changeformat = true; 0308 setText(kundo2_i18n("Fill Selection")); 0309 } 0310 0311 FillManipulator::~FillManipulator() 0312 { 0313 } 0314 0315 Value FillManipulator::newValue(Element *element, int col, int row, 0316 bool *parse, Format::Type *fmtType) 0317 { 0318 Q_UNUSED(fmtType); 0319 const int targetRow = row; 0320 const int targetCol = col; 0321 switch (m_dir) { 0322 case Up: row = element->rect().bottom(); break; 0323 case Down: row = element->rect().top(); break; 0324 case Left: col = element->rect().right(); break; 0325 case Right: col = element->rect().left(); break; 0326 }; 0327 Cell cell(m_sheet, col, row); // the reference cell 0328 if (cell.isFormula()) { 0329 *parse = true; 0330 return Value(Cell(m_sheet, targetCol, targetRow).decodeFormula(cell.encodeFormula())); 0331 } 0332 return cell.value(); 0333 } 0334 0335 Style FillManipulator::newFormat(Element *element, int col, int row) 0336 { 0337 switch (m_dir) { 0338 case Up: row = element->rect().bottom(); break; 0339 case Down: row = element->rect().top(); break; 0340 case Left: col = element->rect().right(); break; 0341 case Right: col = element->rect().left(); break; 0342 }; 0343 return Cell(m_sheet, col, row).style(); 0344 } 0345 0346 CaseManipulator::CaseManipulator() 0347 { 0348 m_mode = Upper; 0349 setText(kundo2_i18n("Change Case")); 0350 } 0351 0352 CaseManipulator::~CaseManipulator() 0353 { 0354 } 0355 0356 Value CaseManipulator::newValue(Element *element, int col, int row, 0357 bool *parse, Format::Type *) 0358 { 0359 Q_UNUSED(element) 0360 // if we are here, we know that we want the change 0361 *parse = false; 0362 QString str = Cell(m_sheet, col, row).value().asString(); 0363 switch (m_mode) { 0364 case Upper: str = str.toUpper(); 0365 break; 0366 case Lower: str = str.toLower(); 0367 break; 0368 case FirstUpper: 0369 if (str.length() > 0) 0370 str = str.at(0).toUpper() + str.right(str.length() - 1); 0371 break; 0372 }; 0373 return Value(str); 0374 } 0375 0376 bool CaseManipulator::wantChange(Element *element, int col, int row) 0377 { 0378 Q_UNUSED(element) 0379 Cell cell(m_sheet, col, row); 0380 // don't change cells with a formula 0381 if (cell.isFormula()) 0382 return false; 0383 // don't change cells containing other things than strings 0384 if (!cell.value().isString()) 0385 return false; 0386 // original version was dismissing text starting with '!' and '*', is this 0387 // necessary ? 0388 return true; 0389 } 0390 0391 0392 0393 ShiftManipulator::ShiftManipulator(KUndo2Command *parent) 0394 : AbstractRegionCommand(parent) 0395 , m_mode(Insert) 0396 { 0397 m_checkLock = true; 0398 setText(kundo2_i18n("Insert Cells")); 0399 } 0400 0401 ShiftManipulator::~ShiftManipulator() 0402 { 0403 } 0404 0405 void ShiftManipulator::setReverse(bool reverse) 0406 { 0407 m_reverse = reverse; 0408 m_mode = reverse ? Delete : Insert; 0409 if (!m_reverse) 0410 setText(kundo2_i18n("Insert Cells")); 0411 else 0412 setText(kundo2_i18n("Remove Cells")); 0413 } 0414 0415 bool ShiftManipulator::process(Element* element) 0416 { 0417 const QRect range = element->rect(); 0418 if (!m_reverse) { // insertion 0419 if (m_direction == ShiftBottom) { 0420 m_sheet->insertShiftDown(range); 0421 m_sheet->cellStorage()->insertShiftDown(range); 0422 } else if (m_direction == ShiftRight) { 0423 m_sheet->insertShiftRight(range); 0424 m_sheet->cellStorage()->insertShiftRight(range); 0425 } 0426 0427 // undo deletion 0428 if (m_mode == Delete) { 0429 KUndo2Command::undo(); // undo child commands 0430 } 0431 } else { // deletion 0432 if (m_direction == ShiftBottom) { 0433 m_sheet->removeShiftUp(range); 0434 m_sheet->cellStorage()->removeShiftUp(range); 0435 } else if (m_direction == ShiftRight) { 0436 m_sheet->removeShiftLeft(range); 0437 m_sheet->cellStorage()->removeShiftLeft(range); 0438 } 0439 0440 // undo insertion 0441 if (m_mode == Insert) { 0442 KUndo2Command::undo(); // undo child commands 0443 } 0444 } 0445 return true; 0446 } 0447 0448 namespace Calligra 0449 { 0450 namespace Sheets 0451 { 0452 bool topRowLessThan(const Region::Element *e1, const Region::Element *e2) 0453 { 0454 return e1->rect().top() < e2->rect().top(); 0455 } 0456 0457 bool leftColumnLessThan(const Region::Element *e1, const Region::Element *e2) 0458 { 0459 return e1->rect().top() < e2->rect().top(); 0460 } 0461 } // namespace Sheets 0462 } // namespace Calligra 0463 0464 bool ShiftManipulator::preProcessing() 0465 { 0466 if (m_firstrun) { 0467 // If we have an NCS, create a child command for each element. 0468 if (cells().count() > 1) { // non-contiguous selection 0469 // Sort the elements by their top row. 0470 if (m_direction == ShiftBottom) { 0471 std::stable_sort(cells().begin(), cells().end(), topRowLessThan); 0472 } else { // ShiftRight 0473 std::stable_sort(cells().begin(), cells().end(), leftColumnLessThan); 0474 } 0475 // Create sub-commands. 0476 const Region::ConstIterator end(constEnd()); 0477 for (Region::ConstIterator it = constBegin(); it != end; ++it) { 0478 ShiftManipulator *const command = new ShiftManipulator(this); 0479 command->setSheet(m_sheet); 0480 command->add(Region((*it)->rect(), (*it)->sheet())); 0481 if (m_mode == Delete) { 0482 command->setReverse(true); 0483 } 0484 command->setDirection(m_direction); 0485 } 0486 } else { // contiguous selection 0487 m_sheet->cellStorage()->startUndoRecording(); 0488 } 0489 } 0490 return AbstractRegionCommand::preProcessing(); 0491 } 0492 0493 bool ShiftManipulator::mainProcessing() 0494 { 0495 if (cells().count() > 1) { // non-contiguous selection 0496 if ((m_reverse && m_mode == Insert) || (!m_reverse && m_mode == Delete)) { 0497 KUndo2Command::undo(); // process all sub-commands 0498 } else { 0499 KUndo2Command::redo(); // process all sub-commands 0500 } 0501 return true; 0502 } 0503 return AbstractRegionCommand::mainProcessing(); // calls process(Element*) 0504 } 0505 0506 bool ShiftManipulator::postProcessing() 0507 { 0508 if (cells().count() > 1) { // non-contiguous selection 0509 return true; 0510 } 0511 if (m_firstrun) { 0512 m_sheet->cellStorage()->stopUndoRecording(this); 0513 } 0514 CellDamage *damage = 0; 0515 if (m_direction == ShiftBottom) { 0516 const QPoint bottomRight(lastRange().right(), KS_rowMax); 0517 const Region region(QRect(lastRange().topLeft(), bottomRight), m_sheet); 0518 damage = new CellDamage(m_sheet, region, CellDamage::Appearance); 0519 } else { // ShiftRight 0520 const QPoint bottomRight(KS_colMax, lastRange().bottom()); 0521 const Region region(QRect(lastRange().topLeft(), bottomRight), m_sheet); 0522 damage = new CellDamage(m_sheet, region, CellDamage::Appearance); 0523 } 0524 m_sheet->map()->addDamage(damage); 0525 return true; 0526 }