File indexing completed on 2024-05-19 16:08:03

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 
0021 #include "SortManipulator.h"
0022 
0023 #include "Map.h"
0024 #include "Sheet.h"
0025 #include "ValueCalc.h"
0026 #include "ValueConverter.h"
0027 
0028 #include <KLocalizedString>
0029 
0030 using namespace Calligra::Sheets;
0031 
0032 SortManipulator::SortManipulator()
0033         : AbstractDFManipulator()
0034         , m_cellStorage(0)
0035 {
0036     m_changeformat = false;
0037     m_rows = true;
0038     m_skipfirst = false;
0039     m_usecustomlist = false;
0040 
0041     setText(kundo2_i18n("Sort Data"));
0042 }
0043 
0044 SortManipulator::~SortManipulator()
0045 {
0046 }
0047 
0048 bool SortManipulator::process(Element* element)
0049 {
0050     // process one element - rectangular range
0051 
0052     // here we perform the actual sorting, remember the new ordering and
0053     // let AbstractDFManipulator::process do the rest of the work
0054     // the new ordering is used in newValue and newFormat to return proper
0055     // values
0056 
0057     // sort
0058     sort(element);
0059 
0060     // set values
0061     return AbstractDFManipulator::process(element);
0062 }
0063 
0064 bool SortManipulator::preProcessing()
0065 {
0066     // Only on sorting we need to temporarily store the old data.
0067     // On restoring (undo) we return immediately.
0068     if (m_reverse)
0069         return AbstractDFManipulator::preProcessing();
0070 
0071     m_cellStorage = new CellStorage(m_sheet->cellStorage()->subStorage(*this));
0072 
0073     Region::ConstIterator endOfList(cells().constEnd());
0074     for (Region::ConstIterator it = cells().constBegin(); it != endOfList; ++it) {
0075         QRect range = (*it)->rect();
0076         for (int col = range.left(); col <= range.right(); ++col)
0077             for (int row = range.top(); row <= range.bottom(); ++row) {
0078                 Cell cell = Cell(m_sheet, col, row);
0079                 m_styles.insert(cell, cell.style());
0080                 // encode the formula if there is one, so that cell references get updated correctly
0081                 if (cell.isFormula()) m_formulas.insert(cell, cell.encodeFormula());
0082             }
0083     }
0084 
0085     // to start undo recording
0086     return AbstractDFManipulator::preProcessing();
0087 }
0088 
0089 bool SortManipulator::postProcessing()
0090 {
0091     delete m_cellStorage;
0092     m_cellStorage = 0;
0093     m_styles.clear();
0094     m_formulas.clear();
0095 
0096     // to stop undo recording
0097     return AbstractDFManipulator::postProcessing();
0098 }
0099 
0100 void SortManipulator::addCriterion(int index, Qt::SortOrder order, Qt::CaseSensitivity caseSensitivity)
0101 {
0102     Criterion criterion;
0103     criterion.index = index;
0104     criterion.order = order;
0105     criterion.caseSensitivity = caseSensitivity;
0106     m_criteria.append(criterion);
0107 }
0108 
0109 void SortManipulator::clearCriteria()
0110 {
0111     m_criteria.clear();
0112 }
0113 
0114 Value SortManipulator::newValue(Element *element, int col, int row,
0115                                 bool *parse, Format::Type *)
0116 {
0117     QRect range = element->rect();
0118     int colidx = col - range.left();
0119     int rowidx = row - range.top();
0120     if (m_rows)  // sort rows
0121         rowidx = sorted[rowidx];
0122     else
0123         colidx = sorted[colidx];
0124     rowidx += range.top();
0125     colidx += range.left();
0126 
0127     // If the cell contained a formula, we need to decode it with the -new- coordinates, so that the references remain intact
0128     Cell orig_cell = Cell(m_sheet, colidx, rowidx);
0129     Cell new_cell = Cell(m_sheet, col, row);
0130     if (m_formulas.contains(orig_cell)) {
0131         *parse = true;
0132         return Value(new_cell.decodeFormula(m_formulas[orig_cell]));
0133     }
0134 
0135     *parse = false;
0136     // have to return the stored value, to prevent the earlier calls from disrupting the latter ones
0137     return m_cellStorage->value(colidx, rowidx);
0138 }
0139 
0140 Style SortManipulator::newFormat(Element *element, int col, int row)
0141 {
0142     QRect range = element->rect();
0143     int colidx = col - range.left();
0144     int rowidx = row - range.top();
0145     if (m_changeformat) {
0146         if (m_rows)  // sort rows
0147             rowidx = sorted[rowidx];
0148         else
0149             colidx = sorted[colidx];
0150     }
0151 
0152     // have to return stored format, to avoid earlier calls disrupting latter ones
0153     return m_styles.value(Cell(m_sheet, colidx + range.left(), rowidx + range.top()));
0154 }
0155 
0156 void SortManipulator::sort(Element *element)
0157 {
0158     // we'll use insert-sort to sort
0159     QRect range = element->rect();
0160     int max = m_rows ? range.bottom() : range.right();
0161     int min = m_rows ? range.top() : range.left();
0162     int count = max - min + 1;
0163     // initially, all values are at their original positions
0164     sorted.clear();
0165     for (int i = 0; i < count; ++i) sorted[i] = i;
0166 
0167     // for each position, find the lowest value and move it there
0168     int start = m_skipfirst ? 1 : 0;
0169     for (int i = start; i < count - 1; ++i) {
0170         int lowest = i;
0171         for (int j = i + 1; j < count; ++j)
0172             if (shouldReorder(element, sorted[lowest], sorted[j]))
0173                 lowest = j;
0174         // move lowest to start
0175         int tmp = sorted[i];
0176         sorted[i] = sorted[lowest];
0177         sorted[lowest] = tmp;
0178     }
0179 
0180     // that's all - process will take care of the rest, together with our
0181     // newValue/newFormat
0182 }
0183 
0184 bool SortManipulator::shouldReorder(Element *element, int first, int second)
0185 {
0186     // we use ValueCalc::natural* to compare
0187     // indexes are real indexes, we don't use the sorted array here
0188 
0189     ValueCalc *calc = m_sheet->map()->calc();
0190     ValueConverter *conv = m_sheet->map()->converter();
0191 
0192     QRect range = element->rect();
0193     int firstrow = range.top();
0194     int firstcol = range.left();
0195 
0196     for (int i = 0; i < m_criteria.count(); ++i) {
0197         int which = m_criteria[i].index;
0198         bool ascending = m_criteria[i].order == Qt::AscendingOrder;
0199         bool caseSensitive = m_criteria[i].caseSensitivity == Qt::CaseSensitive;
0200 
0201         // figure out coordinates of the cells
0202         int row1 = firstrow + (m_rows ? first : which);
0203         int row2 = firstrow + (m_rows ? second : which);
0204         int col1 = firstcol + (m_rows ? which : first);
0205         int col2 = firstcol + (m_rows ? which : second);
0206         Value val1 = Cell(m_sheet, col1, row1).value();
0207         Value val2 = Cell(m_sheet, col2, row2).value();
0208         // empty values always go to the end, so if second value is empty and
0209         // first one is not, we don't need to reorder
0210         if (!val1.isEmpty() && val2.isEmpty())
0211             return false;
0212         if (val1.isEmpty() && !val2.isEmpty())
0213             return true;
0214 
0215         // custom list ?
0216         if (m_usecustomlist) {
0217             QString s1 = conv->asString(val1).asString().toLower();
0218             QString s2 = conv->asString(val2).asString().toLower();
0219             int pos1 = -1, pos2 = -1;
0220             int pos = 0;
0221             // Try to locate our two strings in the list. If both are there, assume
0222             // ordering as specified by the list.
0223             for (QStringList::ConstIterator  it = m_customlist.constBegin(); it != m_customlist.constEnd(); ++it) {
0224                 if ((pos1 == -1) && ((*it).toLower() == s1))
0225                     pos1 = pos;
0226                 if ((pos2 == -1) && ((*it).toLower() == s2))
0227                     pos2 = pos;
0228                 pos++;
0229             }
0230             if ((pos1 >= 0) && (pos2 >= 0) && (pos1 != pos2))
0231                 // both are in the list, not the same
0232                 return (pos1 > pos2);
0233         }
0234 
0235         if (calc->naturalGreater(val1, val2, caseSensitive))
0236             // first one greater - must reorder if ascending, don't reorder if not
0237             return ascending;
0238         if (calc->naturalLower(val1, val2, caseSensitive))
0239             // first one lower - don't reorder if ascending, reorder if not
0240             return !ascending;
0241         // equal - don't know yet, continue
0242     }
0243 
0244     // no difference found, they're the same - no need to reorder
0245     return false;
0246 }