File indexing completed on 2024-04-28 16:21:20

0001 /* This file is part of the KDE project
0002    Copyright 2010 Marijn Kruisselbrink <mkruisselbrink@kde.org>
0003    Copyright 1998, 1999 Torben Weis <weis@kde.org>
0004    Copyright 1999- 2006 The KSpread Team <calligra-devel@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 // Local
0023 #include "Condition.h"
0024 
0025 #include <float.h>
0026 
0027 #include "SheetsDebug.h"
0028 #include "CalculationSettings.h"
0029 #include "Cell.h"
0030 #include "Formula.h"
0031 #include "Map.h"
0032 #include "NamedAreaManager.h"
0033 #include "Region.h"
0034 #include "Sheet.h"
0035 #include "Style.h"
0036 #include "StyleManager.h"
0037 #include "ValueCalc.h"
0038 #include "ValueConverter.h"
0039 #include "ValueParser.h"
0040 
0041 
0042 #include <QDomDocument>
0043 
0044 using namespace Calligra::Sheets;
0045 
0046 /////////////////////////////////////////////////////////////////////////////
0047 //
0048 // Conditional
0049 //
0050 /////////////////////////////////////////////////////////////////////////////
0051 
0052 Conditional::Conditional()
0053     : cond(None)
0054 {
0055 }
0056 
0057 bool Conditional::operator==(const Conditional &other) const
0058 {
0059     if (cond != other.cond) {
0060         return false;
0061     }
0062     if (!value1.equal(other.value1)) {
0063         return false;
0064     }
0065     if (!value2.equal(other.value2)) {
0066         return false;
0067     }
0068     return styleName == other.styleName;
0069 }
0070 /////////////////////////////////////////////////////////////////////////////
0071 //
0072 // Conditions
0073 //
0074 /////////////////////////////////////////////////////////////////////////////
0075 
0076 class Q_DECL_HIDDEN Conditions::Private : public QSharedData
0077 {
0078 public:
0079     QLinkedList<Conditional> conditionList;
0080     Style defaultStyle;
0081 };
0082 
0083 Conditions::Conditions()
0084         : d(new Private)
0085 {
0086 }
0087 
0088 Conditions::Conditions(const Conditions& other)
0089         : d(other.d)
0090 {
0091 }
0092 
0093 Conditions::~Conditions()
0094 {
0095 }
0096 
0097 bool Conditions::isEmpty() const
0098 {
0099     return d->conditionList.isEmpty();
0100 }
0101 
0102 Style Conditions::testConditions( const Cell& cell ) const
0103 {
0104     Conditional condition;
0105     if (currentCondition(cell, condition)) {
0106         StyleManager *const styleManager = cell.sheet()->map()->styleManager();
0107         Style *const style = styleManager->style(condition.styleName);
0108         if (style)
0109             return *style;
0110     }
0111     return d->defaultStyle;
0112 }
0113 
0114 bool Conditions::currentCondition(const Cell& cell, Conditional & condition) const
0115 {
0116     /* for now, the first condition that is true is the one that will be used */
0117 
0118     const Value value = cell.value();
0119     ValueCalc *const calc = cell.sheet()->map()->calc();
0120 
0121     QLinkedList<Conditional>::const_iterator it;
0122     for (it = d->conditionList.begin(); it != d->conditionList.end(); ++it) {
0123         condition = *it;
0124 //         debugSheets << "Checking condition resulting in applying" << it->styleName;
0125 
0126         // The first value of the condition is always used and has to be
0127         // comparable to the cell's value.
0128         if (!value.allowComparison(condition.value1)) {
0129             continue;
0130         }
0131 
0132         switch (condition.cond) {
0133         case Conditional::Equal:
0134             if (value.equal(condition.value1, calc->settings()->caseSensitiveComparisons())) {
0135                 return true;
0136             }
0137             break;
0138         case Conditional::Superior:
0139             if (value.greater(condition.value1, calc->settings()->caseSensitiveComparisons())) {
0140                 return true;
0141             }
0142             break;
0143         case Conditional::Inferior:
0144             if (value.less(condition.value1, calc->settings()->caseSensitiveComparisons())) {
0145                 return true;
0146             }
0147             break;
0148         case Conditional::SuperiorEqual:
0149             if (value.compare(condition.value1, calc->settings()->caseSensitiveComparisons()) >= 0) {
0150                 return true;
0151             }
0152             break;
0153         case Conditional::InferiorEqual:
0154             if (value.compare(condition.value1, calc->settings()->caseSensitiveComparisons()) <= 0) {
0155                 return true;
0156             }
0157             break;
0158         case Conditional::Between: {
0159             const QVector<Value> values(QVector<Value>() << condition.value1 << condition.value2);
0160             const Value min = calc->min(values);
0161             const Value max = calc->max(values);
0162             if (value.compare(min, calc->settings()->caseSensitiveComparisons()) >= 0
0163                     && value.compare(max, calc->settings()->caseSensitiveComparisons()) <= 0) {
0164                 return true;
0165             }
0166             break;
0167         }
0168         case Conditional::Different: {
0169             const QVector<Value> values(QVector<Value>() << condition.value1 << condition.value2);
0170             const Value min = calc->min(values);
0171             const Value max = calc->max(values);
0172             if (value.greater(max, calc->settings()->caseSensitiveComparisons())
0173                     || value.less(min, calc->settings()->caseSensitiveComparisons())) {
0174                 return true;
0175             }
0176             break;
0177         }
0178         case Conditional::DifferentTo:
0179             if (!value.equal(condition.value1, calc->settings()->caseSensitiveComparisons())) {
0180                 return true;
0181             }
0182             break;
0183         case Conditional::IsTrueFormula:
0184             // TODO: do some caching
0185             if (isTrueFormula(cell, condition.value1.asString(), condition.baseCellAddress)) {
0186                 return true;
0187             }
0188             break;
0189         default:
0190             break;
0191         }
0192     }
0193     return false;
0194 }
0195 
0196 bool Conditions::isTrueFormula(const Cell &cell, const QString &formula, const QString &baseCellAddress) const
0197 {
0198     Map* const map = cell.sheet()->map();
0199     ValueCalc *const calc = map->calc();
0200     Formula f(cell.sheet(), cell);
0201     f.setExpression('=' + formula);
0202     Region r(baseCellAddress, map, cell.sheet());
0203     if (r.isValid() && r.isSingular()) {
0204         QPoint basePoint = static_cast<Region::Point*>(*r.constBegin())->pos();
0205         QString newFormula('=');
0206         const Tokens tokens = f.tokens();
0207         for (int t = 0; t < tokens.count(); ++t) {
0208             const Token token = tokens[t];
0209             if (token.type() == Token::Cell || token.type() == Token::Range) {
0210                 if (map->namedAreaManager()->contains(token.text())) {
0211                     newFormula.append(token.text());
0212                     continue;
0213                 }
0214                 const Region region(token.text(), map, cell.sheet());
0215                 if (!region.isValid() || !region.isContiguous()) {
0216                     newFormula.append(token.text());
0217                     continue;
0218                 }
0219                 if (region.firstSheet() != r.firstSheet()) {
0220                     newFormula.append(token.text());
0221                     continue;
0222                 }
0223                 Region::Element* element = *region.constBegin();
0224                 if (element->type() == Region::Element::Point) {
0225                     Region::Point* point = static_cast<Region::Point*>(element);
0226                     QPoint pos = point->pos();
0227                     if (!point->isRowFixed()) {
0228                         int delta = pos.y() - basePoint.y();
0229                         pos.setY(cell.row() + delta);
0230                     }
0231                     if (!point->isColumnFixed()) {
0232                         int delta = pos.x() - basePoint.x();
0233                         pos.setX(cell.column() + delta);
0234                     }
0235                     newFormula.append(Region(pos, cell.sheet()).name());
0236                 } else {
0237                     Region::Range* range = static_cast<Region::Range*>(element);
0238                     QRect r = range->rect();
0239                     if (!range->isTopFixed()) {
0240                         int delta = r.top() - basePoint.y();
0241                         r.setTop(cell.row() + delta);
0242                     }
0243                     if (!range->isBottomFixed()) {
0244                         int delta = r.bottom() - basePoint.y();
0245                         r.setBottom(cell.row() + delta);
0246                     }
0247                     if (!range->isLeftFixed()) {
0248                         int delta = r.left() - basePoint.x();
0249                         r.setLeft(cell.column() + delta);
0250                     }
0251                     if (!range->isRightFixed()) {
0252                         int delta = r.right() - basePoint.x();
0253                         r.setRight(cell.column() + delta);
0254                     }
0255                     newFormula.append(Region(r, cell.sheet()).name());
0256                 }
0257             } else {
0258                 newFormula.append(token.text());
0259             }
0260         }
0261         f.setExpression(newFormula);
0262     }
0263     Value val = f.eval();
0264     return calc->conv()->asBoolean(val).asBoolean();
0265 }
0266 
0267 QLinkedList<Conditional> Conditions::conditionList() const
0268 {
0269     return d->conditionList;
0270 }
0271 
0272 void Conditions::setConditionList(const QLinkedList<Conditional> & list)
0273 {
0274     d->conditionList = list;
0275 }
0276 
0277 Style Conditions::defaultStyle() const
0278 {
0279     return d->defaultStyle;
0280 }
0281 
0282 void Conditions::setDefaultStyle(const Style &style)
0283 {
0284     d->defaultStyle = style;
0285 }
0286 
0287 void Conditions::addCondition(Conditional cond)
0288 {
0289     d->conditionList.append(cond);
0290 }
0291 
0292 
0293 QDomElement Conditions::saveConditions(QDomDocument &doc, ValueConverter *converter) const
0294 {
0295     QDomElement conditions = doc.createElement("condition");
0296     QLinkedList<Conditional>::const_iterator it;
0297     QDomElement child;
0298     int num = 0;
0299     QString name;
0300 
0301     for (it = d->conditionList.begin(); it != d->conditionList.end(); ++it) {
0302         Conditional condition = *it;
0303 
0304         /* the name of the element will be "condition<n>"
0305             * This is unimportant now but in older versions three conditions were
0306             * hardcoded with names "first" "second" and "third"
0307         */
0308         name.setNum(num);
0309         name.prepend("condition");
0310 
0311         child = doc.createElement(name);
0312         child.setAttribute("cond", QString::number((int) condition.cond));
0313 
0314         // TODO: saving in KSpread 1.1 | KSpread 1.2 format
0315         if (condition.value1.isString()) {
0316             child.setAttribute("strval1", condition.value1.asString());
0317             if (!condition.value2.asString().isEmpty()) {
0318                 child.setAttribute("strval2", condition.value2.asString());
0319             }
0320         } else {
0321             child.setAttribute("val1", converter->asString(condition.value1).asString());
0322             child.setAttribute("val2", converter->asString(condition.value2).asString());
0323         }
0324         if (!condition.styleName.isEmpty()) {
0325             child.setAttribute("style", condition.styleName);
0326         }
0327 
0328         conditions.appendChild(child);
0329 
0330         ++num;
0331     }
0332 
0333     if (num == 0) {
0334         /* there weren't any real conditions -- return a null dom element */
0335         return QDomElement();
0336     } else {
0337         return conditions;
0338     }
0339 }
0340 
0341 void Conditions::loadConditions(const KoXmlElement &element, const ValueParser *parser)
0342 {
0343     Conditional newCondition;
0344 
0345     KoXmlElement conditionElement;
0346     forEachElement(conditionElement, element) {
0347         if (!conditionElement.hasAttribute("cond"))
0348             continue;
0349 
0350         bool ok = true;
0351         newCondition.cond = (Conditional::Type) conditionElement.attribute("cond").toInt(&ok);
0352         if(!ok)
0353             continue;
0354 
0355         if (conditionElement.hasAttribute("val1")) {
0356             newCondition.value1 = parser->parse(conditionElement.attribute("val1"));
0357 
0358             if (conditionElement.hasAttribute("val2"))
0359                 newCondition.value2 = parser->parse(conditionElement.attribute("val2"));
0360         }
0361 
0362         if (conditionElement.hasAttribute("strval1")) {
0363             newCondition.value1 = Value(conditionElement.attribute("strval1"));
0364 
0365             if (conditionElement.hasAttribute("strval2"))
0366                 newCondition.value2 = Value(conditionElement.attribute("strval2"));
0367         }
0368 
0369         if (conditionElement.hasAttribute("style")) {
0370             newCondition.styleName = conditionElement.attribute("style");
0371         }
0372 
0373         d->conditionList.append(newCondition);
0374     }
0375 }
0376 
0377 void Conditions::operator=(const Conditions & other)
0378 {
0379     d = other.d;
0380 }
0381 
0382 bool Conditions::operator==(const Conditions& other) const
0383 {
0384     if (d->defaultStyle != other.d->defaultStyle)
0385         return false;
0386     if (d->conditionList.count() != other.d->conditionList.count())
0387         return false;
0388     QLinkedList<Conditional>::ConstIterator end(d->conditionList.end());
0389     for (QLinkedList<Conditional>::ConstIterator it(d->conditionList.begin()); it != end; ++it) {
0390         bool found = false;
0391         QLinkedList<Conditional>::ConstIterator otherEnd(other.d->conditionList.end());
0392         for (QLinkedList<Conditional>::ConstIterator otherIt(other.d->conditionList.begin()); otherIt != otherEnd; ++otherIt) {
0393             if ((*it) == (*otherIt))
0394                 found = true;
0395         }
0396         if (!found)
0397             return false;
0398     }
0399     return true;
0400 }
0401 
0402 uint Calligra::Sheets::qHash(const Conditions &c)
0403 {
0404     uint res = qHash(c.defaultStyle());
0405     foreach (const Conditional& co, c.conditionList()) {
0406         res ^= qHash(co);
0407     }
0408     return res;
0409 }
0410 
0411 uint Calligra::Sheets::qHash(const Conditional& c)
0412 {
0413     return qHash(c.value1);
0414 }