diff --git a/kspread/Selection.cpp b/kspread/Selection.cpp index f4b85f100d..8fafacb02d 100644 --- a/kspread/Selection.cpp +++ b/kspread/Selection.cpp @@ -1,1083 +1,1128 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2005-2006 Stefan Nikolaus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local #include "Selection.h" #include #include #include "Cell.h" #include "CellStorage.h" #include "ui/Editors.h" #include "RowColumnFormat.h" #include "Sheet.h" #include "commands/DataManipulators.h" using namespace KSpread; +// TODO +// - Allow resizing of all ranges in a normal selection; not just the last one. +// - Get rid of anchor and marker. They are the corners of the active element. + + /*************************************************************************** class Selection::Private ****************************************************************************/ class Selection::Private { public: Private() { activeSheet = 0; originSheet = 0; anchor = QPoint(1, 1); cursor = QPoint(1, 1); marker = QPoint(1, 1); colors.push_back(Qt::red); colors.push_back(Qt::blue); colors.push_back(Qt::magenta); colors.push_back(Qt::darkRed); colors.push_back(Qt::darkGreen); colors.push_back(Qt::darkMagenta); colors.push_back(Qt::darkCyan); colors.push_back(Qt::darkYellow); multipleOccurences = false; selectionMode = MultipleCells; - activeElement = 0; + activeElement = 1; activeSubRegionStart = 0; activeSubRegionLength = 1; canvasBase = 0; referenceMode = false; } Sheet* activeSheet; Sheet* originSheet; QPoint anchor; QPoint cursor; QPoint marker; QList colors; bool multipleOccurences : 1; Mode selectionMode : 2; // For reference selections this selection represents all references in a // formula. The user can place the text cursor at any reference while // editing the formula. Such a reference may not just be a contiguous range, // but a non-contiguous sub-region. // (Even though the text delimiter that separates ranges in a sub-region, // ';', is also used as delimiter for function arguments. Functions, that // accept two or more adjacent references as arguments cannot cope with // non-contiguous references for this reason. In this case it's up to the // user to select references, that serve the function's needs.) // That's what the next three variables are for. // For 'normal' selections these variables are actually superfluous, but may // be used in conjunction with the reference selection where appropriate. int activeElement; // the active range in a referenced sub-region int activeSubRegionStart; // the start of a referenced sub-region int activeSubRegionLength; // the length of a referenced sub-region KoCanvasBase* canvasBase; bool referenceMode : 1; Region formerSelection; // for reference selection mode Region oldSelection; // for select all }; /*************************************************************************** class Selection ****************************************************************************/ Selection::Selection(KoCanvasBase* canvasBase) : KoToolSelection(0) , Region(1, 1) , d(new Private()) { d->canvasBase = canvasBase; } Selection::Selection(const Selection& selection) : KoToolSelection(selection.parent()) , Region() , d(new Private()) { d->activeSheet = selection.d->activeSheet; d->originSheet = selection.d->originSheet; - d->activeElement = cells().count() - 1; + d->activeElement = cells().count(); d->activeSubRegionStart = 0; d->activeSubRegionLength = cells().count(); d->canvasBase = selection.d->canvasBase; } Selection::~Selection() { delete d; } KoCanvasBase* Selection::canvas() const { return d->canvasBase; } void Selection::initialize(const QPoint& point, Sheet* sheet) { if (!isValid(point)) return; if (!d->activeSheet) return; if (!sheet) { if (d->originSheet) { sheet = d->originSheet; } else { sheet = d->activeSheet; } } Region changedRegion(*this); changedRegion.add(extendToMergedAreas(QRect(d->anchor, d->marker))); // for the case of a merged cell QPoint topLeft(point); Cell cell(d->activeSheet, point); if (cell.isPartOfMerged()) { cell = cell.masterCell(); topLeft = QPoint(cell.column(), cell.row()); } d->anchor = topLeft; d->cursor = point; d->marker = topLeft; fixSubRegionDimension(); // TODO remove this sanity check int index = d->activeSubRegionStart + d->activeSubRegionLength; if (insert(index, topLeft, sheet/*, true*/)) { // if the point was inserted clearSubRegion(); + // Sets: + // d->activeElement = d->activeSubRegionStart + 1; + // d->activeSubRegionLength = 0; + } else { + kWarning() << "Unable to insert" << topLeft << "in" << sheet->sheetName(); } Element* element = cells()[d->activeSubRegionStart]; // we end up with one element in the subregion d->activeSubRegionLength = 1; if (element && element->type() == Element::Point) { Point* point = static_cast(element); point->setColor(d->colors[cells().size() % d->colors.size()]); } else if (element && element->type() == Element::Range) { Range* range = static_cast(element); range->setColor(d->colors[cells().size() % d->colors.size()]); } - d->activeElement = d->activeSubRegionStart; - if (changedRegion == *this) { emitChanged(Region(topLeft, sheet)); return; } changedRegion.add(topLeft, sheet); emitChanged(changedRegion); } void Selection::initialize(const QRect& range, Sheet* sheet) { if (!isValid(range) || (range == QRect(0, 0, 1, 1))) return; if (!d->activeSheet) return; if (d->selectionMode == SingleCell) { initialize(range.bottomRight(), sheet); return; } if (!sheet) { if (d->originSheet) { sheet = d->originSheet; } else { sheet = d->activeSheet; } } Region changedRegion(*this); changedRegion.add(extendToMergedAreas(QRect(d->anchor, d->marker))); // for the case of a merged cell QPoint topLeft(range.topLeft()); Cell cell(d->activeSheet, topLeft); if (cell.isPartOfMerged()) { cell = cell.masterCell(); topLeft = QPoint(cell.column(), cell.row()); } // for the case of a merged cell QPoint bottomRight(range.bottomRight()); cell = Cell(d->activeSheet, bottomRight); if (cell.isPartOfMerged()) { cell = cell.masterCell(); bottomRight = QPoint(cell.column(), cell.row()); } d->anchor = topLeft; d->cursor = bottomRight; d->marker = bottomRight; fixSubRegionDimension(); // TODO remove this sanity check int index = d->activeSubRegionStart + d->activeSubRegionLength; if (insert(index, QRect(topLeft, bottomRight), sheet/*, true*/)) { // if the range was inserted clearSubRegion(); + // Sets: + // d->activeElement = d->activeSubRegionStart + 1; + // d->activeSubRegionLength = 0; + } else { + kWarning() << "Unable to insert" << topLeft << "in" << sheet->sheetName(); } Element* element = cells()[d->activeSubRegionStart]; // we end up with one element in the subregion d->activeSubRegionLength = 1; if (element && element->type() == Element::Point) { Point* point = static_cast(element); point->setColor(d->colors[cells().size() % d->colors.size()]); } else if (element && element->type() == Element::Range) { Range* range = static_cast(element); range->setColor(d->colors[cells().size() % d->colors.size()]); } - d->activeElement = 0; - if (changedRegion == *this) { return; } changedRegion.add(QRect(topLeft, bottomRight), sheet); emitChanged(changedRegion); } void Selection::initialize(const Region& region, Sheet* sheet) { if (!region.isValid()) return; if (d->selectionMode == SingleCell) { if (!cells().isEmpty()) initialize(region.firstRange().bottomRight(), sheet); return; } if (!sheet) { if (d->originSheet) { sheet = d->originSheet; } else { sheet = d->activeSheet; } } Region changedRegion(*this); changedRegion.add(extendToMergedAreas(QRect(d->anchor, d->marker))); // TODO Stefan: handle subregion insertion // TODO Stefan: handle obscured cells correctly - clear(); + Region::clear(); // all elements; no residuum Element* element = add(region); if (element && element->type() == Element::Point) { Point* point = static_cast(element); point->setColor(d->colors[cells().size() % d->colors.size()]); } else if (element && element->type() == Element::Range) { Range* range = static_cast(element); range->setColor(d->colors[cells().size() % d->colors.size()]); } // for the case of a merged cell QPoint topLeft(cells().last()->rect().topLeft()); Cell cell(d->activeSheet, topLeft); if (cell.isPartOfMerged()) { cell = cell.masterCell(); topLeft = QPoint(cell.column(), cell.row()); } // for the case of a merged cell QPoint bottomRight(cells().last()->rect().bottomRight()); cell = Cell(d->activeSheet, bottomRight); if (cell.isPartOfMerged()) { cell = cell.masterCell(); bottomRight = QPoint(cell.column(), cell.row()); } d->anchor = topLeft; d->cursor = topLeft; d->marker = bottomRight; - d->activeElement = cells().count() - 1; + d->activeElement = cells().count(); d->activeSubRegionStart = 0; d->activeSubRegionLength = cells().count(); if (changedRegion == *this) { return; } changedRegion.add(region); emitChanged(changedRegion); } void Selection::update() { emitChanged(*this); } void Selection::update(const QPoint& point) { - uint count = cells().count(); - if (d->selectionMode == SingleCell) { initialize(point); - d->activeElement = 0; + d->activeElement = 1; d->activeSubRegionStart = 0; d->activeSubRegionLength = 1; return; } + // A length of 0 means inserting at the position d->activeSubRegionStart. + if (d->activeSubRegionLength == 0) { + extend(point); + return; + } + if (cells().isEmpty()) { - add(point); - d->activeElement = 0; + initialize(point); + d->activeElement = 1; d->activeSubRegionStart = 0; d->activeSubRegionLength = 1; return; } - if (d->activeElement == cells().count()) { - // we're not empty, so this will not become again end() - d->activeElement--; + + // Take the last range, if pointing beyond the sub-region's end. + const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength; + const bool atEnd = d->activeElement >= subRegionEnd; + if (atEnd) { + // d->activeSubRegionLength == 0 is already excluded. + d->activeElement = subRegionEnd - 1; } Sheet* sheet = cells()[d->activeElement]->sheet(); if (sheet != d->activeSheet) { extend(point); - d->activeElement = cells().count() - 1; + d->activeElement = cells().count(); d->activeSubRegionStart = cells().count() - 1; d->activeSubRegionLength = 1; return; } // for the case of a merged cell QPoint topLeft(point); Cell cell(d->activeSheet, point); if (cell.isPartOfMerged()) { cell = cell.masterCell(); topLeft = QPoint(cell.column(), cell.row()); } if (topLeft == d->marker) { return; } QRect area1 = cells()[d->activeElement]->rect(); QRect newRange = extendToMergedAreas(QRect(d->anchor, topLeft)); + // If the updated range is bigger, it may cover already existing ranges. + // These get removed, if multiple occurences are not allowed. Store the old + // amount of ranges, to figure out how many ranges have been removed later. + const int count = cells().count(); + // The update may have shrunk the range, which would be containend in + // the former range. Remove the latter before inserting the new range. delete cells().takeAt(d->activeElement); - // returns iterator to the new element (before 'it') or 'it' insert(d->activeElement, newRange, sheet, d->multipleOccurences); - d->activeSubRegionLength += cells().count() - count; - - // The call to insert() above can just return the iterator which has been - // passed in. This may be cells.end(), if the old active element was the - // iterator to the list's end (!= last element). So attempts to dereference - // it will fail. - if (d->activeElement == cells().count()) { - d->activeElement--; + const int delta = cells().count() - count; + d->activeSubRegionLength += delta; + if (atEnd) { + d->activeElement = d->activeSubRegionStart + d->activeSubRegionLength; + } else { + d->activeElement += delta; } - QRect area2 = cells()[d->activeElement]->rect(); + QRect area2 = newRange; Region changedRegion; bool newLeft = area1.left() != area2.left(); bool newTop = area1.top() != area2.top(); bool newRight = area1.right() != area2.right(); bool newBottom = area1.bottom() != area2.bottom(); /* first, calculate some numbers that we'll use a few times */ int farLeft = qMin(area1.left(), area2.left()); int innerLeft = qMax(area1.left(), area2.left()); int farTop = qMin(area1.top(), area2.top()); int innerTop = qMax(area1.top(), area2.top()); int farRight = qMax(area1.right(), area2.right()); int innerRight = qMin(area1.right(), area2.right()); int farBottom = qMax(area1.bottom(), area2.bottom()); int innerBottom = qMin(area1.bottom(), area2.bottom()); if (newLeft) { changedRegion.add(QRect(QPoint(farLeft, innerTop), QPoint(innerLeft - 1, innerBottom))); if (newTop) { changedRegion.add(QRect(QPoint(farLeft, farTop), QPoint(innerLeft - 1, innerTop - 1))); } if (newBottom) { changedRegion.add(QRect(QPoint(farLeft, innerBottom + 1), QPoint(innerLeft - 1, farBottom))); } } if (newTop) { changedRegion.add(QRect(QPoint(innerLeft, farTop), QPoint(innerRight, innerTop - 1))); } if (newRight) { changedRegion.add(QRect(QPoint(innerRight + 1, innerTop), QPoint(farRight, innerBottom))); if (newTop) { changedRegion.add(QRect(QPoint(innerRight + 1, farTop), QPoint(farRight, innerTop - 1))); } if (newBottom) { changedRegion.add(QRect(QPoint(innerRight + 1, innerBottom + 1), QPoint(farRight, farBottom))); } } if (newBottom) { changedRegion.add(QRect(QPoint(innerLeft, innerBottom + 1), QPoint(innerRight, farBottom))); } d->marker = topLeft; d->cursor = point; emitChanged(changedRegion); } void Selection::extend(const QPoint& point, Sheet* sheet) { if (!isValid(point)) return; if (isEmpty() || d->selectionMode == SingleCell) { initialize(point, sheet); return; } - if (d->activeElement == cells().count()) { - // we're not empty, so this will not become again end() - d->activeElement--; - } kDebug() ; if (!sheet) { if (d->originSheet) { sheet = d->originSheet; } else { sheet = d->activeSheet; } } Region changedRegion = Region(extendToMergedAreas(QRect(d->marker, d->marker))); // for the case of a merged cell QPoint topLeft(point); Cell cell(d->activeSheet, point); if (cell.isPartOfMerged()) { cell = cell.masterCell(); topLeft = QPoint(cell.column(), cell.row()); } - uint count = cells().count(); if (d->multipleOccurences) { - // always successful - insert(++d->activeElement, point, sheet, false); + const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength; + const bool prepend = d->activeSubRegionLength == 0; + const bool atEnd = d->activeElement == subRegionEnd; + // Insert the new location after the active element, if possible. + const int index = d->activeElement + ((prepend || atEnd) ? 0 : 1); + insert(index, topLeft, sheet, true); + ++d->activeSubRegionLength; + ++d->activeElement; + d->anchor = topLeft; + d->marker = topLeft; } else { + // TODO Replace for normal selection and resizing of any range. + // The new point may split an existing range. Anyway, the new + // location/range is appended and the last element becomes active. + const int count = cells().count(); eor(topLeft, sheet); + d->activeSubRegionLength += cells().count() - count; d->activeElement = cells().count() - 1; + d->anchor = cells()[d->activeElement]->rect().topLeft(); + d->marker = cells()[d->activeElement]->rect().bottomRight(); } - d->anchor = cells()[d->activeElement]->rect().topLeft(); - d->cursor = cells()[d->activeElement]->rect().bottomRight(); - d->marker = d->cursor; - - d->activeSubRegionLength += cells().count() - count; + d->cursor = d->marker; changedRegion.add(topLeft, sheet); changedRegion.add(*this); emitChanged(changedRegion); } void Selection::extend(const QRect& range, Sheet* sheet) { if (!isValid(range) || (range == QRect(0, 0, 1, 1))) return; if (isEmpty() || d->selectionMode == SingleCell) { initialize(range, sheet); return; } - if (d->activeElement == cells().count()) { - // we're not empty, so this will not become again end() - d->activeElement--; - } if (!sheet) { if (d->originSheet) { sheet = d->originSheet; } else { sheet = d->activeSheet; } } // for the case of a merged cell QPoint topLeft(range.topLeft()); Cell cell(d->activeSheet, topLeft); if (cell.isPartOfMerged()) { cell = cell.masterCell(); topLeft = QPoint(cell.column(), cell.row()); } // for the case of a merged cell QPoint bottomRight(range.bottomRight()); cell = Cell(d->activeSheet, bottomRight); if (cell.isPartOfMerged()) { cell = cell.masterCell(); bottomRight = QPoint(cell.column(), cell.row()); } - d->anchor = topLeft; - d->cursor = topLeft; - d->marker = bottomRight; + const QRect newRange = extendToMergedAreas(QRect(topLeft, bottomRight)); - uint count = cells().count(); Element* element = 0; if (d->multipleOccurences) { - //always successful - insert(++d->activeElement, extendToMergedAreas(QRect(topLeft, bottomRight)), sheet, false); + const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength; + const bool prepend = d->activeSubRegionLength == 0; + const bool atEnd = d->activeElement == subRegionEnd; + // Insert the new location after the active element, if possible. + const int index = d->activeElement + ((prepend || atEnd) ? 0 : 1); + insert(index, newRange, sheet, true); + ++d->activeSubRegionLength; + ++d->activeElement; + d->anchor = newRange.topLeft(); + d->marker = newRange.bottomRight(); } else { - element = add(extendToMergedAreas(QRect(topLeft, bottomRight)), sheet); + const int count = cells().count(); + element = add(newRange, sheet); + d->activeSubRegionLength += cells().count() - count; d->activeElement = cells().count() - 1; + d->anchor = cells()[d->activeElement]->rect().topLeft(); + d->marker = cells()[d->activeElement]->rect().bottomRight(); } + d->cursor = d->marker; + if (element && element->type() == Element::Point) { Point* point = static_cast(element); point->setColor(d->colors[cells().size() % d->colors.size()]); } else if (element && element->type() == Element::Range) { Range* range = static_cast(element); range->setColor(d->colors[cells().size() % d->colors.size()]); } - d->activeSubRegionLength += cells().count() - count; - emitChanged(*this); } void Selection::extend(const Region& region) { if (!region.isValid()) return; uint count = cells().count(); ConstIterator end(region.constEnd()); for (ConstIterator it = region.constBegin(); it != end; ++it) { Element *element = *it; if (!element) continue; if (element->type() == Element::Point) { Point* point = static_cast(element); extend(point->pos(), element->sheet()); } else { extend(element->rect(), element->sheet()); } } d->activeSubRegionLength += cells().count() - count; emitChanged(*this); } Selection::Element* Selection::eor(const QPoint& point, Sheet* sheet) { + // The selection always has to contain one location/range at least. if (isSingular()) { return Region::add(point, sheet); } return Region::eor(point, sheet); } const QPoint& Selection::anchor() const { return d->anchor; } const QPoint& Selection::cursor() const { return d->cursor; } const QPoint& Selection::marker() const { return d->marker; } bool Selection::isSingular() const { return Region::isSingular(); } QString Selection::name(Sheet* sheet) const { return Region::name(sheet ? sheet : d->originSheet); } void Selection::setActiveSheet(Sheet* sheet) { if (d->activeSheet == sheet) { return; } d->activeSheet = sheet; emit activeSheetChanged(sheet); } Sheet* Selection::activeSheet() const { return d->activeSheet; } void Selection::setOriginSheet(Sheet* sheet) { d->originSheet = sheet; } Sheet* Selection::originSheet() const { return d->originSheet; } -void Selection::setActiveElement(const QPoint& point, CellEditor* cellEditor) +int Selection::setActiveElement(const Cell &cell) { for (int index = 0; index < cells().count(); ++index) { + if (cells()[index]->sheet() != cell.sheet()) { + continue; + } QRect range = cells()[index]->rect(); + const QPoint point = cell.cellPosition(); if (range.topLeft() == point || range.bottomRight() == point) { d->anchor = range.topLeft(); d->cursor = range.bottomRight(); d->marker = range.bottomRight(); d->activeElement = index; - d->activeSubRegionStart = index; - d->activeSubRegionLength = 1; - if (cellEditor) - cellEditor->setCursorToRange(index); + // Only adjust the sub-region, if index is out of bounds. + if (index < d->activeSubRegionStart) { + d->activeSubRegionStart = index; + } + if (index > d->activeSubRegionStart + d->activeSubRegionLength) { + d->activeSubRegionStart = index; + d->activeSubRegionLength = 1; + } + return index; } } -} - -void Selection::setActiveElement(int pos) -{ - if (pos >= cells().count() || pos < 0) { - kDebug() << "Selection::setActiveElement: position exceeds list"; - d->activeElement = 0; - return; - } - - QRect range = cells()[pos]->rect(); - d->anchor = range.topLeft(); - d->cursor = range.bottomRight(); - d->marker = range.bottomRight(); - d->activeElement = pos; + return -1; } KSpread::Region::Element* Selection::activeElement() const { return (d->activeElement == cells().count()) ? 0 : cells()[d->activeElement]; } void Selection::clear() { d->activeElement = 0; d->activeSubRegionStart = 0; d->activeSubRegionLength = 0; Region::clear(); + // If this is the normal, not the reference mode, one element must survive. + if (!referenceSelection()) { + initialize(QPoint(1, 1), d->activeSheet); + } } void Selection::clearSubRegion() { if (isEmpty()) { return; } for (int index = 0; index < d->activeSubRegionLength; ++index) { delete cells().takeAt(d->activeSubRegionStart); } d->activeSubRegionLength = 0; - d->activeElement = d->activeSubRegionStart; + d->activeElement = d->activeSubRegionStart + 1; // point behind the last } void Selection::fixSubRegionDimension() { if (d->activeSubRegionStart > cells().count()) { - kDebug() << "Selection::fixSubRegionDimension: start position exceeds list"; + kDebug() << "start position" << d->activeSubRegionStart << "exceeds list" << cells().count(); d->activeSubRegionStart = 0; d->activeSubRegionLength = cells().count(); return; } if (d->activeSubRegionStart + d->activeSubRegionLength > cells().count()) { - kDebug() << "Selection::fixSubRegionDimension: length exceeds list"; + kDebug() << "subregion (" << d->activeSubRegionStart << ".." + << d->activeSubRegionStart + d->activeSubRegionLength + << ") exceeds list" << cells().count(); d->activeSubRegionLength = cells().count() - d->activeSubRegionStart; return; } } -void Selection::setActiveSubRegion(uint start, uint length) +void Selection::setActiveSubRegion(int start, int length, int active) { -// kDebug() ; - d->activeElement = start; - d->activeSubRegionStart = start; - d->activeSubRegionLength = length; - fixSubRegionDimension(); + // Set the active sub-region. + d->activeSubRegionStart = qBound(0, start, cells().count()); + d->activeSubRegionLength = qBound(0, length, cells().count() - d->activeSubRegionStart); + + // Set the active element. + d->activeElement = qBound(d->activeSubRegionStart, active, d->activeSubRegionStart + d->activeSubRegionLength); + + if (isEmpty()) { + return; + } + + // Set the anchor, marker and cursor according to the active element. + const int subRegionEnd = d->activeSubRegionStart + d->activeSubRegionLength; + const bool atEnd = d->activeElement == subRegionEnd; + const int index = qBound(0, d->activeElement - (atEnd ? 1 : 0), cells().count() - 1); + const QRect range = cells()[index]->rect(); + d->anchor = range.topLeft(); + d->marker = range.bottomRight(); + d->cursor = d->marker; } QString Selection::activeSubRegionName() const { QStringList names; int end = d->activeSubRegionStart + d->activeSubRegionLength; for (int index = d->activeSubRegionStart; index < end; ++index) { names += cells()[index]->name(d->originSheet); } return names.isEmpty() ? "" : names.join(";"); } void Selection::setSelectionMode(Mode mode) { d->selectionMode = mode; } const QList& Selection::colors() const { return d->colors; } void Selection::selectAll() { if (!isAllSelected()) { d->oldSelection = *this; initialize(QRect(QPoint(KS_colMax, KS_rowMax), QPoint(1, 1))); } else { initialize(d->oldSelection); d->oldSelection.clear(); } } -void Selection::startReferenceSelection(const Region& region) +void Selection::startReferenceSelection() { - // (Tomas) do we really need this? - if (d->referenceMode) { - if (region.isValid()) { - initialize(region); - } - return; - } // former selection exists - we are in ref mode already, even though it's suspended - if (!d->formerSelection.isEmpty()) + if (!d->formerSelection.isEmpty()) { + setReferenceSelectionMode(true); return; - + } d->formerSelection = *this; - clear(); + clear(); // all elements; no residuum; setOriginSheet(activeSheet()); - if (region.isValid()) { - initialize(region); - } // It is important to enable this AFTER we set the rect! d->referenceMode = true; d->multipleOccurences = true; // Visual cue to indicate that the user can drag-select the selection selection d->canvasBase->canvasWidget()->setCursor(Qt::CrossCursor); } void Selection::endReferenceSelection(bool saveChanges) { // The reference selection may be temporarily disabled. // The stored selection reliably indicates the reference selection mode. if (d->formerSelection.isEmpty()) { return; } d->referenceMode = false; d->multipleOccurences = false; if (originSheet() != activeSheet()) { emit visibleSheetRequested(originSheet()); } // While entering a formula the choose mode is turned on and off. // Clear the choice. Otherwise, cell references will stay highlighted. if (!isEmpty()) { emit changed(*this); - clear(); + clear(); // all elements; no residuum } if (saveChanges) { initialize(d->formerSelection); } d->formerSelection.clear(); + // The normal selection does not support the replacments of sub-regions. + // Reset the active sub-region to the whole region. + // TODO Why not allow that? Would make resizing of all ranges in a + // non-contiguous selection possible! + setActiveSubRegion(0, cells().count()); d->canvasBase->canvasWidget()->setCursor(Qt::ArrowCursor); } void Selection::setReferenceSelectionMode(bool enable) { d->referenceMode = enable; d->multipleOccurences = enable; d->canvasBase->canvasWidget()->setCursor(enable ? Qt::CrossCursor : Qt::ArrowCursor); } bool Selection::referenceSelectionMode() const { return d->referenceMode; } bool Selection::referenceSelection() const { return (!d->formerSelection.isEmpty()); } void Selection::emitAboutToModify() { emit aboutToModify(*this); } void Selection::emitModified() { emit modified(*this); } void Selection::emitRefreshSheetViews() { emit refreshSheetViews(); } void Selection::emitVisibleSheetRequested(Sheet* sheet) { emit visibleSheetRequested(sheet); } void Selection::emitCloseEditor(bool saveChanges, bool expandMatrix) { emit closeEditor(saveChanges, expandMatrix); } void Selection::emitRequestFocusEditor() { emit requestFocusEditor(); } QRect Selection::extendToMergedAreas(const QRect& _area) const { if (!d->activeSheet) return _area; QRect area = normalized(_area); Cell cell(d->activeSheet, area.left(), area.top()); if (Region::Range(area).isColumn() || Region::Range(area).isRow()) { return area; } else if (!(cell.isPartOfMerged()) && (cell.mergedXCells() + 1) >= area.width() && (cell.mergedYCells() + 1) >= area.height()) { /* if just a single cell is selected, we need to merge even when the obscuring isn't forced. But only if this is the cell that is doing the obscuring -- we still want to be able to click on a cell that is being obscured. */ area.setWidth(cell.mergedXCells() + 1); area.setHeight(cell.mergedYCells() + 1); } else { int top = area.top(); int left = area.left(); int bottom = area.bottom(); int right = area.right(); for (int x = area.left(); x <= area.right(); x++) for (int y = area.top(); y <= area.bottom(); y++) { cell = Cell(d->activeSheet, x, y); if (cell.doesMergeCells()) { right = qMax(right, cell.mergedXCells() + x); bottom = qMax(bottom, cell.mergedYCells() + y); } else if (cell.isPartOfMerged()) { cell = cell.masterCell(); left = qMin(left, cell.column()); top = qMin(top, cell.row()); bottom = qMax(bottom, cell.row() + cell.mergedYCells()); right = qMax(right, cell.column() + cell.mergedXCells()); } } area.setCoords(left, top, right, bottom); } return area; } KSpread::Region::Point* Selection::createPoint(const QPoint& point) const { return new Point(point); } KSpread::Region::Point* Selection::createPoint(const QString& string) const { return new Point(string); } KSpread::Region::Point* Selection::createPoint(const Region::Point& point) const { return new Point(point); } KSpread::Region::Range* Selection::createRange(const QRect& rect) const { return new Range(rect); } KSpread::Region::Range* Selection::createRange(const KSpread::Region::Point& tl, const KSpread::Region::Point& br) const { return new Range(tl, br); } KSpread::Region::Range* Selection::createRange(const QString& string) const { return new Range(string); } KSpread::Region::Range* Selection::createRange(const Region::Range& range) const { return new Range(range); } void Selection::emitChanged(const Region& region) { Sheet * const sheet = d->activeSheet; if(!sheet) // no sheet no update needed return; Region extendedRegion; ConstIterator end(region.constEnd()); for (ConstIterator it = region.constBegin(); it != end; ++it) { Element* element = *it; QRect area = element->rect(); const ColumnFormat *col; const RowFormat *rl; //look at if column is hiding. //if it's hiding refreshing column+1 (or column -1 ) int left = area.left(); int right = area.right(); int top = area.top(); int bottom = area.bottom(); // a merged cells is selected if (element->type() == Region::Element::Point) { Cell cell(sheet, left, top); if (cell.doesMergeCells()) { // extend to the merged region // prevents artefacts of the selection rectangle right += cell.mergedXCells(); bottom += cell.mergedYCells(); } } if (right < KS_colMax) { do { right++; col = sheet->columnFormat(right); } while (col->isHiddenOrFiltered() && right != KS_colMax); } if (left > 1) { do { left--; col = sheet->columnFormat(left); } while (col->isHiddenOrFiltered() && left != 1); } if (bottom < KS_rowMax) { do { bottom++; rl = sheet->rowFormat(bottom); } while (rl->isHiddenOrFiltered() && bottom != KS_rowMax); } if (top > 1) { do { top--; rl = sheet->rowFormat(top); } while (rl->isHiddenOrFiltered() && top != 1); } area.setLeft(left); area.setRight(right); area.setTop(top); area.setBottom(bottom); extendedRegion.add(area, element->sheet()); } const QList masterCells = sheet->cellStorage()->masterCells(extendedRegion); for (int i = 0; i < masterCells.count(); ++i) extendedRegion.add(masterCells[i].cellPosition(), sheet); emit changed(extendedRegion); } void Selection::dump() const { kDebug() << *this; kDebug() << "d->activeElement:" << d->activeElement; kDebug() << "d->activeSubRegionStart:" << d->activeSubRegionStart; kDebug() << "d->activeSubRegionLength:" << d->activeSubRegionLength; } /*************************************************************************** class Point ****************************************************************************/ Selection::Point::Point(const QPoint& point) : Region::Point(point), m_color(Qt::black) { } Selection::Point::Point(const QString& string) : Region::Point(string), m_color(Qt::black) { } Selection::Point::Point(const Region::Point& point) : Region::Point(point), m_color(Qt::black) { } /*************************************************************************** class Range ****************************************************************************/ Selection::Range::Range(const QRect& range) : Region::Range(range), m_color(Qt::black) { } Selection::Range::Range(const KSpread::Region::Point& tl, const KSpread::Region::Point& br) : Region::Range(tl, br), m_color(Qt::black) { } Selection::Range::Range(const QString& string) : Region::Range(string), m_color(Qt::black) { } Selection::Range::Range(const Region::Range& range) : Region::Range(range), m_color(Qt::black) { } #include "Selection.moc" diff --git a/kspread/Selection.h b/kspread/Selection.h index 2c93506a06..6d1a1a478e 100644 --- a/kspread/Selection.h +++ b/kspread/Selection.h @@ -1,406 +1,418 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2005-2006 Stefan Nikolaus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSPREAD_SELECTION #define KSPREAD_SELECTION #include #include #include #include #include "Region.h" class KoCanvasBase; namespace KSpread { class CellEditor; /** * \class Selection * \brief Manages the selection of cells. * Represents cell selections for general operations and for cell references * used in formulaes. * \author Torben Weis * \author Stefan Nikolaus */ class KSPREAD_EXPORT Selection : public KoToolSelection, public Region { Q_OBJECT public: /** * The selection mode. */ // TODO Stefan: merge with RegionSelector::SelectionMode enum Mode { SingleCell = 0, ///< single cell selection mode MultipleCells = 1 ///< multiple cell selection mode }; /** * Constructor. * Creates a new selection with (1,1) as initial location. * @param canvasBase the canvas interface */ explicit Selection(KoCanvasBase* canvasBase); /** * Copy Constructor. * Creates a copy of @p selection * @param selection the Selection to copy */ Selection(const Selection& selection); /** * Destructor. */ virtual ~Selection(); /** * \return the canvas this selection works for. */ KoCanvasBase* canvas() const; /** * Sets the selection to @p point * @param point the point's location * @param sheet the sheet the point belongs to */ void initialize(const QPoint& point, Sheet* sheet = 0); /** * Sets the selection to @p range * @param range the range's location * @param sheet the sheet the range belongs to */ void initialize(const QRect& range, Sheet* sheet = 0); /** * Sets the selection to @p region * @param region the region's locations * @param sheet the sheet the region belongs to */ void initialize(const Region& region, Sheet* sheet = 0); /** * Emits signal changed(const Region&) */ void update(); /** * Update the marker of the selection to @p point . * Uses the anchor as starting point * @p point the new marker location */ void update(const QPoint& point); /** * Extends the current selection with the Point @p point * @param point the point's location * @param sheet the sheet the point belongs to */ void extend(const QPoint& point, Sheet* sheet = 0); /** * Extends the current selection with the Range @p range * @param range the range's location * @param sheet the sheet the range belongs to */ void extend(const QRect& range, Sheet* sheet = 0); /** * Extends the current selection with the Region @p region * @param region the region's locations */ void extend(const Region& region); /** * @param point the point's location * @param sheet the sheet the point belongs to */ virtual Element* eor(const QPoint& point, Sheet* sheet = 0); /** * The anchor is the starting point of a range. For points marker and anchor are the same */ const QPoint& anchor() const; /** * The cursor represents the cursor position. This is needed for merged cells */ const QPoint& cursor() const; /** * The marker is the end point of a range. For points marker and anchor are the same */ const QPoint& marker() const; /** * Checks whether the region consists only of one point */ bool isSingular() const; /** * @return the name of the region (e.g. "A1:A2") */ QString name(Sheet* originSheet = 0) const; /** * Sets the selection's active sheet. * For usual selections this is always the origin sheet, * but for cell choices used for formulaes it may differ. * @param sheet the sheet which is currently active */ void setActiveSheet(Sheet* sheet); /** * @return the selection's active sheet */ Sheet* activeSheet() const; /** * Sets the selection's origin sheet. * @param sheet the sheet from which the selection starts */ void setOriginSheet(Sheet* sheet); /** * @return the selection's origin sheet */ Sheet* originSheet() const; /** - * Sets the element, which has @p point as anchor, as active + * Activates the cell location/range, that has \p cell as bottom left or + * right cell. If more than one occurence would fit, the first one is taken. + * \return the index of the activated range or \c -1, if nothing fits */ - void setActiveElement(const QPoint& point, CellEditor* cellEditor); + int setActiveElement(const Cell &cell); /** - * Sets the @p number 'th element as active + * Sets the element, which has @p point as anchor, as active */ - void setActiveElement(int number); + void setActiveElement( const QPoint& point, CellEditor* cellEditor ); /** * @return the active element */ Element* activeElement() const; /** - * Sets the starting position and the length of a subregion in a selection - * which allows multiple occurrences of elements. + * Sets the starting position and the length of a sub-region. + * On inserting/updating the selection the sub-region gets replaced + * by the new cell location/range. + * A \p length of \c 0 results in no replacement, but just in inserting the + * new cell location/range before the range index \p start. + * \param start The index of a range in this selection. It has to be a valid + * index; otherwise the sub-region will be set to the whole region. + * \param length The amount of ranges in the sub-region. If it exceeds the + * amount of ranges, beginning from \p start to the end of range list, it + * will be adjusted. + * \param active The active element within the sub-region. + * \verbatim start <= active <= start + length \endverbatim */ - void setActiveSubRegion(uint start, uint length); + void setActiveSubRegion(int start, int length, int active = -1); /** * */ QString activeSubRegionName() const; /** * Clears the elements of the subregion */ void clearSubRegion(); /** * fix subregion dimensions */ void fixSubRegionDimension(); /** * Deletes all elements of the region. The result is an empty region. */ virtual void clear(); /** * \param mode single cell or multiple cell selection */ void setSelectionMode(Mode mode); /** * Extends \p area to include the merged cells, that are not fully covered, * completely. * \return the extended area */ QRect extendToMergedAreas(const QRect& area) const; const QList& colors() const; void selectAll(); /** Start using a reference selection instead of normal one. */ - void startReferenceSelection(const Region& region = Region()); + void startReferenceSelection(); /** End using reference selection. */ void endReferenceSelection(bool saveChanges = true); /** Enable/disable reference choosing mode. */ void setReferenceSelectionMode(bool enable); /** Are we in reference choosing mode ? */ bool referenceSelectionMode() const; /** Are we currently using a reference selection ? */ bool referenceSelection() const; void emitAboutToModify(); void emitModified(); void emitRefreshSheetViews(); void emitVisibleSheetRequested(Sheet* sheet); void emitCloseEditor(bool saveChanges, bool expandMatrix = false); void emitRequestFocusEditor(); signals: /** * Emitted when the Selection was changed. * @param region the changed part of the Selection */ void changed(const Region& region); /** * An operation on the selection is about to happen. */ void aboutToModify(const Region& region); /** * Emitted when the content was modified. */ void modified(const Region& region); void refreshSheetViews(); void visibleSheetRequested(Sheet* sheet); void closeEditor(bool saveChanges, bool expandMatrix); void activeSheetChanged(Sheet* sheet); void requestFocusEditor(); void documentReadWriteToggled(bool readWrite); void sheetProtectionToggled(bool protect); protected: class Point; class Range; /** * @internal used to create derived Points */ virtual Region::Point* createPoint(const QPoint&) const; /** * @internal used to create derived Points */ virtual Region::Point* createPoint(const QString&) const; /** * @internal used to create derived Points */ virtual Region::Point* createPoint(const Region::Point&) const; /** * @internal used to create derived Ranges */ virtual Region::Range* createRange(const QRect&) const; /** * @internal used to create derived Ranges */ virtual Region::Range* createRange(const Region::Point&, const Region::Point&) const; /** * @internal used to create derived Ranges */ virtual Region::Range* createRange(const QString&) const; /** * @internal used to create derived Ranges */ virtual Region::Range* createRange(const Region::Range&) const; /** * Dilates the region and emits the changed() signal. * \internal */ void emitChanged(const Region& changedRegion); /** * @internal */ void dump() const; private: // do not allow assignment Selection& operator=(const Selection&); class Private; Private * const d; }; /*************************************************************************** class Selection::Point ****************************************************************************/ /** * This Point is extended by an color attribute. */ class Selection::Point : public Region::Point { public: Point(const QPoint& point); Point(const QString& string); Point(const Region::Point& point); void setColor(const QColor& color) { m_color = color; } virtual const QColor& color() const { return m_color; } private: QColor m_color; }; /*************************************************************************** class Selection::Range ****************************************************************************/ /** * This Range is extended by an color attribute. */ class Selection::Range : public Region::Range { public: Range(const QRect& rect); Range(const KSpread::Region::Point& tl, const KSpread::Region::Point& br); Range(const QString& string); Range(const Region::Range& range); void setColor(const QColor& color) { m_color = color; } const QColor& color() const { return m_color; } private: QColor m_color; }; } // namespace KSpread #endif // KSPREAD_SELECTION diff --git a/kspread/dialogs/FormulaDialog.cpp b/kspread/dialogs/FormulaDialog.cpp index d42c4b83dc..669950cc61 100644 --- a/kspread/dialogs/FormulaDialog.cpp +++ b/kspread/dialogs/FormulaDialog.cpp @@ -1,766 +1,766 @@ /* This file is part of the KDE project Copyright (C) 2002-2003 Ariya Hidayat (C) 2002-2003 Norbert Andres (C) 1999-2003 Laurent Montel (C) 2002 Philipp Mueller (C) 2002 John Dailey (C) 2002 Daniel Herring (C) 2000-2001 Werner Trobin (C) 1998-2000 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local #include "FormulaDialog.h" #include #include #include #include #include #include "CalculationSettings.h" #include "Cell.h" #include "Util.h" #include "ui/Editors.h" #include "Localization.h" #include "Map.h" #include "Selection.h" #include "Sheet.h" #include "Functions.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KSpread; FormulaDialog::FormulaDialog(QWidget* parent, Selection* selection, CellEditor* editor, const QString& formulaName) : KDialog(parent) { setCaption(i18n("Function")); setButtons(Ok | Cancel); //setWFlags( Qt::WDestructiveClose ); m_selection = selection; m_editor = editor; m_focus = 0; m_desc = 0; Cell cell(m_selection->activeSheet(), m_selection->marker()); m_oldText = cell.userInput(); // Make sure that there is a cell editor running. if (cell.userInput().isEmpty()) m_editor->setText("="); else if (cell.userInput().at(0) != '=') m_editor->setText('=' + cell.userInput()); else m_editor->setText(cell.userInput()); QWidget *page = new QWidget(this); setMainWidget(page); QGridLayout *grid1 = new QGridLayout(page); grid1->setMargin(KDialog::marginHint()); grid1->setSpacing(KDialog::spacingHint()); searchFunct = new KLineEdit(page); searchFunct->setClearButtonShown(true); searchFunct->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); grid1->addWidget(searchFunct, 0, 0); typeFunction = new KComboBox(page); QStringList cats = FunctionRepository::self()->groups(); cats.prepend(i18n("All")); typeFunction->setMaxVisibleItems(15); typeFunction->insertItems(0, cats); grid1->addWidget(typeFunction, 1, 0); functions = new QListView(page); functions->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); functions->setSelectionMode(QAbstractItemView::SingleSelection); functions->setEditTriggers(QAbstractItemView::NoEditTriggers); grid1->addWidget(functions, 2, 0); functionsModel = new QStringListModel(this); proxyModel = new QSortFilterProxyModel(functions); proxyModel->setSourceModel(functionsModel); proxyModel->setFilterKeyColumn(0); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); functions->setModel(proxyModel); QItemSelectionModel* selectionmodel = new QItemSelectionModel(proxyModel, this); functions->setSelectionModel(selectionmodel); connect(selectionmodel, SIGNAL(currentRowChanged(QModelIndex, QModelIndex)), this, SLOT(slotSelected())); // When items are activated on single click, also change the help page on mouse-over, otherwise there is no (easy) way to get // the help without inserting the function if (functions->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, functions)) { connect(functions, SIGNAL(entered(QModelIndex)), this, SLOT(slotIndexSelected(QModelIndex))); functions->setMouseTracking(true); } //connect(proxyModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(slotDataChanged(QModelIndex,QModelIndex))); selectFunction = new QPushButton(page); selectFunction->setToolTip(i18n("Insert function")); selectFunction->setIcon(BarIcon("go-down", KIconLoader::SizeSmall)); grid1->addWidget(selectFunction, 3, 0); result = new KLineEdit(page); grid1->addWidget(result, 4, 0, 1, -1); m_tabwidget = new KTabWidget(page); m_tabwidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); grid1->addWidget(m_tabwidget, 0, 1, 4, 1); m_browser = new KTextBrowser(m_tabwidget, true); m_browser->document()->setDefaultStyleSheet("h1 { font-size:x-large; } h2 { font-size:large; } h3 { font-size:medium; }"); m_browser->setMinimumWidth(300); m_tabwidget->addTab(m_browser, i18n("Help")); int index = m_tabwidget->currentIndex(); m_input = new QWidget(m_tabwidget); QVBoxLayout *grid2 = new QVBoxLayout(m_input); grid2->setMargin(KDialog::marginHint()); grid2->setSpacing(KDialog::spacingHint()); // grid2->setResizeMode (QLayout::Minimum); label1 = new QLabel(m_input); grid2->addWidget(label1); firstElement = new KLineEdit(m_input); grid2->addWidget(firstElement); label2 = new QLabel(m_input); grid2->addWidget(label2); secondElement = new KLineEdit(m_input); grid2->addWidget(secondElement); label3 = new QLabel(m_input); grid2->addWidget(label3); thirdElement = new KLineEdit(m_input); grid2->addWidget(thirdElement); label4 = new QLabel(m_input); grid2->addWidget(label4); fourElement = new KLineEdit(m_input); grid2->addWidget(fourElement); label5 = new QLabel(m_input); grid2->addWidget(label5); fiveElement = new KLineEdit(m_input); grid2->addWidget(fiveElement); grid2->addStretch(10); m_tabwidget->addTab(m_input, i18n("Parameters")); m_tabwidget->setTabEnabled(m_tabwidget->indexOf(m_input), false); m_tabwidget->setCurrentIndex(index); refresh_result = true; connect(this, SIGNAL(cancelClicked()), this, SLOT(slotClose())); connect(this, SIGNAL(okClicked()), this, SLOT(slotOk())); connect(typeFunction, SIGNAL(activated(const QString &)), this, SLOT(slotActivated(const QString &))); /* connect( functions, SIGNAL( highlighted(const QString &) ), this, SLOT( slotSelected(const QString &) ) ); connect( functions, SIGNAL( selected(const QString &) ), this, SLOT( slotSelected(const QString &) ) ); */ connect(functions, SIGNAL(activated(QModelIndex)), this , SLOT(slotDoubleClicked(QModelIndex))); slotActivated(i18n("All")); connect(selectFunction, SIGNAL(clicked()), this, SLOT(slotSelectButton())); connect(firstElement, SIGNAL(textChanged(const QString &)), this, SLOT(slotChangeText(const QString &))); connect(secondElement, SIGNAL(textChanged(const QString &)), this, SLOT(slotChangeText(const QString &))); connect(thirdElement, SIGNAL(textChanged(const QString &)), this, SLOT(slotChangeText(const QString &))); connect(fourElement, SIGNAL(textChanged(const QString &)), this, SLOT(slotChangeText(const QString &))); connect(fiveElement, SIGNAL(textChanged(const QString &)), this, SLOT(slotChangeText(const QString &))); connect(m_selection, SIGNAL(changed(const Region&)), this, SLOT(slotSelectionChanged())); connect(m_browser, SIGNAL(urlClick(const QString&)), this, SLOT(slotShowFunction(const QString&))); // Save the name of the active sheet. m_sheetName = m_selection->activeSheet()->sheetName(); // Save the cells current text. - QString tmp_oldText = m_editor->text(); + QString tmp_oldText = m_editor->toPlainText(); // Position of the cell. m_column = m_selection->marker().x(); m_row = m_selection->marker().y(); if (tmp_oldText.isEmpty()) result->setText("="); else { if (tmp_oldText.at(0) != '=') result->setText('=' + tmp_oldText); else result->setText(tmp_oldText); } // Allow the user to select cells on the spreadsheet. m_selection->startReferenceSelection(); qApp->installEventFilter(this); // Was a function name passed along with the constructor ? Then activate it. if (!formulaName.isEmpty()) { kDebug() << "formulaName=" << formulaName; #if 0 QList items = functions->findItems(formulaName, Qt::MatchFixedString); if (items.count() > 0) { functions->setCurrentItem(items[0]); slotDoubleClicked(items[0]); } #else int row = functionsModel->stringList().indexOf(formulaName); const QModelIndex sourcemodelindex = functionsModel->index(row, 0); const QModelIndex proxymodelindex = proxyModel->mapFromSource(sourcemodelindex); if (proxymodelindex.isValid()) { functions->setCurrentIndex(proxymodelindex); slotDoubleClicked(proxymodelindex); } #endif } else { // Set keyboard focus to allow selection of a formula. searchFunct->setFocus(); } // Add auto completion. searchFunct->setCompletionMode(KGlobalSettings::CompletionAuto); searchFunct->setCompletionObject(&listFunct, true); if (functions->currentIndex().isValid()) selectFunction->setEnabled(false); connect(searchFunct, SIGNAL(textChanged(const QString &)), this, SLOT(slotSearchText(const QString &))); connect(searchFunct, SIGNAL(returnPressed()), this, SLOT(slotPressReturn())); resize(QSize(660, 520).expandedTo(minimumSizeHint())); } FormulaDialog::~FormulaDialog() { kDebug(36001) << "FormulaDialog::~FormulaDialog()"; } void FormulaDialog::slotPressReturn() { //laurent 2001-07-07 desactivate this code //because kspread crash. //TODO fix it /* if( !functions->currentText().isEmpty() ) slotDoubleClicked( functions->findItem( functions->currentText() ) ); */ } void FormulaDialog::slotSearchText(const QString &_text) { proxyModel->setFilterFixedString(_text); if (functions->currentIndex().isValid()) functions->scrollTo(functions->currentIndex()); } bool FormulaDialog::eventFilter(QObject* obj, QEvent* ev) { if (obj == firstElement && ev->type() == QEvent::FocusIn) m_focus = firstElement; else if (obj == secondElement && ev->type() == QEvent::FocusIn) m_focus = secondElement; else if (obj == thirdElement && ev->type() == QEvent::FocusIn) m_focus = thirdElement; else if (obj == fourElement && ev->type() == QEvent::FocusIn) m_focus = fourElement; else if (obj == fiveElement && ev->type() == QEvent::FocusIn) m_focus = fiveElement; else return false; if (m_focus) m_selection->startReferenceSelection(); return false; } void FormulaDialog::slotOk() { // Pretend none of the text edits have focus; otherwise the next line will change the // value of whatever parameter has focus to the name of the cell we're editing m_focus = 0; m_selection->endReferenceSelection(); // Revert the marker to its original position m_selection->initialize(QPoint(m_column, m_row)); // If there is still an editor then set the text. // Usually the editor is always in place. if (m_editor != 0) { Q_ASSERT(m_editor); QString tmp = result->text(); if (tmp.at(0) != '=') tmp = '=' + tmp; int pos = m_editor->cursorPosition() + tmp.length(); m_editor->setText(tmp); m_editor->setFocus(); m_editor->setCursorPosition(pos); } m_selection->emitModified(); accept(); deleteLater(); } void FormulaDialog::slotClose() { deleteLater(); m_selection->endReferenceSelection(); // Revert the marker to its original position m_selection->initialize(QPoint(m_column, m_row)); // If there is still an editor then reset the text. // Usually the editor is always in place. if (m_editor != 0) { Q_ASSERT(m_editor); m_editor->setText(m_oldText); m_editor->setFocus(); } m_selection->emitModified(); reject(); } void FormulaDialog::slotSelectButton() { if (functions->currentIndex().isValid()) { //slotDoubleClicked(functions->findItem(functions->text(functions->currentItem()))); slotDoubleClicked(functions->currentIndex()); } } void FormulaDialog::slotChangeText(const QString&) { // Test the lock if (!refresh_result) return; if (m_focus == 0) return; QString tmp = m_leftText + m_funcName + '('; tmp += createFormula(); tmp = tmp + ')' + m_rightText; result->setText(tmp); } QString FormulaDialog::createFormula() { QString tmp(""); if (!m_desc) return QString(); bool first = true; int count = m_desc->params(); if (!firstElement->text().isEmpty() && count >= 1) { tmp = tmp + createParameter(firstElement->text(), 0); first = false; } if (!secondElement->text().isEmpty() && count >= 2) { first = false; if (!first) tmp = tmp + ';' + createParameter(secondElement->text(), 1); else tmp = tmp + createParameter(secondElement->text(), 1); } if (!thirdElement->text().isEmpty() && count >= 3) { first = false; if (!first) tmp = tmp + ';' + createParameter(thirdElement->text(), 2); else tmp = tmp + createParameter(thirdElement->text(), 2); } if (!fourElement->text().isEmpty() && count >= 4) { first = false; if (!first) tmp = tmp + ';' + createParameter(fourElement->text(), 3); else tmp = tmp + createParameter(fourElement->text(), 3); } if (!fiveElement->text().isEmpty() && count >= 5) { first = false; if (!first) tmp = tmp + ';' + createParameter(fiveElement->text(), 4); else tmp = tmp + createParameter(fiveElement->text(), 4); } return(tmp); } QString FormulaDialog::createParameter(const QString& _text, int param) { if (_text.isEmpty()) return QString(""); if (!m_desc) return QString(""); QString text; ParameterType elementType = m_desc->param(param).type(); switch (elementType) { case KSpread_Any: { bool isNumber; double tmp = m_selection->activeSheet()->map()->calculationSettings()->locale()->readNumber(_text, &isNumber); Q_UNUSED(tmp); //In case of number or boolean return _text, else return value as KSpread_String if (isNumber || _text.toUpper() == "FALSE" || _text.toUpper() == "TRUE") return _text; } // fall through case KSpread_String: { // Does the text start with quotes? if (_text[0] == '"') { text = '\\'; // changed: was '"' // Escape quotes QString tmp = _text; int pos; int start = 1; while ((pos = tmp.indexOf('"', start)) != -1) { if (tmp[pos - 1] != '\\') tmp.replace(pos, 1, "\\\""); else start = pos + 1; } text += tmp; text += '"'; } else { const Region region(_text, m_selection->activeSheet()->map()); if (!region.isValid()) { text = '"'; // Escape quotes QString tmp = _text; int pos; int start = 1; while ((pos = tmp.indexOf('"', start)) != -1) { if (tmp[pos - 1] != '\\') tmp.replace(pos, 1, "\\\""); else start = pos + 1; } text += tmp; text += '"'; } else text = _text; } } return text; case KSpread_Float: return _text; case KSpread_Boolean: return _text; case KSpread_Int: return _text; } // Never reached return text; } static void showEntry(KLineEdit* edit, QLabel* label, FunctionDescription* desc, int param) { edit->show(); label->setText(desc->param(param).helpText() + ':'); label->show(); ParameterType elementType = desc->param(param).type(); KFloatValidator *validate = 0; switch (elementType) { case KSpread_String: case KSpread_Boolean: case KSpread_Any: edit->setValidator(0); break; case KSpread_Float: validate = new KFloatValidator(edit); validate->setAcceptLocalizedNumbers(true); edit->setValidator(validate); edit->setText("0"); break; case KSpread_Int: edit->setValidator(new QIntValidator(edit)); edit->setText("0"); break; } } void FormulaDialog::slotDoubleClicked(QModelIndex item) { if (!item.isValid()) { item = functions->currentIndex(); if (!item.isValid()) return; } refresh_result = false; if (!m_desc) { m_browser->setText(""); return; } m_focus = 0; int old_length = result->text().length(); // Do not change order of these function calls due to a bug in Qt 2.2 m_browser->setText(m_desc->toQML()); m_tabwidget->setTabEnabled(m_tabwidget->indexOf(m_input), true); m_tabwidget->setCurrentIndex(1); // // Show as many KLineEdits as needed. // if (m_desc->params() > 0) { m_focus = firstElement; firstElement->setFocus(); showEntry(firstElement, label1, m_desc, 0); } else { label1->hide(); firstElement->hide(); } if (m_desc->params() > 1) { showEntry(secondElement, label2, m_desc, 1); } else { label2->hide(); secondElement->hide(); } if (m_desc->params() > 2) { showEntry(thirdElement, label3, m_desc, 2); } else { label3->hide(); thirdElement->hide(); } if (m_desc->params() > 3) { showEntry(fourElement, label4, m_desc, 3); } else { label4->hide(); fourElement->hide(); } if (m_desc->params() > 4) { showEntry(fiveElement, label5, m_desc, 4); } else { label5->hide(); fiveElement->hide(); } if (m_desc->params() > 5) kDebug(36001) << "Error in param->nb_param"; refresh_result = true; // // Put the new function call in the result. // if (result->cursorPosition() < old_length) { m_rightText = result->text().right(old_length - result->cursorPosition()); m_leftText = result->text().left(result->cursorPosition()); } else { m_rightText = ""; m_leftText = result->text(); } int pos = result->cursorPosition(); { const QString text = proxyModel->data(functions->currentIndex()).toString(); result->setText(m_leftText + text + "()" + m_rightText); if (result->text()[0] != '=') result->setText('=' + result->text()); } // // Put focus somewhere is there are no KLineEdits visible // if (m_desc->params() == 0) { label1->show(); label1->setText(i18n("This function has no parameters.")); result->setFocus(); const QString text = proxyModel->data(functions->currentIndex()).toString(); result->setCursorPosition(pos + text.length() + 2); } slotChangeText(""); } void FormulaDialog::slotIndexSelected(const QModelIndex& index) { // This slot is only called when single-click to activate is uesd in the listbox, when the mouse moves over a item; to prevent // the active selection to change after the user activated one, slotSelected is only called when the current tab is the Help tab, // and not when the parameters tab is active if (m_tabwidget->currentIndex() != 0) return; QString function = proxyModel->data(index).toString(); slotSelected(function); } void FormulaDialog::slotSelected(const QString& afunction) { QString function = afunction; if (function.isNull()) function = proxyModel->data(functions->currentIndex()).toString(); FunctionDescription* desc = FunctionRepository::self()->functionInfo(function); if (!desc) { m_browser->setText(i18n("Description is not available.")); return; } if (functions->currentIndex().isValid()) selectFunction->setEnabled(true); // Lock refresh_result = false; m_funcName = function; m_desc = desc; // Set the help text m_browser->setText(m_desc->toQML()); //m_browser->setContentsPos( 0, 0 ); m_focus = 0; m_tabwidget->setCurrentIndex(0); m_tabwidget->setTabEnabled(m_tabwidget->indexOf(m_input), false); // Unlock refresh_result = true; } // from hyperlink in the "Related Function" void FormulaDialog::slotShowFunction(const QString& function) { FunctionDescription* desc = FunctionRepository::self()->functionInfo(function); if (!desc) return; // select the category QString category = desc->group(); typeFunction->setCurrentIndex(typeFunction->findText(category)); slotActivated(category); // select the function //Q3ListBoxItem* item = functions->findItem( function, QKeySequence::ExactMatch | Qt::CaseSensitive ); //if( item ) functions->setCurrentItem( item ); int row = functionsModel->stringList().indexOf(function); const QModelIndex sourcemodelindex = functionsModel->index(row, 0); const QModelIndex proxymodelindex = proxyModel->mapFromSource(sourcemodelindex); if (proxymodelindex.isValid()) functions->setCurrentIndex(proxymodelindex); slotSelected(function); } void FormulaDialog::slotSelectionChanged() { if (!m_focus) return; if (m_selection->isValid()) { QString area = m_selection->name(); m_focus->setText(area); } } void FormulaDialog::slotActivated(const QString& category) { QStringList lst; if (category == i18n("All")) lst = FunctionRepository::self()->functionNames(); else lst = FunctionRepository::self()->functionNames(category); kDebug(36001) << "category:" << category << " (" << lst.count() << "functions)"; functionsModel->setStringList(lst); QStringList upperList; for (QStringList::Iterator it = lst.begin(); it != lst.end();++it) upperList.append((*it).toUpper()); listFunct.setItems(upperList); // Go to the first function in the list. const QString text = proxyModel->data(proxyModel->index(0, 0)).toString(); slotSelected(text); } void FormulaDialog::closeEvent(QCloseEvent * e) { deleteLater(); e->accept(); } #include "FormulaDialog.moc" diff --git a/kspread/part/Canvas.cpp b/kspread/part/Canvas.cpp index 65487e3bf4..8d74e11f91 100644 --- a/kspread/part/Canvas.cpp +++ b/kspread/part/Canvas.cpp @@ -1,856 +1,862 @@ /* This file is part of the KDE project Copyright 2009 Thomas Zander Copyright 2006-2007 Stefan Nikolaus Copyright 2006 Robert Knight Copyright 2006 Inge Wallin Copyright 1999-2002,2004 Laurent Montel Copyright 2002-2005 Ariya Hidayat Copyright 1999-2004 David Faure Copyright 2004-2005 Meni Livne Copyright 2001-2003 Philipp Mueller Copyright 2002-2003 Norbert Andres Copyright 2003 Hamish Rodda Copyright 2003 Joseph Wenninger Copyright 2003 Lukas Tinkl Copyright 2000-2002 Werner Trobin Copyright 2002 Harri Porten Copyright 2002 John Dailey Copyright 2002 Daniel Naber Copyright 1999-2000 Torben Weis Copyright 1999-2000 Stephan Kulow Copyright 2000 Bernd Wuebben Copyright 2000 Wilco Greven Copyright 2000 Simon Hausmann Copyright 1999 Boris Wedl Copyright 1999 Reginald Stadlbauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Local #include "Canvas.h" #include "Canvas_p.h" // std #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include // KOffice #include #include #include #include #include #include #include // KSpread #include "CellStorage.h" #include "Doc.h" #include "Global.h" #include "Headers.h" #include "Localization.h" #include "Map.h" #include "RowColumnFormat.h" #include "Selection.h" #include "Sheet.h" #include "Util.h" #include "Validity.h" #include "View.h" // commands #include "commands/CopyCommand.h" #include "commands/DeleteCommand.h" #include "commands/PasteCommand.h" #include "commands/StyleCommand.h" #include "commands/Undo.h" // ui #include "ui/CellView.h" #include "ui/Editors.h" #include "ui/SheetView.h" #define MIN_SIZE 10 using namespace KSpread; /**************************************************************** * * Canvas * ****************************************************************/ Canvas::Canvas(View *view) : QWidget(view) , KoCanvasBase(0) , d(new Private) { setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_StaticContents); setBackgroundRole(QPalette::Base); d->validationInfo = 0; QWidget::setFocusPolicy(Qt::StrongFocus); d->offset = QPointF(0.0, 0.0); d->view = view; setMouseTracking(true); installEventFilter(this); // for TAB key processing, otherwise focus change setAcceptDrops(true); setAttribute(Qt::WA_InputMethodEnabled, true); // ensure using the InputMethod // flake d->shapeManager = new KoShapeManager(this); d->toolProxy = new KoToolProxy(this); } Canvas::~Canvas() { delete d->shapeManager; delete d->toolProxy; delete d->validationInfo; delete d; } View* Canvas::view() const { return d->view; } Doc* Canvas::doc() const { return d->view->doc(); } void Canvas::gridSize(qreal* horizontal, qreal* vertical) const { *horizontal = doc()->map()->defaultColumnFormat()->width(); *vertical = doc()->map()->defaultRowFormat()->height(); } bool Canvas::snapToGrid() const { return false; // FIXME } void Canvas::addCommand(QUndoCommand* command) { doc()->addCommand(command); } KoShapeManager* Canvas::shapeManager() const { return d->shapeManager; } void Canvas::updateCanvas(const QRectF& rc) { QRect clipRect(viewConverter()->documentToView(rc.translated(-offset())).toRect()); clipRect.adjust(-2, -2, 2, 2); // Resize to fit anti-aliasing update(clipRect); } const KoViewConverter* Canvas::viewConverter() const { return view()->zoomHandler(); } KoUnit Canvas::unit() const { return doc()->unit(); } KoToolProxy* Canvas::toolProxy() const { return d->toolProxy; } QPointF Canvas::offset() const { return d->offset; } double Canvas::xOffset() const { return d->offset.x(); } double Canvas::yOffset() const { return d->offset.y(); } bool Canvas::eventFilter(QObject *o, QEvent *e) { /* this canvas event filter acts on events sent to the line edit as well as events to this filter itself. */ if (!o || !e) return true; switch (e->type()) { case QEvent::KeyPress: { QKeyEvent * keyev = static_cast(e); if ((keyev->key() == Qt::Key_Tab) || (keyev->key() == Qt::Key_Backtab)) { keyPressEvent(keyev); return true; } break; } case QEvent::InputMethod: { //QIMEvent * imev = static_cast(e); //processIMEvent( imev ); //break; } case QEvent::ToolTip: { QHelpEvent* helpEvent = static_cast(e); showToolTip(helpEvent->pos()); } default: break; } return false; } Selection* Canvas::selection() const { return d->view->selection(); } ColumnHeader* Canvas::columnHeader() const { return d->view->columnHeader(); } RowHeader* Canvas::rowHeader() const { return d->view->rowHeader(); } QScrollBar* Canvas::horzScrollBar() const { return d->view->horzScrollBar(); } QScrollBar* Canvas::vertScrollBar() const { return d->view->vertScrollBar(); } Sheet* Canvas::activeSheet() const { return d->view->activeSheet(); } void Canvas::validateSelection() { register Sheet * const sheet = activeSheet(); if (!sheet) return; if (selection()->isSingular()) { const Cell cell = Cell(sheet, selection()->marker()).masterCell(); Validity validity = cell.validity(); if (validity.displayValidationInformation()) { const QString title = validity.titleInfo(); QString message = validity.messageInfo(); if (title.isEmpty() && message.isEmpty()) return; if (!d->validationInfo) { d->validationInfo = new QLabel(this); QPalette palette = d->validationInfo->palette(); palette.setBrush(QPalette::Window, palette.toolTipBase()); palette.setBrush(QPalette::WindowText, palette.toolTipText()); d->validationInfo->setPalette(palette); // d->validationInfo->setWindowFlags(Qt::ToolTip); d->validationInfo->setFrameShape(QFrame::Box); d->validationInfo->setAlignment(Qt::AlignVCenter); d->validationInfo->setTextFormat(Qt::RichText); } QString resultText(""); if (!title.isEmpty()) { resultText += "

" + title + "

"; } if (!message.isEmpty()) { message.replace(QChar('\n'), QString("
")); resultText += "

" + message + "

"; } resultText += ""; d->validationInfo->setText(resultText); const double xpos = sheet->columnPosition(cell.column()) + cell.width(); const double ypos = sheet->rowPosition(cell.row()) + cell.height(); const QPointF position = QPointF(xpos, ypos) - offset(); const QPoint viewPosition = viewConverter()->documentToView(position).toPoint(); d->validationInfo->move(/*mapToGlobal*/(viewPosition)); // Qt::ToolTip! d->validationInfo->show(); } else { delete d->validationInfo; d->validationInfo = 0; } } else { delete d->validationInfo; d->validationInfo = 0; } } void Canvas::setDocumentOffset(const QPoint& offset) { const QPoint delta = offset - viewConverter()->documentToView(d->offset).toPoint(); d->offset = viewConverter()->viewToDocument(offset); columnHeader()->scroll(delta.x(), 0); rowHeader()->scroll(0, delta.y()); } void Canvas::setDocumentSize(const QSizeF& size) { const QSize s = viewConverter()->documentToView(size).toSize(); emit documentSizeChanged(s); } void Canvas::mousePressEvent(QMouseEvent* event) { QMouseEvent *const origEvent = event; QPointF documentPosition; if (layoutDirection() == Qt::LeftToRight) { documentPosition = viewConverter()->viewToDocument(event->pos()) + offset(); } else { const QPoint position(width() - event->x(), event->y()); const QPointF offset(this->offset().x(), this->offset().y()); documentPosition = viewConverter()->viewToDocument(position) + offset; kDebug() << "----------------------------"; kDebug() << "event->pos():" << event->pos(); kDebug() << "event->globalPos():" << event->globalPos(); kDebug() << "position:" << position; kDebug() << "offset:" << offset; kDebug() << "documentPosition:" << documentPosition; event = new QMouseEvent(QEvent::MouseButtonPress, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers()); kDebug() << "newEvent->pos():" << event->pos(); kDebug() << "newEvent->globalPos():" << event->globalPos(); } // flake if(d->toolProxy) { d->toolProxy->mousePressEvent(event, documentPosition); if (!event->isAccepted() && event->button() == Qt::RightButton) { d->view->unplugActionList("toolproxy_action_list"); d->view->plugActionList("toolproxy_action_list", toolProxy()->popupActionList()); QMenu* menu = dynamic_cast(d->view->factory()->container("default_canvas_popup", d->view)); // Only show the menu, if there are items. The plugged action list counts as one action. if (menu && menu->actions().count() > 1) { menu->exec(origEvent->globalPos()); } origEvent->setAccepted(true); } } if (layoutDirection() == Qt::RightToLeft) { delete event; } } void Canvas::mouseReleaseEvent(QMouseEvent* event) { QPointF documentPosition; if (layoutDirection() == Qt::LeftToRight) { documentPosition = viewConverter()->viewToDocument(event->pos()) + offset(); } else { const QPoint position(width() - event->x(), event->y()); const QPointF offset(this->offset().x(), this->offset().y()); documentPosition = viewConverter()->viewToDocument(position) + offset; event = new QMouseEvent(QEvent::MouseButtonRelease, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers()); } // flake if(d->toolProxy) d->toolProxy->mouseReleaseEvent(event, documentPosition); if (layoutDirection() == Qt::RightToLeft) { delete event; } } void Canvas::mouseMoveEvent(QMouseEvent* event) { QPointF documentPosition; if (layoutDirection() == Qt::LeftToRight) { documentPosition = viewConverter()->viewToDocument(event->pos()) + offset(); } else { const QPoint position(width() - event->x(), event->y()); const QPointF offset(this->offset().x(), this->offset().y()); documentPosition = viewConverter()->viewToDocument(position) + offset; event = new QMouseEvent(QEvent::MouseMove, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers()); } // flake if(d->toolProxy) d->toolProxy->mouseMoveEvent(event, documentPosition); if (layoutDirection() == Qt::RightToLeft) { delete event; } } void Canvas::mouseDoubleClickEvent(QMouseEvent* event) { QPointF documentPosition; if (layoutDirection() == Qt::LeftToRight) { documentPosition = viewConverter()->viewToDocument(event->pos()) + offset(); } else { const QPoint position(width() - event->x(), event->y()); const QPointF offset(this->offset().x(), this->offset().y()); documentPosition = viewConverter()->viewToDocument(position) + offset; event = new QMouseEvent(QEvent::MouseButtonDblClick, position, mapToGlobal(position), event->button(), event->buttons(), event->modifiers()); } // flake if(d->toolProxy) d->toolProxy->mouseDoubleClickEvent(event, documentPosition); if (layoutDirection() == Qt::RightToLeft) { delete event; } } void Canvas::keyPressEvent(QKeyEvent* event) { // flake if(d->toolProxy) d->toolProxy->keyPressEvent(event); } void Canvas::tabletEvent(QTabletEvent *e) { // flake if(d->toolProxy) d->toolProxy->tabletEvent(e, viewConverter()->viewToDocument(e->pos() + offset())); } QVariant Canvas::inputMethodQuery(Qt::InputMethodQuery query) const { // flake return d->toolProxy ? d->toolProxy->inputMethodQuery(query, *(viewConverter())) : 0; } void Canvas::inputMethodEvent(QInputMethodEvent *event) { // flake if(d->toolProxy) d->toolProxy->inputMethodEvent(event); } void Canvas::paintEvent(QPaintEvent* event) { if (d->view->doc()->map()->isLoading() || d->view->isLoading()) return; register Sheet * const sheet = activeSheet(); if (!sheet) return; // ElapsedTime et("Painting cells", ElapsedTime::PrintOnlyTime); QPainter painter(this); painter.setClipRegion(event->region()); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing); painter.save(); // After the scaling, the painter methods need document coordinates! qreal zoomX, zoomY; viewConverter()->zoom(&zoomX, &zoomY); painter.scale(zoomX, zoomY); const bool layoutReversed = sheet->layoutDirection() == Qt::RightToLeft; const QPointF offset(layoutReversed ? -this->offset().x() : this->offset().x(), this->offset().y()); painter.translate(-offset); // erase background const QRectF paintRect(viewConverter()->viewToDocument(rect()).translated(offset)); painter.fillRect(paintRect, painter.background()); // paint visible cells const QRect visibleRect = visibleCells(); const QPointF topLeft(sheet->columnPosition(visibleRect.left()), sheet->rowPosition(visibleRect.top())); view()->sheetView(sheet)->setPaintCellRange(visibleRect); view()->sheetView(sheet)->paintCells(this, painter, paintRect, topLeft); // flake painter.restore(); // d->offset is the negated CanvasController offset in document coordinates. // painter.save(); painter.translate(-viewConverter()->documentToView(offset)); d->shapeManager->paint(painter, *viewConverter(), false); // painter.restore(); // const QPointF p = -viewConverter()->documentToView(this->offset()); // painter.translate(p.x() /*+ width()*/, p.y()); painter.setRenderHint(QPainter::Antialiasing, false); if(d->toolProxy) d->toolProxy->paint(painter, *viewConverter()); event->accept(); } void Canvas::focusInEvent(QFocusEvent *event) { // If we are in editing mode, we redirect the // focus to the CellEditor or ExternalEditor. + // Using a focus proxy does not work here, because in reference selection + // mode clicking on the canvas to select a reference should end up in the + // editor, which got the focus before. This is determined by storing the + // last editor with focus. It is set by the editors on getting focus by user + // interaction. Setting a focus proxy would always result in the proxy being + // the last editor, because clicking the canvas is a user interaction. // This screws up though (David) selection()->emitRequestFocusEditor(); QWidget::focusInEvent(event); } void Canvas::focusOutEvent(QFocusEvent *event) { QWidget::focusOutEvent(event); } void Canvas::dragEnterEvent(QDragEnterEvent* event) { const QMimeData* mimeData = event->mimeData(); if (mimeData->hasText() || mimeData->hasFormat("application/x-kspread-snippet")) { event->acceptProposedAction(); } } void Canvas::dragMoveEvent(QDragMoveEvent* event) { register Sheet * const sheet = activeSheet(); if (!sheet) { event->ignore(); return; } const QMimeData* mimeData = event->mimeData(); if (mimeData->hasText() || mimeData->hasFormat("application/x-kspread-snippet")) { event->acceptProposedAction(); } else { event->ignore(); return; } #if 0 // TODO Stefan: implement drag marking rectangle QRect dragMarkingRect; if (mimeData->hasFormat("application/x-kspread-snippet")) { if (event->source() == this) { kDebug(36005) << "source == this"; dragMarkingRect = selection()->boundingRect(); } else { kDebug(36005) << "source != this"; QByteArray data = mimeData->data("application/x-kspread-snippet"); QString errorMsg; int errorLine; int errorColumn; QDomDocument doc; if (!doc.setContent(data, false, &errorMsg, &errorLine, &errorColumn)) { // an error occurred kDebug(36005) << "Canvas::daragMoveEvent: an error occurred" << endl << "line: " << errorLine << " col: " << errorColumn << ' ' << errorMsg << endl; dragMarkingRect = QRect(1, 1, 1, 1); } else { QDomElement root = doc.documentElement(); // "spreadsheet-snippet" dragMarkingRect = QRect(1, 1, root.attribute("columns").toInt(), root.attribute("rows").toInt()); } } } else { // if ( mimeData->hasText() ) kDebug(36005) << "has text"; dragMarkingRect = QRect(1, 1, 1, 1); } #endif const QPoint dragAnchor = selection()->boundingRect().topLeft(); double xpos = sheet->columnPosition(dragAnchor.x()); double ypos = sheet->rowPosition(dragAnchor.y()); double width = sheet->columnFormat(dragAnchor.x())->width(); double height = sheet->rowFormat(dragAnchor.y())->height(); // consider also the selection rectangle const QRectF noGoArea(xpos - 1, ypos - 1, width + 3, height + 3); // determine the current position double eventPosX; if (sheet->layoutDirection() == Qt::RightToLeft) { eventPosX = viewConverter()->viewToDocumentX(this->width() - event->pos().x()) + xOffset(); } else { eventPosX = viewConverter()->viewToDocumentX(event->pos().x()) + xOffset(); } double eventPosY = viewConverter()->viewToDocumentY(event->pos().y()) + yOffset(); if (noGoArea.contains(QPointF(eventPosX, eventPosY))) { event->ignore(noGoArea.toRect()); return; } #if 0 // TODO Stefan: implement drag marking rectangle // determine the cell position under the mouse double tmp; const int col = sheet->leftColumn(eventPosX, tmp); const int row = sheet->topRow(eventPosY, tmp); dragMarkingRect.moveTo(QPoint(col, row)); kDebug(36005) << "MARKING RECT =" << dragMarkingRect; #endif } void Canvas::dragLeaveEvent(QDragLeaveEvent *) { } void Canvas::dropEvent(QDropEvent *event) { register Sheet * const sheet = activeSheet(); // FIXME Sheet protection: Not all cells have to be protected. if (!sheet || sheet->isProtected()) { event->ignore(); return; } const QMimeData* mimeData = event->mimeData(); if (!PasteCommand::supports(mimeData)) { event->ignore(); return; } // Do not allow dropping onto the same position. const QPoint topLeft(selection()->boundingRect().topLeft()); const double xpos = sheet->columnPosition(topLeft.x()); const double ypos = sheet->rowPosition(topLeft.y()); const double width = sheet->columnFormat(topLeft.x())->width(); const double height = sheet->rowFormat(topLeft.y())->height(); const QRectF noGoArea(xpos - 1, ypos - 1, width + 3, height + 3); double ev_PosX; if (sheet->layoutDirection() == Qt::RightToLeft) { ev_PosX = viewConverter()->viewToDocumentX(this->width() - event->pos().x()) + xOffset(); } else { ev_PosX = viewConverter()->viewToDocumentX(event->pos().x()) + xOffset(); } double ev_PosY = viewConverter()->viewToDocumentY(event->pos().y()) + yOffset(); if (noGoArea.contains(QPointF(ev_PosX, ev_PosY))) { event->ignore(); return; } else { event->setAccepted(true); } // The destination cell location. double tmp; const int col = sheet->leftColumn(ev_PosX, tmp); const int row = sheet->topRow(ev_PosY, tmp); PasteCommand *const command = new PasteCommand(); command->setSheet(sheet); command->add(Region(col, row, 1, 1, sheet)); command->setMimeData(mimeData); if (event->source() == this) { DeleteCommand *const deleteCommand = new DeleteCommand(command); deleteCommand->setSheet(sheet); deleteCommand->add(*selection()); // selection is still, where the drag started deleteCommand->setRegisterUndo(false); } command->execute(); // Select the pasted cells const int columns = selection()->boundingRect().width(); const int rows = selection()->boundingRect().height(); selection()->initialize(QRect(col, row, columns, rows), sheet); } QRect Canvas::viewToCellCoordinates(const QRectF& viewRect) const { register Sheet * const sheet = activeSheet(); if (!sheet) return QRect(); // NOTE Stefan: Do not consider the layout direction in this case. const QRectF rect = viewConverter()->viewToDocument(viewRect.normalized()).translated(offset()); double tmp; const int left = sheet->leftColumn(rect.left(), tmp); const int right = sheet->rightColumn(rect.right()); const int top = sheet->topRow(rect.top(), tmp); const int bottom = sheet->bottomRow(rect.bottom()); return QRect(left, top, right - left + 1, bottom - top + 1); } QRect Canvas::visibleCells() const { return viewToCellCoordinates(rect()); } //--------------------------------------------- // // Drawing Engine // //--------------------------------------------- QRectF Canvas::cellCoordinatesToView(const QRect& cellRange) const { register Sheet * const sheet = activeSheet(); if (!sheet) return QRectF(); QRectF rect = sheet->cellCoordinatesToDocument(cellRange); // apply scrolling offset rect.translate(-xOffset(), -yOffset()); // convert it to view coordinates rect = d->view->zoomHandler()->documentToView(rect); // apply layout direction if (sheet->layoutDirection() == Qt::RightToLeft) { const double left = rect.left(); const double right = rect.right(); rect.setLeft(width() - right); rect.setRight(width() - left); } return rect; } void Canvas::showToolTip(const QPoint& p) { register Sheet * const sheet = activeSheet(); if (!sheet) return; // Over which cell is the mouse ? double ypos, xpos; double dwidth = d->view->zoomHandler()->viewToDocumentX(width()); int col; if (sheet->layoutDirection() == Qt::RightToLeft) col = sheet->leftColumn((dwidth - d->view->zoomHandler()->viewToDocumentX(p.x()) + xOffset()), xpos); else col = sheet->leftColumn((d->view->zoomHandler()->viewToDocumentX(p.x()) + xOffset()), xpos); int row = sheet->topRow((d->view->zoomHandler()->viewToDocumentY(p.y()) + yOffset()), ypos); Cell cell = Cell(sheet, col, row).masterCell(); CellView cellView = view()->sheetView(sheet)->cellView(cell.column(), cell.row()); if (cellView.isObscured()) { cell = Cell(sheet, cellView.obscuringCell()); cellView = view()->sheetView(sheet)->cellView(cellView.obscuringCell().x(), cellView.obscuringCell().y()); } // displayed tool tip, which has the following priorities: // - cell content if the cell dimension is too small // - cell comment // - hyperlink // Ensure that it is plain text. // Not funny if (intentional or not) appears as hyperlink. QString tipText; // If cell is too small, show the content if (!cellView.dimensionFits()) tipText = cell.displayText().replace('<', "<"); // Show hyperlink, if any if (tipText.isEmpty()) tipText = cell.link().replace('<', "<"); // Nothing to display, bail out if (tipText.isEmpty() && cell.comment().isEmpty()) return; // Cut if the tip is ridiculously long const int maxLen = 256; if (tipText.length() > maxLen) tipText = tipText.left(maxLen).append("..."); // Determine position and width of the current cell. const double cellWidth = cellView.cellWidth(); const double cellHeight = cellView.cellHeight(); // Get the cell dimensions QRect cellRect; bool insideCellRect = false; if (sheet->layoutDirection() == Qt::RightToLeft) { const QRectF rect(dwidth - cellWidth - xpos + xOffset(), ypos - yOffset(), cellWidth, cellHeight); cellRect = viewConverter()->documentToView(rect).toRect(); insideCellRect = cellRect.contains(p); } else { QRectF rect(xpos - xOffset(), ypos - yOffset(), cellWidth, cellHeight); cellRect = viewConverter()->documentToView(rect).toRect(); insideCellRect = cellRect.contains(p); } // No use if mouse is somewhere else if (!insideCellRect) return; // Show comment, if any. if (tipText.isEmpty()) tipText = cell.comment().replace('<', "<"); else if (!cell.comment().isEmpty()) tipText += "

" + i18n("Comment:") + "

" + cell.comment().replace('<', "<"); // Now we show the tip QToolTip::showText(mapToGlobal(cellRect.bottomRight()), "

" + tipText.replace('\n', "
") + "

", this, cellRect.translated(-mapToGlobal(cellRect.topLeft()))); } void Canvas::updateInputMethodInfo() { updateMicroFocus(); } #include "Canvas.moc" diff --git a/kspread/tests/CMakeLists.txt b/kspread/tests/CMakeLists.txt index 5d53f4e277..b0f3e9509b 100644 --- a/kspread/tests/CMakeLists.txt +++ b/kspread/tests/CMakeLists.txt @@ -1,145 +1,151 @@ include(MacroAddCompileFlags) set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) include_directories( ${CMAKE_SOURCE_DIR}/kspread ${EIGEN2_INCLUDE_DIR} ${KOMAIN_INCLUDES} ${KDE4_INCLUDES} ) ########### Core Functionality ############### set(TestDependencies_SRCS TestDependencies.cpp) kde4_add_unit_test(TestDependencies TESTNAME kspread-Dependencies ${TestDependencies_SRCS}) target_link_libraries(TestDependencies kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestFormula_SRCS TestFormula.cpp) kde4_add_unit_test(TestFormula TESTNAME kspread-Formula ${TestFormula_SRCS}) target_link_libraries(TestFormula kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestOpenFormula_SRCS TestOpenFormula.cpp) kde4_add_unit_test(TestOpenFormula TESTNAME kspread-OpenFormula ${TestOpenFormula_SRCS}) target_link_libraries(TestOpenFormula kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestPointStorage_SRCS TestPointStorage.cpp) kde4_add_unit_test(TestPointStorage TESTNAME kspread-PointStorage ${TestPointStorage_SRCS}) target_link_libraries(TestPointStorage kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestRegion_SRCS TestRegion.cpp) kde4_add_unit_test(TestRegion TESTNAME kspread-Region ${TestRegion_SRCS}) target_link_libraries(TestRegion kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestRTree_SRCS TestRTree.cpp) kde4_add_unit_test(TestRTree TESTNAME kpsread-RTree ${TestRTree_SRCS}) target_link_libraries(TestRTree ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY}) ########### next target ############### +set(TestSelection_SRCS TestSelection.cpp) +kde4_add_unit_test(TestSelection TESTNAME kspread-Selection ${TestSelection_SRCS}) +target_link_libraries(TestSelection kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) + +########### next target ############### + set(TestValue_SRCS TestValue.cpp) kde4_add_unit_test(TestValue TESTNAME kspread-Value ${TestValue_SRCS}) target_link_libraries(TestValue kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### Function tests ############### set(TestBitopsFunctions_SRCS TestBitopsFunctions.cpp) kde4_add_unit_test(TestBitopsFunctions TESTNAME kspread-BitopsFunctions ${TestBitopsFunctions_SRCS}) target_link_libraries(TestBitopsFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestDatabaseFunctions_SRCS TestDatabaseFunctions.cpp) kde4_add_unit_test(TestDatabaseFunctions TESTNAME kspread-DatabaseFunctions ${TestDatabaseFunctions_SRCS}) target_link_libraries(TestDatabaseFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### target TestDatetimeFunctions ############### set(TestDatetimeFunctions_SRCS TestDatetimeFunctions.cpp) kde4_add_unit_test(TestDatetimeFunctions TESTNAME kspread-DatetimeFunctions ${TestDatetimeFunctions_SRCS}) target_link_libraries(TestDatetimeFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestEngineeringFunctions_SRCS TestEngineeringFunctions.cpp) kde4_add_unit_test(TestEngineeringFunctions TESTNAME kspread-EngineeringFunctions ${TestEngineeringFunctions_SRCS}) target_link_libraries(TestEngineeringFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestFinancialFunctions_SRCS TestFinancialFunctions.cpp) kde4_add_unit_test(TestFinancialFunctions TESTNAME kspread-FinancialFunctions ${TestFinancialFunctions_SRCS}) target_link_libraries(TestFinancialFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestInformationFunctions_SRCS TestInformationFunctions.cpp) kde4_add_unit_test(TestInformationFunctions TESTNAME kspread-InformationFunctions ${TestInformationFunctions_SRCS}) target_link_libraries(TestInformationFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestLogicFunctions_SRCS TestLogicFunctions.cpp) kde4_add_unit_test(TestLogicFunctions TESTNAME kspread-LogicFunctions ${TestLogicFunctions_SRCS}) target_link_libraries(TestLogicFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestMathFunctions_SRCS TestMathFunctions.cpp) kde4_add_unit_test(TestMathFunctions TESTNAME kspread-MathFunctions ${TestMathFunctions_SRCS}) target_link_libraries(TestMathFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestStatisticalFunctions_SRCS TestStatisticalFunctions.cpp) kde4_add_unit_test(TestStatisticalFunctions TESTNAME kspread-StatisticalFunctions ${TestStatisticalFunctions_SRCS}) target_link_libraries(TestStatisticalFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestTextFunctions_SRCS TestTextFunctions.cpp) kde4_add_unit_test(TestTextFunctions TESTNAME kspread-TextFunctions ${TestTextFunctions_SRCS}) target_link_libraries(TestTextFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestTrigFunctions_SRCS TestTrigFunctions.cpp) kde4_add_unit_test(TestTrigFunctions TESTNAME kspread-TrigFunctions ${TestTrigFunctions_SRCS}) target_link_libraries(TestTrigFunctions kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestValueFormatter_SRCS TestValueFormatter.cpp) kde4_add_unit_test(TestValueFormatter TESTNAME kspread-ValueFormatter ${TestValueFormatter_SRCS}) macro_add_compile_flags(TestValueFormatter "-DKSPREAD_UNIT_TEST") target_link_libraries(TestValueFormatter kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(TestSheet_SRCS TestSheet.cpp) kde4_add_unit_test(TestSheet TESTNAME kspread-Sheet ${TestSheet_SRCS}) macro_add_compile_flags(TestSheet "-DKSPREAD_UNIT_TEST") target_link_libraries(TestSheet kspreadcommon ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### Benchmarks ############### # set(BenchmarkCluster_SRCS BenchmarkCluster.cpp ../Cluster.cpp) # explicit Cluster.cpp for no extra symbol visibility # kde4_add_executable(BenchmarkCluster TEST ${BenchmarkCluster_SRCS}) # target_link_libraries(BenchmarkCluster kspreadcommon ${QT_QTTEST_LIBRARY}) ########### next target ############### set(BenchmarkPointStorage_SRCS BenchmarkPointStorage.cpp) kde4_add_executable(BenchmarkPointStorage TEST ${BenchmarkPointStorage_SRCS}) target_link_libraries(BenchmarkPointStorage ${QT_QTCORE_LIBRARY} ${QT_QTTEST_LIBRARY}) ########### next target ############### set(BenchmarkRTree_SRCS BenchmarkRTree.cpp) kde4_add_executable(BenchmarkRTree TEST ${BenchmarkRTree_SRCS}) target_link_libraries(BenchmarkRTree ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY}) diff --git a/kspread/tests/TestSelection.cpp b/kspread/tests/TestSelection.cpp new file mode 100644 index 0000000000..79a92547a4 --- /dev/null +++ b/kspread/tests/TestSelection.cpp @@ -0,0 +1,467 @@ +/* This file is part of the KDE project + Copyright 2009 Stefan Nikolaus + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "TestSelection.h" + +#include + +#define kWarning qWarning + +#include "part/Canvas.h" +#include "Map.h" +#include "Selection.h" +#include "Sheet.h" + +using namespace KSpread; + +void TestSelection::initialize() +{ + Map map; + Sheet sheet1(&map, "Sheet1"); + map.addSheet(&sheet1); + Sheet sheet2(&map, "Sheet2"); + map.addSheet(&sheet2); + Canvas canvas(0); + Selection selection(&canvas); + selection.setActiveSheet(&sheet1); + + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("A1")); + + // Clearing in normal selection mode, results in A1 as residuum. + selection.clear(); + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("Sheet1!A1")); + + selection.initialize(QPoint(2, 4), &sheet2); + QCOMPARE(selection.name(), QString("Sheet2!B4")); + + selection.initialize(QRect(3, 5, 2, 3)); + QCOMPARE(selection.name(), QString("Sheet1!C5:D7")); + + Region region("A1:A4", &map, &sheet2); + selection.initialize(region); + QCOMPARE(selection.name(), QString("Sheet2!A1:A4")); + + region = Region("Sheet1!A2:A4", &map, &sheet2); + selection.initialize(region); + QCOMPARE(selection.name(), QString("Sheet1!A2:A4")); + + // reference mode: + selection.startReferenceSelection(); + + region = Region("Sheet1!A2:A4;B3;C5", &map, &sheet2); + selection.initialize(region); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5")); + QCOMPARE(selection.activeSubRegionName(), QString("A2:A4;Sheet2!B3;Sheet2!C5")); + + selection.setActiveSubRegion(1, 1); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!B3")); + selection.initialize(QPoint(2, 2)); + QCOMPARE(selection.name(), QString("A2:A4;B2;Sheet2!C5")); + QCOMPARE(selection.activeSubRegionName(), QString("B2")); + + selection.setActiveSubRegion(1, 2, 2); + QCOMPARE(selection.activeSubRegionName(), QString("B2;Sheet2!C5")); + selection.initialize(QPoint(3, 3), &sheet2); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!C3")); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!C3")); + + selection.setActiveSubRegion(1, 0, 2); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.initialize(QPoint(4, 4), &sheet2); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!D4;Sheet2!C3")); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!D4")); + + selection.setActiveSubRegion(1, 1, 2); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!D4")); + selection.clearSubRegion(); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!C3")); + QCOMPARE(selection.activeSubRegionName(), QString()); + + // Two appendings. + selection.clear(); + selection.setActiveSubRegion(0, 0, 0); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.initialize(QPoint(1, 1), &sheet1); + QCOMPARE(selection.activeSubRegionName(), QString("A1")); + selection.setActiveSubRegion(1, 0, 1); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.initialize(QPoint(2, 2), &sheet1); + QCOMPARE(selection.name(), QString("A1;B2")); + QCOMPARE(selection.activeSubRegionName(), QString("B2")); +} + +void TestSelection::update() +{ + Map map; + Sheet sheet1(&map, "Sheet1"); + map.addSheet(&sheet1); + Sheet sheet2(&map, "Sheet2"); + map.addSheet(&sheet2); + Canvas canvas(0); + Selection selection(&canvas); + selection.setActiveSheet(&sheet1); + + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("A1")); + + // Selection::update(const QPoint&) works always on the active sheet. + + // normal mode: + + // init with location + selection.initialize(QPoint(1, 6), &sheet1); + QCOMPARE(selection.name(), QString("Sheet1!A6")); + + // init with region + selection.initialize(Region("A3", &map, &sheet1)); + QCOMPARE(selection.name(), QString("Sheet1!A3")); + + // init with location after init with region + selection.initialize(QPoint(1, 5), &sheet1); + QCOMPARE(selection.name(), QString("Sheet1!A5")); + + // TODO + + // reference mode: + selection.startReferenceSelection(); + + Region region("Sheet1!A2:A4;B3;C5", &map, &sheet2); + selection.initialize(region); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5")); + + // Prepend new range. + selection.setActiveSubRegion(0, 0); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.update(QPoint(1, 1)); + QCOMPARE(selection.name(), QString("A1;A2:A4;Sheet2!B3;Sheet2!C5")); + + // Insert new range on active sheet. + selection.setActiveSubRegion(2, 0); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.update(QPoint(1, 1)); + QCOMPARE(selection.name(), QString("A1;A2:A4;A1;Sheet2!B3;Sheet2!C5")); + + // Append new range. + selection.setActiveSubRegion(5, 0); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.update(QPoint(1, 1)); + QCOMPARE(selection.name(), QString("A1;A2:A4;A1;Sheet2!B3;Sheet2!C5;A1")); + + // Update range. + selection.setActiveSubRegion(2, 2); + QCOMPARE(selection.activeSubRegionName(), QString("A1;Sheet2!B3")); + selection.update(QPoint(2, 2)); + QCOMPARE(selection.name(), QString("A1;A2:A4;A1:B2;Sheet2!B3;Sheet2!C5;A1")); + + // Try to update range on non-active sheet. Inserts location on active sheet. + selection.setActiveSubRegion(2, 2, 3); + QCOMPARE(selection.activeSubRegionName(), QString("A1:B2;Sheet2!B3")); + selection.update(QPoint(2, 2)); + QCOMPARE(selection.name(), QString("A1;A2:A4;A1:B2;Sheet2!B3;B2;Sheet2!C5;A1")); +} + +void TestSelection::extend() +{ + Map map; + Sheet sheet1(&map, "Sheet1"); + map.addSheet(&sheet1); + Sheet sheet2(&map, "Sheet2"); + map.addSheet(&sheet2); + Canvas canvas(0); + Selection selection(&canvas); + selection.setActiveSheet(&sheet1); + + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("A1")); + + // normal mode: + // TODO + + // reference mode: + selection.startReferenceSelection(); + + Region region("Sheet1!A2:A4;B3;C5", &map, &sheet2); + selection.initialize(region); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5")); + + // Prepend new location. + selection.setActiveSubRegion(0, 0); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.extend(QPoint(1, 1), &sheet2); + QCOMPARE(selection.name(), QString("Sheet2!A1;A2:A4;Sheet2!B3;Sheet2!C5")); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!A1")); + + // Try to prepend new location. Prepending needs length = 0. + selection.setActiveSubRegion(0, 2); // results in: 0, 2, 0 + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!A1;A2:A4")); + selection.extend(QPoint(1, 1), &sheet1); + QCOMPARE(selection.name(), QString("Sheet2!A1;A1;A2:A4;Sheet2!B3;Sheet2!C5")); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!A1;A1;A2:A4")); + + // Prepend new range. + selection.setActiveSubRegion(0, 0); + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.extend(QRect(1, 1, 2, 2), &sheet1); + QCOMPARE(selection.name(), QString("A1:B2;Sheet2!A1;A1;A2:A4;Sheet2!B3;Sheet2!C5")); + QCOMPARE(selection.activeSubRegionName(), QString("A1:B2")); + + // Try to prepend new range. Prepending needs length = 0. + selection.setActiveSubRegion(0, 3); // results in: 0, 3, 0 + QCOMPARE(selection.activeSubRegionName(), QString("A1:B2;Sheet2!A1;A1")); + selection.extend(QRect(1, 2, 2, 2), &sheet2); + QCOMPARE(selection.name(), QString("A1:B2;Sheet2!A2:B3;Sheet2!A1;A1;A2:A4;Sheet2!B3;Sheet2!C5")); + QCOMPARE(selection.activeSubRegionName(), QString("A1:B2;Sheet2!A2:B3;Sheet2!A1;A1")); + + selection.clear(); + QVERIFY(selection.isEmpty()); + selection.initialize(region); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5")); + + // Append new location. + selection.setActiveSubRegion(1, 3, 3); // results in: 1, 2, 3 + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!B3;Sheet2!C5")); + selection.extend(QPoint(1, 1), &sheet2); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5;Sheet2!A1")); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!B3;Sheet2!C5;Sheet2!A1")); + + // Append new location. + selection.setActiveSubRegion(4, 1, 3); // results in: 4, 0, 4 + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.extend(QPoint(1, 1), &sheet1); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5;Sheet2!A1;A1")); + QCOMPARE(selection.activeSubRegionName(), QString("A1")); + + // Append new range. + selection.setActiveSubRegion(5, 0); // results in: 5, 0, 5 + QCOMPARE(selection.activeSubRegionName(), QString()); + selection.extend(QRect(1, 1, 2, 2), &sheet1); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5;Sheet2!A1;A1;A1:B2")); + QCOMPARE(selection.activeSubRegionName(), QString("A1:B2")); + + // Append new range. + selection.setActiveSubRegion(5, 1, 6); + QCOMPARE(selection.activeSubRegionName(), QString("A1:B2")); + selection.extend(QRect(1, 2, 2, 2), &sheet2); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5;Sheet2!A1;A1;A1:B2;Sheet2!A2:B3")); + QCOMPARE(selection.activeSubRegionName(), QString("A1:B2;Sheet2!A2:B3")); + + selection.clear(); + QVERIFY(selection.isEmpty()); + selection.initialize(region); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!C5")); + + // Insert new location. + selection.setActiveSubRegion(0, 3, 1); + QCOMPARE(selection.activeSubRegionName(), QString("A2:A4;Sheet2!B3;Sheet2!C5")); + selection.extend(QPoint(1, 1), &sheet2); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!A1;Sheet2!C5")); + QCOMPARE(selection.activeSubRegionName(), QString("A2:A4;Sheet2!B3;Sheet2!A1;Sheet2!C5")); + + // Insert new range. + selection.setActiveSubRegion(1, 3, 3); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!B3;Sheet2!A1;Sheet2!C5")); + selection.extend(QRect(1, 1, 2, 2), &sheet1); + QCOMPARE(selection.name(), QString("A2:A4;Sheet2!B3;Sheet2!A1;Sheet2!C5;A1:B2")); + QCOMPARE(selection.activeSubRegionName(), QString("Sheet2!B3;Sheet2!A1;Sheet2!C5;A1:B2")); +} + +void TestSelection::activeElement() +{ + Map map; + Sheet sheet1(&map, "Sheet1"); + map.addSheet(&sheet1); + Sheet sheet2(&map, "Sheet2"); + map.addSheet(&sheet2); + Canvas canvas(0); + Selection selection(&canvas); + selection.setActiveSheet(&sheet1); + + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("A1")); + QVERIFY(!selection.activeElement()); + + // normal mode: + + //BEGIN Leave the active element unset. + selection.startReferenceSelection(); + QVERIFY(!selection.activeElement()); + + selection.initialize(Region("A3:A4;B2;C4:D5", &map, &sheet1)); + QCOMPARE(selection.name(&sheet1), QString("A3:A4;B2;C4:D5")); + QVERIFY(!selection.activeElement()); + + selection.extend(QRect(1, 1, 4, 4), &sheet1); + QCOMPARE(selection.name(&sheet1), QString("A3:A4;B2;C4:D5;A1:D4")); + QVERIFY(!selection.activeElement()); + + selection.update(QPoint(5, 5)); + QCOMPARE(selection.name(&sheet1), QString("A3:A4;B2;C4:D5;A1:E5")); + QVERIFY(!selection.activeElement()); + + selection.initialize(QPoint(5, 6), &sheet1); + QCOMPARE(selection.name(&sheet1), QString("E6")); + QVERIFY(!selection.activeElement()); + + selection.initialize(QRect(1, 1, 2, 1), &sheet1); + QCOMPARE(selection.name(&sheet1), QString("A1:B1")); + QVERIFY(!selection.activeElement()); + + selection.extend(QPoint(1, 3), &sheet1); + QCOMPARE(selection.name(&sheet1), QString("A1:B1;A3")); + QVERIFY(!selection.activeElement()); + //END Leave the active element unset. +} + +void TestSelection::referenceSelectionMode() +{ + Map map; + Sheet sheet1(&map, "Sheet1"); + map.addSheet(&sheet1); + Sheet sheet2(&map, "Sheet2"); + map.addSheet(&sheet2); + Canvas canvas(0); + Selection selection(&canvas); + selection.setActiveSheet(&sheet1); + + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("A1")); + + // reference mode: + selection.startReferenceSelection(); + QVERIFY(selection.isEmpty()); + + selection.initialize(QPoint(2, 2), &sheet2); + QCOMPARE(selection.name(), QString("Sheet2!B2")); + + selection.extend(QPoint(1, 2)); + QCOMPARE(selection.name(), QString("Sheet2!B2;A2")); + + selection.setActiveSubRegion(1, 1, 1); + QCOMPARE(selection.activeSubRegionName(), QString("A2")); + + selection.endReferenceSelection(); + QCOMPARE(selection.name(), QString("A1")); + + selection.initialize(QPoint(2, 2), &sheet1); + QCOMPARE(selection.name(), QString("B2")); + + // quit it again + selection.endReferenceSelection(); + QCOMPARE(selection.name(), QString("B2")); + + selection.initialize(QRect(2, 2, 2, 2)); + QCOMPARE(selection.name(), QString("B2:C3")); + + // start it again + selection.startReferenceSelection(); + QVERIFY(selection.isEmpty()); + + selection.initialize(Region("A3", &map, &sheet1)); + QCOMPARE(selection.name(), QString("A3")); + + // set the active sub-region beyond the last range + selection.setActiveSubRegion(1, 0, 1); + QCOMPARE(selection.activeSubRegionName(), QString()); + + // quit it again + selection.endReferenceSelection(); + QCOMPARE(selection.name(), QString("B2:C3")); +} + +void TestSelection::covering() +{ + Map map; + Sheet sheet1(&map, "Sheet1"); + map.addSheet(&sheet1); + Sheet sheet2(&map, "Sheet2"); + map.addSheet(&sheet2); + Canvas canvas(0); + Selection selection(&canvas); + selection.setActiveSheet(&sheet1); + + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("A1")); + + // normal mode: + // convered ranges get removed + + selection.initialize(Region("A3:A4;B2;C4:D5", &map, &sheet1)); + QCOMPARE(selection.name(&sheet1), QString("A3:A4;B2;C4:D5")); + + selection.extend(QRect(1, 1, 4, 4), &sheet1); + QCOMPARE(selection.name(&sheet1), QString("C4:D5;A1:D4")); + + selection.update(QPoint(5, 5)); + QCOMPARE(selection.name(&sheet1), QString("A1:E5")); + + // reference mode: + // covered ranges get ignored + + selection.startReferenceSelection(); + + selection.initialize(Region("A3:A4;B2;C4:D5", &map, &sheet1)); + QCOMPARE(selection.name(), QString("A3:A4;B2;C4:D5")); + + selection.extend(QRect(1, 1, 4, 4), &sheet1); + QCOMPARE(selection.name(), QString("A3:A4;B2;C4:D5;A1:D4")); + + selection.update(QPoint(5, 5)); + QCOMPARE(selection.name(&sheet1), QString("A3:A4;B2;C4:D5;A1:E5")); +} + +void TestSelection::splitting() +{ + Map map; + Sheet sheet1(&map, "Sheet1"); + map.addSheet(&sheet1); + Sheet sheet2(&map, "Sheet2"); + map.addSheet(&sheet2); + Canvas canvas(0); + Selection selection(&canvas); + selection.setActiveSheet(&sheet1); + + QVERIFY(!selection.isEmpty()); + QCOMPARE(selection.name(), QString("A1")); + + // normal mode: + // ranges get split + + selection.initialize(Region("A1:D5", &map, &sheet1)); + QCOMPARE(selection.name(&sheet1), QString("A1:D5")); + + selection.extend(QPoint(2, 2), &sheet1); + QCOMPARE(selection.name(&sheet1), QString("A3:D5;C2:D2;A2;A1:D1")); + + // reference mode: + // ranges are not affected + + selection.startReferenceSelection(); + + selection.initialize(Region("A1:D5", &map, &sheet1)); + QCOMPARE(selection.name(), QString("A1:D5")); + + selection.extend(QPoint(2, 2), &sheet1); + QCOMPARE(selection.name(), QString("A1:D5;B2")); +} + +QTEST_KDEMAIN(TestSelection, GUI) + +#include "TestSelection.moc" diff --git a/kspread/tests/TestSelection.h b/kspread/tests/TestSelection.h new file mode 100644 index 0000000000..8be5030113 --- /dev/null +++ b/kspread/tests/TestSelection.h @@ -0,0 +1,43 @@ +/* This file is part of the KDE project + Copyright 2009 Stefan Nikolaus + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KSPREAD_TEST_SELECTION +#define KSPREAD_TEST_SELECTION + +#include + +namespace KSpread +{ + +class TestSelection : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initialize(); + void update(); + void extend(); + void activeElement(); + void referenceSelectionMode(); + void covering(); + void splitting(); +}; + +} // namespace KSpread + +#endif // KSPREAD_TEST_SELECTION diff --git a/kspread/ui/CellToolBase.cpp b/kspread/ui/CellToolBase.cpp index 4ab9b382a5..6ff8a3e2c9 100644 --- a/kspread/ui/CellToolBase.cpp +++ b/kspread/ui/CellToolBase.cpp @@ -1,3368 +1,3386 @@ /* This file is part of the KDE project Copyright 2006-2008 Stefan Nikolaus Copyright 2005-2006 Raphael Langerhorst Copyright 2002-2005 Ariya Hidayat Copyright 1999-2003 Laurent Montel Copyright 2002-2003 Norbert Andres Copyright 2002-2003 Philipp Mueller Copyright 2002-2003 John Dailey Copyright 1999-2003 David Faure Copyright 1999-2001 Simon Hausmann Copyright 1998-2000 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or(at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "CellToolBase.h" #include "CellToolBase_p.h" // KSpread #include "ApplicationSettings.h" #include "AutoFillStrategy.h" #include "CalculationSettings.h" #include "Cell.h" #include "CellToolOptionWidget.h" #include "CellView.h" #include "Damages.h" #include "database/Database.h" #include "DragAndDropStrategy.h" #include "HyperlinkStrategy.h" #include "tests/inspector.h" #include "Map.h" #include "MergeStrategy.h" #include "NamedAreaManager.h" #include "PasteStrategy.h" #include "SelectionStrategy.h" #include "Sheet.h" #include "SheetView.h" #include "StyleManager.h" // commands #include "commands/AutoFilterCommand.h" #include "commands/BorderColorCommand.h" #include "commands/CommentCommand.h" #include "commands/ConditionCommand.h" #include "commands/DataManipulators.h" #include "commands/DeleteCommand.h" #include "commands/IndentationCommand.h" #include "commands/LinkCommand.h" #include "commands/MergeCommand.h" #include "commands/PageBreakCommand.h" #include "commands/PasteCommand.h" #include "commands/PrecisionCommand.h" #include "commands/RowColumnManipulators.h" #include "commands/SortManipulator.h" #include "commands/SpellCheckCommand.h" #include "commands/StyleCommand.h" #include "commands/ValidityCommand.h" // dialogs #include "dialogs/AddNamedAreaDialog.h" #include "dialogs/AngleDialog.h" #include "dialogs/AutoFormatDialog.h" #include "dialogs/CommentDialog.h" #include "dialogs/ConditionalDialog.h" #include "dialogs/ConsolidateDialog.h" #include "dialogs/CSVDialog.h" #include "dialogs/DatabaseDialog.h" #include "dialogs/DocumentSettingsDialog.h" #include "dialogs/GoalSeekDialog.h" #include "dialogs/GotoDialog.h" #include "dialogs/InsertDialog.h" #include "dialogs/LayoutDialog.h" #include "dialogs/LinkDialog.h" #include "dialogs/ListDialog.h" #include "dialogs/NamedAreaDialog.h" #include "dialogs/PasteInsertDialog.h" #include "dialogs/Resize2Dialog.h" #include "dialogs/SeriesDialog.h" #include "dialogs/ShowColRowDialog.h" #include "dialogs/SortDialog.h" #include "dialogs/SpecialPasteDialog.h" #include "dialogs/StyleManagerDialog.h" #include "dialogs/SubtotalDialog.h" #include "dialogs/ValidityDialog.h" // KOffice #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include #include // Qt #include #include #include #include #include #ifndef NDEBUG #include #include "SheetModel.h" #endif using namespace KSpread; CellToolBase::CellToolBase(KoCanvasBase* canvas) : KoInteractionTool(canvas) , d(new Private(this)) { d->cellEditor = 0; d->formulaDialog = 0; d->specialCharDialog = 0; d->optionWidget = 0; d->initialized = false; d->popupListChoose = 0; d->lastEditorWithFocus = EmbeddedEditor; d->findOptions = 0; d->findLeftColumn = 0; d->findRightColumn = 0; d->findTopRow = 0; d->findBottomRow = 0; d->typeValue = FindOption::Value; d->directionValue = FindOption::Row; d->find = 0; d->replace = 0; d->replaceCommand = 0; d->searchInSheets.currentSheet = 0; d->searchInSheets.firstSheet = 0; // Create the extra and ones with extended names for the context menu. d->createPopupMenuActions(); // Create the actions. KAction* action = 0; // -- cell style actions -- action = new KAction(KIcon("cell_layout"), i18n("Cell Format..."), this); action->setIconText(i18n("Format")); addAction("cellStyle", action); action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_F)); connect(action, SIGNAL(triggered(bool)), this, SLOT(cellStyle())); action->setToolTip(i18n("Set the cell formatting")); action = new KAction(i18n("Default"), this); addAction("setDefaultStyle", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(setDefaultStyle())); action->setToolTip(i18n("Resets to the default format")); action = new KAction(i18n("Style Manager"), this); addAction("styleDialog", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(styleDialog())); action->setToolTip(i18n("Edit and organize cell styles")); action = new KSelectAction(i18n("Style"), this); addAction("setStyle", action); action->setToolTip(i18n("Apply a predefined style to the selected cells")); connect(action, SIGNAL(triggered(const QString&)), this, SLOT(setStyle(const QString&))); action = new KAction(i18n("Create Style From Cell..."), this); action->setIconText(i18n("Style From Cell")); addAction("createStyleFromCell", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(createStyleFromCell())); action->setToolTip(i18n("Create a new style based on the currently selected cell")); // -- font actions -- action = new KToggleAction(KIcon("format-text-bold"), i18n("Bold"), this); addAction("bold", action); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_B)); connect(action, SIGNAL(triggered(bool)), this, SLOT(bold(bool))); action = new KToggleAction(KIcon("format-text-italic"), i18n("Italic"), this); addAction("italic", action); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_I)); connect(action, SIGNAL(triggered(bool)), this, SLOT(italic(bool))); action = new KToggleAction(KIcon("format-text-underline"), i18n("Underline"), this); addAction("underline", action); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U)); connect(action, SIGNAL(triggered(bool)), this, SLOT(underline(bool))); action = new KToggleAction(KIcon("format-text-strikethrough"), i18n("Strike Out"), this); addAction("strikeOut", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool))); action = new KFontAction(i18n("Select Font..."), this); action->setIconText(i18n("Font")); addAction("font", action); connect(action, SIGNAL(triggered(const QString&)), this, SLOT(font(const QString&))); action = new KFontSizeAction(i18n("Select Font Size"), this); action->setIconText(i18n("Font Size")); addAction("fontSize", action); connect(action, SIGNAL(fontSizeChanged(int)), this, SLOT(fontSize(int))); action = new KAction(KIcon("format-font-size-more"), i18n("Increase Font Size"), this); addAction("increaseFontSize", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(increaseFontSize())); action = new KAction(KIcon("format-font-size-less"), i18n("Decrease Font Size"), this); addAction("decreaseFontSize", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(decreaseFontSize())); action = new KoColorPopupAction(this); action->setIcon(KIcon("format-text-color")); action->setText(i18n("Text Color")); action->setToolTip(i18n("Set the text color")); addAction("textColor", action); connect(action, SIGNAL(colorChanged(const KoColor &)), this, SLOT(changeTextColor(const KoColor &))); // -- horizontal alignment actions -- QActionGroup* groupAlign = new QActionGroup(this); action = new KToggleAction(KIcon("format-justify-left"), i18n("Align Left"), this); action->setIconText(i18n("Left")); addAction("alignLeft", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(alignLeft(bool))); action->setToolTip(i18n("Left justify the cell contents")); action->setActionGroup(groupAlign); action = new KToggleAction(KIcon("format-justify-center"), i18n("Align Center"), this); action->setIconText(i18n("Center")); addAction("alignCenter", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(alignCenter(bool))); action->setToolTip(i18n("Center the cell contents")); action->setActionGroup(groupAlign); action = new KToggleAction(KIcon("format-justify-right"), i18n("Align Right"), this); action->setIconText(i18n("Right")); addAction("alignRight", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(alignRight(bool))); action->setToolTip(i18n("Right justify the cell contents")); action->setActionGroup(groupAlign); // -- vertical alignment actions -- QActionGroup* groupPos = new QActionGroup(this); action = new KToggleAction(KIcon("text_top"), i18n("Align Top"), this); action->setIconText(i18n("Top")); addAction("alignTop", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(alignTop(bool))); action->setToolTip(i18n("Align cell contents along the top of the cell")); action->setActionGroup(groupPos); action = new KToggleAction(KIcon("middle"), i18n("Align Middle"), this); action->setIconText(i18n("Middle")); addAction("alignMiddle", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(alignMiddle(bool))); action->setToolTip(i18n("Align cell contents centered in the cell")); action->setActionGroup(groupPos); action = new KToggleAction(KIcon("text_bottom"), i18n("Align Bottom"), this); action->setIconText(i18n("Bottom")); addAction("alignBottom", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(alignBottom(bool))); action->setToolTip(i18n("Align cell contents along the bottom of the cell")); action->setActionGroup(groupPos); // -- border actions -- action = new KAction(KIcon("border_left"), i18n("Border Left"), this); action->setIconText(i18n("Left")); addAction("borderLeft", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(borderLeft())); action->setToolTip(i18n("Set a left border to the selected area")); action = new KAction(KIcon("border_right"), i18n("Border Right"), this); action->setIconText(i18n("Right")); addAction("borderRight", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(borderRight())); action->setToolTip(i18n("Set a right border to the selected area")); action = new KAction(KIcon("border_top"), i18n("Border Top"), this); action->setIconText(i18n("Top")); addAction("borderTop", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(borderTop())); action->setToolTip(i18n("Set a top border to the selected area")); action = new KAction(KIcon("border_bottom"), i18n("Border Bottom"), this); action->setIconText(i18n("Bottom")); addAction("borderBottom", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(borderBottom())); action->setToolTip(i18n("Set a bottom border to the selected area")); action = new KAction(KIcon("border_all"), i18n("All Borders"), this); action->setIconText(i18n("All")); addAction("borderAll", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(borderAll())); action->setToolTip(i18n("Set a border around all cells in the selected area")); action = new KAction(KIcon("border_remove"), i18n("No Borders"), this); action->setIconText(i18n("None")); addAction("borderRemove", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(borderRemove())); action->setToolTip(i18n("Remove all borders in the selected area")); action = new KAction(KIcon(("border_outline")), i18n("Border Outline"), this); action->setIconText(i18n("Outline")); addAction("borderOutline", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(borderOutline())); action->setToolTip(i18n("Set a border to the outline of the selected area")); action = new KoColorPopupAction(this); action->setIcon(KIcon("format-stroke-color")); action->setToolTip(i18n("Select a new border color")); action->setText(i18n("Border Color")); addAction("borderColor", action); connect(action, SIGNAL(colorChanged(const KoColor &)), this, SLOT(borderColor(const KoColor &))); // -- text layout actions -- action = new KToggleAction(KIcon("multirow"), i18n("Wrap Text"), this); action->setIconText(i18n("Wrap")); addAction("wrapText", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(wrapText(bool))); action->setToolTip(i18n("Make the cell text wrap onto multiple lines")); action = new KToggleAction(KIcon("vertical_text"), i18n("Vertical Text"), this); action->setIconText(i18n("Vertical")); addAction("verticalText", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(verticalText(bool))); action->setToolTip(i18n("Print cell contents vertically")); action = new KAction(KIcon(QApplication::isRightToLeft() ? "format-indent-less" : "format-indent-more"), i18n("Increase Indent"), this); addAction("increaseIndentation", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(increaseIndentation())); action->setToolTip(i18n("Increase the indentation")); action = new KAction(KIcon(QApplication::isRightToLeft() ? "format-indent-more" : "format-indent-less"), i18n("Decrease Indent"), this); addAction("decreaseIndentation", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(decreaseIndentation())); action->setToolTip(i18n("Decrease the indentation")); action = new KAction(i18n("Change Angle..."), this); action->setIconText(i18n("Angle")); addAction("changeAngle", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(changeAngle())); action->setToolTip(i18n("Change the angle that cell contents are printed")); // -- value format actions -- action = new KToggleAction(KIcon("percent"), i18n("Percent Format"), this); action->setIconText(i18n("Percent")); addAction("percent", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(percent(bool))); action->setToolTip(i18n("Set the cell formatting to look like a percentage")); action = new KToggleAction(KIcon("money"), i18n("Money Format"), this); action->setIconText(i18n("Money")); addAction("currency", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(currency(bool))); action->setToolTip(i18n("Set the cell formatting to look like your local currency")); action = new KAction(KIcon("prec_plus"), i18n("Increase Precision"), this); addAction("increasePrecision", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(increasePrecision())); action->setToolTip(i18n("Increase the decimal precision shown onscreen")); action = new KAction(KIcon("prec_minus"), i18n("Decrease Precision"), this); addAction("decreasePrecision", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(decreasePrecision())); action->setToolTip(i18n("Decrease the decimal precision shown onscreen")); // -- misc style attribute actions -- action = new KAction(KIcon("fontsizeup"), i18n("Upper Case"), this); action->setIconText(i18n("Upper")); addAction("toUpperCase", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(toUpperCase())); action->setToolTip(i18n("Convert all letters to upper case")); action = new KAction(KIcon("fontsizedown"), i18n("Lower Case"), this); action->setIconText(i18n("Lower")); addAction("toLowerCase", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(toLowerCase())); action->setToolTip(i18n("Convert all letters to lower case")); action = new KAction(KIcon("first_letter_upper"), i18n("Convert First Letter to Upper Case"), this); action->setIconText(i18n("First Letter Upper")); addAction("firstLetterToUpperCase", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(firstLetterToUpperCase())); action->setToolTip(i18n("Capitalize the first letter")); action = new KoColorPopupAction(this); action->setIcon(KIcon("format-fill-color")); action->setToolTip(i18n("Set the background color")); action->setText(i18n("Background Color")); addAction("backgroundColor", action); connect(action, SIGNAL(colorChanged(const KoColor &)), this, SLOT(changeBackgroundColor(const KoColor &))); // -- cell merging actions -- action = new KAction(KIcon("mergecell"), i18n("Merge Cells"), this); addAction("mergeCells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeCells())); action->setToolTip(i18n("Merge the selected region")); action = new KAction(KIcon("mergecell-horizontal"), i18n("Merge Cells Horizontally"), this); action->setToolTip(i18n("Merge the selected region horizontally")); addAction("mergeCellsHorizontal", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeCellsHorizontal())); action = new KAction(KIcon("mergecell-vertical"), i18n("Merge Cells Vertically"), this); action->setToolTip(i18n("Merge the selected region vertically")); addAction("mergeCellsVertical", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeCellsVertical())); action = new KAction(KIcon("dissociatecell"), i18n("Dissociate Cells"), this); action->setToolTip(i18n("Unmerge the selected region")); addAction("dissociateCells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(dissociateCells())); // -- column & row actions -- action = new KAction(KIcon("resizecol"), i18n("Resize Column..."), this); addAction("resizeCol", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(resizeColumn())); action->setToolTip(i18n("Change the width of a column")); action = new KAction(KIcon("insert_table_col"), i18n("Columns"), this); action->setIconText(i18n("Insert Columns")); action->setToolTip(i18n("Inserts a new column into the spreadsheet")); addAction("insertColumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertColumn())); action = new KAction(KIcon("delete_table_col"), i18n("Columns"), this); action->setIconText(i18n("Remove Columns")); action->setToolTip(i18n("Removes a column from the spreadsheet")); addAction("deleteColumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteColumn())); action = new KAction(KIcon("hide_table_column"), i18n("Hide Columns"), this); addAction("hideColumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(hideColumn())); action->setToolTip(i18n("Hide the column from this")); action = new KAction(KIcon("show_table_column"), i18n("Show Columns..."), this); addAction("showColumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(slotShowColumnDialog())); action->setToolTip(i18n("Show hidden columns")); action = new KAction(KIcon("adjustcol"), i18n("Equalize Column"), this); addAction("equalizeCol", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(equalizeColumn())); action->setToolTip(i18n("Resizes selected columns to be the same size")); action = new KAction(KIcon("show_sheet_column"), i18n("Show Columns"), this); addAction("showSelColumns", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(showColumn())); action->setToolTip(i18n("Show hidden columns in the selection")); action->setEnabled(false); action = new KAction(KIcon("resizerow"), i18n("Resize Row..."), this); addAction("resizeRow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(resizeRow())); action->setToolTip(i18n("Change the height of a row")); action = new KAction(KIcon("insert_table_row"), i18n("Rows"), this); action->setIconText(i18n("Insert Rows")); action->setToolTip(i18n("Inserts a new row into the spreadsheet")); addAction("insertRow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertRow())); action = new KAction(KIcon("delete_table_row"), i18n("Rows"), this); action->setIconText(i18n("Remove Rows")); action->setToolTip(i18n("Removes a row from the spreadsheet")); addAction("deleteRow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteRow())); action = new KAction(KIcon("hide_table_row"), i18n("Hide Rows"), this); addAction("hideRow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(hideRow())); action->setToolTip(i18n("Hide a row from this")); action = new KAction(KIcon("show_table_row"), i18n("Show Rows..."), this); addAction("showRow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(slotShowRowDialog())); action->setToolTip(i18n("Show hidden rows")); action = new KAction(KIcon("adjustrow"), i18n("Equalize Row"), this); addAction("equalizeRow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(equalizeRow())); action->setToolTip(i18n("Resizes selected rows to be the same size")); action = new KAction(KIcon("show_table_row"), i18n("Show Rows"), this); addAction("showSelRows", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(showRow())); action->setEnabled(false); action->setToolTip(i18n("Show hidden rows in the selection")); action = new KAction(i18n("Adjust Row && Column"), this); addAction("adjust", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(adjust())); action->setToolTip(i18n("Adjusts row/column size so that the contents will fit")); // -- cell insert/remove actions -- action = new KAction(KIcon("insertcell"), i18n("Cells..."), this); action->setIconText(i18n("Insert Cells...")); action->setToolTip(i18n("Insert a blank cell into the spreadsheet")); addAction("insertCell", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertCells())); action = new KAction(KIcon("removecell"), i18n("Cells..."), this); action->setIconText(i18n("Remove Cells...")); action->setToolTip(i18n("Removes the cells from the spreadsheet")); addAction("deleteCell", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteCells())); // -- cell content actions -- action = new KAction(KIcon("deletecell"), i18n("All"), this); action->setIconText(i18n("Clear All")); action->setToolTip(i18n("Clear all contents and formatting of the current cell")); addAction("clearAll", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(clearAll())); action = new KAction(KIcon("edit-clear"), i18n("Contents"), this); action->setIconText(i18n("Clear Contents")); action->setToolTip(i18n("Remove the contents of the current cell")); addAction("clearContents", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(clearContents())); action = new KAction(KIcon("comment"), i18n("Comment..."), this); action->setToolTip(i18n("Edit a comment for this cell")); addAction("comment", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(comment())); action = new KAction(KIcon("removecomment"), i18n("Comment"), this); action->setIconText(i18n("Remove Comment")); action->setToolTip(i18n("Remove this cell's comment")); addAction("clearComment", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(clearComment())); action = new KAction(i18n("Conditional Styles..."), this); action->setToolTip(i18n("Set cell style based on certain conditions")); addAction("conditional", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(conditional())); action = new KAction(i18n("Conditional Styles"), this); action->setIconText(i18n("Remove Conditional Styles")); action->setToolTip(i18n("Remove the conditional cell styles")); addAction("clearConditional", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(clearConditionalStyles())); action = new KAction(KIcon("insert-link"), i18n("&Link..."), this); addAction("insertHyperlink", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertHyperlink())); action->setToolTip(i18n("Insert an Internet hyperlink")); action = new KAction(i18n("Link"), this); action->setIconText(i18n("Remove Link")); action->setToolTip(i18n("Remove a link")); addAction("clearHyperlink", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(clearHyperlink())); action = new KAction(i18n("Validity..."), this); action->setToolTip(i18n("Set tests to confirm cell data is valid")); addAction("validity", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(validity())); action = new KAction(i18n("Validity"), this); action->setIconText(i18n("Remove Validity")); action->setToolTip(i18n("Remove the validity tests on this cell")); addAction("clearValidity", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(clearValidity())); // -- sorting/filtering action -- action = new KAction(i18n("&Sort..."), this); addAction("sort", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(sort())); action->setToolTip(i18n("Sort a group of cells")); action = new KAction(KIcon("this-sort-descending"), i18n("Sort &Decreasing"), this); addAction("sortDec", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(sortDec())); action->setToolTip(i18n("Sort a group of cells in decreasing(last to first) order")); action = new KAction(KIcon("this-sort-ascending"), i18n("Sort &Increasing"), this); addAction("sortInc", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(sortInc())); action->setToolTip(i18n("Sort a group of cells in ascending(first to last) order")); action = new KAction(KIcon("this-filter"), i18n("&Auto-Filter"), this); addAction("autoFilter", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(autoFilter())); action->setToolTip(i18n("Add an automatic filter to a cell range")); // -- fill actions -- action = new KAction(/*KIcon("arrow-left"), */i18n("&Left"), this); addAction("fillLeft", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(fillLeft())); action = new KAction(/*KIcon("arrow-right"), */i18n("&Right"), this); addAction("fillRight", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(fillRight())); action = new KAction(/*KIcon("arrow-up"), */i18n("&Up"), this); addAction("fillUp", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(fillUp())); action = new KAction(/*KIcon("arrow-down"), */i18n("&Down"), this); addAction("fillDown", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(fillDown())); action = new KAction(KIcon("black_sum"), i18n("Autosum"), this); addAction("autoSum", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(autoSum())); action->setToolTip(i18n("Insert the 'sum' function")); // -- data insert actions -- action = new KAction(KIcon("series"), i18n("&Series..."), this); addAction("insertSeries", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertSeries())); action ->setToolTip(i18n("Insert a series")); action = new KAction(KIcon("funct"), i18n("&Function..."), this); addAction("insertFormula", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertFormula())); action->setToolTip(i18n("Insert math expression")); action = new KAction(KIcon("accessories-character-map"), i18n("S&pecial Character..."), this); addAction("insertSpecialChar", action); action->setToolTip(i18n("Insert one or more symbols or letters not found on the keyboard")); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertSpecialChar())); #ifndef QT_NO_SQL action = new KAction(KIcon("network-server-database"), i18n("From &Database..."), this); action->setIconText(i18n("Database")); addAction("insertFromDatabase", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertFromDatabase())); action->setToolTip(i18n("Insert data from a SQL database")); #endif action = new KAction(KIcon("text-plain"), i18n("From &Text File..."), this); action->setIconText(i18n("Text File")); addAction("insertFromTextfile", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertFromTextfile())); action->setToolTip(i18n("Insert data from a text file to the current cursor position/selection")); action = new KAction(KIcon("klipper"), i18n("From &Clipboard..."), this); action->setIconText(i18n("Clipboard")); addAction("insertFromClipboard", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertFromClipboard())); action->setToolTip(i18n("Insert CSV data from the clipboard to the current cursor position/selection")); action = new KAction(i18n("&Text to Columns..."), this); addAction("textToColumns", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(textToColumns())); action->setToolTip(i18n("Expand the content of cells to multiple columns")); action = new KAction(i18n("Custom Lists..."), this); addAction("sortList", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(sortList())); action->setToolTip(i18n("Create custom lists for sorting or autofill")); action = new KAction(i18n("&Consolidate..."), this); addAction("consolidate", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(consolidate())); action->setToolTip(i18n("Create a region of summary data from a group of similar regions")); action = new KAction(i18n("&Goal Seek..."), this); addAction("goalSeek", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(goalSeek())); action->setToolTip(i18n("Repeating calculation to find a specific value")); action = new KAction(i18n("&Subtotals..."), this); addAction("subtotals", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(subtotals())); action->setToolTip(i18n("Create different kind of subtotals to a list or database")); action = new KAction(i18n("Area Name..."), this); addAction("setAreaName", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(setAreaName())); action->setToolTip(i18n("Set a name for a region of the spreadsheet")); action = new KAction(i18n("Named Areas..."), this); action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G)); action->setIconText(i18n("Named Areas")); action->setIcon(KIcon("bookmarks")); action->setToolTip(i18n("Edit or select named areas")); addAction("namedAreaDialog", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(namedAreaDialog())); action = new KSelectAction(i18n("Formula Selection"), this); addAction("formulaSelection", action); action->setToolTip(i18n("Insert a function")); QStringList functionList; functionList.append("SUM"); functionList.append("AVERAGE"); functionList.append("IF"); functionList.append("COUNT"); functionList.append("MIN"); functionList.append("MAX"); functionList.append(i18n("Others...")); static_cast(action)->setItems(functionList); static_cast(action)->setComboWidth(80); static_cast(action)->setCurrentItem(0); connect(action, SIGNAL(triggered(const QString&)), this, SLOT(formulaSelection(const QString&))); // -- general editing actions -- action = new KAction(KIcon("cell_edit"), i18n("Modify Cell"), this); addAction("editCell", action); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); connect(action, SIGNAL(triggered(bool)), this, SLOT(edit())); action->setToolTip(i18n("Edit the highlighted cell")); action = KStandardAction::cut(this, SLOT(cut()), this); action->setToolTip(i18n("Move the cell object to the clipboard")); addAction("cut", action); action = KStandardAction::copy(this, SLOT(copy()), this); action->setToolTip(i18n("Copy the cell object to the clipboard")); addAction("copy", action); action = KStandardAction::paste(this, SLOT(paste()), this); action->setToolTip(i18n("Paste the contents of the clipboard at the cursor")); addAction("paste", action); action = new KAction(KIcon("special_paste"), i18n("Special Paste..."), this); addAction("specialPaste", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(specialPaste())); action->setToolTip(i18n("Paste the contents of the clipboard with special options")); action = new KAction(KIcon("insertcellcopy"), i18n("Paste with Insertion"), this); addAction("pasteWithInsertion", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(pasteWithInsertion())); action->setToolTip(i18n("Inserts a cell from the clipboard into the spreadsheet")); action = KStandardAction::selectAll(this, SLOT(selectAll()), this); action->setToolTip(i18n("Selects all cells in the current sheet")); addAction("selectAll", action); action = KStandardAction::find(this, SLOT(find()), this); addAction("edit_find", action); action = KStandardAction::findNext(this, SLOT(findNext()), this); addAction("edit_find_next", action); action = KStandardAction::findPrev(this, SLOT(findPrevious()), this); addAction("edit_find_last", action); action = KStandardAction::replace(this, SLOT(replace()), this); addAction("edit_replace", action); // -- misc actions -- action = new KAction(KIcon("go-jump"), i18n("Goto Cell..."), this); action->setIconText(i18n("Goto")); action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_G)); addAction("gotoCell", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(gotoCell())); action->setToolTip(i18n("Move to a particular cell")); action = KStandardAction::spelling(this, SLOT(spellCheck()), this); action->setToolTip(i18n("Check the spelling")); addAction("tools_spelling", action); action = new KAction(KIcon("inspector"), i18n("Run Inspector..."), this); addAction("inspector", action); action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); connect(action, SIGNAL(triggered(bool)), this, SLOT(inspector())); #ifndef NDEBUG action = new KAction(KIcon("table"), i18n("Show QTableView..."), this); addAction("qTableView", action); action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_T)); connect(action, SIGNAL(triggered(bool)), this, SLOT(qTableView())); #endif action = new KAction(i18n("Auto-Format..."), this); addAction("sheetFormat", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(sheetFormat())); action->setToolTip(i18n("Set the worksheet formatting")); action = new KAction(i18n("Document Settings..."), this); addAction("documentSettingsDialog", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(documentSettingsDialog())); action->setToolTip(i18n("Show document settings dialog")); action = new KToggleAction(i18n("Break Before Column"), this); addAction("format_break_before_column", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(breakBeforeColumn(bool))); action->setIconText(i18n("Column Break")); action->setToolTip(i18n("Set a manual page break before the column")); action = new KToggleAction(i18n("Break Before Row"), this); addAction("format_break_before_row", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(breakBeforeRow(bool))); action->setIconText(i18n("Row Break")); action->setToolTip(i18n("Set a manual page break before the row")); } CellToolBase::~CellToolBase() { delete d->formulaDialog; delete d->popupListChoose; qDeleteAll(d->popupMenuActions); qDeleteAll(actions()); delete d; } void CellToolBase::paint(QPainter &painter, const KoViewConverter &viewConverter) { KoShape::applyConversion(painter, viewConverter); painter.translate(offset()); // the table shape offset const QRectF paintRect = QRectF(QPointF(), size()); /* paint the selection */ d->paintReferenceSelection(painter, paintRect); d->paintSelection(painter, paintRect); } void CellToolBase::paintReferenceSelection(QPainter &painter, const QRectF &paintRect) { d->paintReferenceSelection(painter, paintRect); } void CellToolBase::paintSelection(QPainter &painter, const QRectF &paintRect) { d->paintSelection(painter, paintRect); } void CellToolBase::mousePressEvent(KoPointerEvent* event) { KoInteractionTool::mousePressEvent(event); } void CellToolBase::mouseMoveEvent(KoPointerEvent* event) { // Special handling for drag'n'drop. if (dynamic_cast(currentStrategy())) { KoInteractionTool::mouseMoveEvent(event); return; } // Indicators are not necessary if there's a strategy. if (currentStrategy()) { return KoInteractionTool::mouseMoveEvent(event); } Sheet *const sheet = selection()->activeSheet(); // Get info about where the event occurred. QPointF position = event->point - offset(); // the shape offset, not the scrolling one. // Diagonal cursor, if the selection handle was hit. if (SelectionStrategy::hitTestReferenceSizeGrip(canvas(), selection(), position) || SelectionStrategy::hitTestSelectionSizeGrip(canvas(), selection(), position)) { if (selection()->activeSheet()->layoutDirection() == Qt::RightToLeft) { useCursor(Qt::SizeBDiagCursor); } else { useCursor(Qt::SizeFDiagCursor); } return KoInteractionTool::mouseMoveEvent(event); } // Hand cursor, if the selected area was hit. if (!selection()->referenceSelectionMode()) { const Region::ConstIterator end(selection()->constEnd()); for (Region::ConstIterator it(selection()->constBegin()); it != end; ++it) { const QRect range = (*it)->rect(); if (sheet->cellCoordinatesToDocument(range).contains(position)) { useCursor(Qt::PointingHandCursor); return KoInteractionTool::mouseMoveEvent(event); } } } // In which cell did the user click? double xpos; double ypos; const int col = this->selection()->activeSheet()->leftColumn(position.x(), xpos); const int row = this->selection()->activeSheet()->topRow(position.y(), ypos); // Check boundaries. if (col > maxCol() || row > maxRow()) { kDebug(36005) << "col or row is out of range:" << "col:" << col << " row:" << row; } else { const Cell cell = Cell(selection()->activeSheet(), col, row).masterCell(); SheetView* const sheetView = this->sheetView(selection()->activeSheet()); QString url; const CellView cellView = sheetView->cellView(col, row); if (selection()->activeSheet()->layoutDirection() == Qt::RightToLeft) { url = cellView.testAnchor(cell, cell.width() - position.x() + xpos, position.y() - ypos); } else { url = cellView.testAnchor(cell, position.x() - xpos, position.y() - ypos); } if (!url.isEmpty()) { useCursor(Qt::PointingHandCursor); return KoInteractionTool::mouseMoveEvent(event); } } // Reset to normal cursor. useCursor(Qt::ArrowCursor); KoInteractionTool::mouseMoveEvent(event); } void CellToolBase::mouseReleaseEvent(KoPointerEvent* event) { KoInteractionTool::mouseReleaseEvent(event); scrollToCell(selection()->cursor()); } void CellToolBase::mouseDoubleClickEvent(KoPointerEvent* event) { Q_UNUSED(event) cancelCurrentStrategy(); scrollToCell(selection()->cursor()); createEditor(false /* keep content */); } void CellToolBase::keyPressEvent(QKeyEvent* event) { register Sheet * const sheet = selection()->activeSheet(); if (!sheet) { return; } // Don't handle the remaining special keys. if (event->modifiers() & (Qt::AltModifier | Qt::ControlModifier) && (event->key() != Qt::Key_Down) && (event->key() != Qt::Key_Up) && (event->key() != Qt::Key_Right) && (event->key() != Qt::Key_Left) && (event->key() != Qt::Key_Home) && (event->key() != Qt::Key_Enter) && (event->key() != Qt::Key_Return)) { event->ignore(); // QKeyEvent return; } // Check for formatting key combination CTRL + ... // Qt::Key_Exclam, Qt::Key_At, Qt::Key_Ampersand, Qt::Key_Dollar // Qt::Key_Percent, Qt::Key_AsciiCircum, Qt::Key_NumberSign if (d->formatKeyPress(event)) { return; } #if 0 // TODO move this to the contextMenuEvent of the view. // keyPressEvent() is not called with the contextMenuKey, // it's handled separately by Qt. if (event->key() == KGlobalSettings::contextMenuKey()) { int row = d->canvas->selection()->marker().y(); int col = d->canvas->selection()->marker().x(); QPointF p(sheet->columnPosition(col), sheet->rowPosition(row)); d->canvas->view()->openPopupMenu(d->canvas->mapToGlobal(p.toPoint())); } #endif switch (event->key()) { case Qt::Key_Return: case Qt::Key_Enter: d->processEnterKey(event); return; break; case Qt::Key_Down: case Qt::Key_Up: case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Tab: /* a tab behaves just like a right/left arrow */ case Qt::Key_Backtab: /* and so does Shift+Tab */ if (event->modifiers() & Qt::ControlModifier) { if (!d->processControlArrowKey(event)) return; } else { d->processArrowKey(event); return; } break; case Qt::Key_Escape: d->processEscapeKey(event); return; break; case Qt::Key_Home: if (!d->processHomeKey(event)) return; break; case Qt::Key_End: if (!d->processEndKey(event)) return; break; case Qt::Key_PageUp: /* Page Up */ if (!d->processPriorKey(event)) return; break; case Qt::Key_PageDown: /* Page Down */ if (!d->processNextKey(event)) return; break; case Qt::Key_Delete: d->processDeleteKey(event); return; break; case Qt::Key_F2: d->processF2Key(event); return; break; case Qt::Key_F4: d->processF4Key(event); return; break; default: d->processOtherKey(event); return; break; } } void CellToolBase::inputMethodEvent(QInputMethodEvent * event) { - editor()->handleInputMethodEvent(event); + // Send it to the embedded editor. + if (editor()) { + QApplication::sendEvent(editor(), event); + } } void CellToolBase::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); Q_UNUSED(shapes); if (!d->initialized) { init(); d->initialized = true; } useCursor(Qt::ArrowCursor); // paint the selection rectangle selection()->update(); // Initialize cell style selection action. const StyleManager* styleManager = selection()->activeSheet()->map()->styleManager(); static_cast(this->action("setStyle"))->setItems(styleManager->styleNames()); // Establish connections. connect(selection(), SIGNAL(changed(const Region&)), this, SLOT(selectionChanged(const Region&))); connect(selection(), SIGNAL(closeEditor(bool, bool)), this, SLOT(deleteEditor(bool, bool))); connect(selection(), SIGNAL(modified(const Region&)), this, SLOT(updateEditor())); connect(selection(), SIGNAL(activeSheetChanged(Sheet*)), this, SLOT(activeSheetChanged(Sheet*))); connect(selection(), SIGNAL(requestFocusEditor()), this, SLOT(focusEditorRequested())); connect(selection(), SIGNAL(documentReadWriteToggled(bool)), this, SLOT(documentReadWriteToggled(bool))); connect(selection(), SIGNAL(sheetProtectionToggled(bool)), this, SLOT(sheetProtectionToggled(bool))); } void CellToolBase::deactivate() { // Disconnect. disconnect(selection(), 0, this, 0); // close the cell editor deleteEditor(true); // save changes // clear the selection rectangle selection()->update(); } void CellToolBase::init() { } QWidget* CellToolBase::createOptionWidget() { d->optionWidget = new CellToolOptionWidget(this); connect(selection()->activeSheet()->map()->namedAreaManager(), SIGNAL(namedAreaAdded(const QString&)), d->optionWidget->locationComboBox(), SLOT(slotAddAreaName(const QString&))); connect(selection()->activeSheet()->map()->namedAreaManager(), SIGNAL(namedAreaRemoved(const QString&)), d->optionWidget->locationComboBox(), SLOT(slotRemoveAreaName(const QString&))); selection()->update(); // initialize the location combobox return d->optionWidget; } KoInteractionStrategy* CellToolBase::createStrategy(KoPointerEvent* event) { // Get info about where the event occurred. QPointF position = event->point - offset(); // the shape offset, not the scrolling one. // Autofilling or merging, if the selection handle was hit. if (SelectionStrategy::hitTestSelectionSizeGrip(canvas(), selection(), position)) { if (event->button() == Qt::LeftButton) return new AutoFillStrategy(this, event->point, event->modifiers()); else if (event->button() == Qt::MidButton) return new MergeStrategy(this, event->point, event->modifiers()); } // Pasting with the middle mouse button. if (event->button() == Qt::MidButton) { return new PasteStrategy(this, event->point, event->modifiers()); } // Check, if the selected area was hit. bool hitSelection = false; Region::ConstIterator end = selection()->constEnd(); for (Region::ConstIterator it = selection()->constBegin(); it != end; ++it) { const QRect range = (*it)->rect(); if (selection()->activeSheet()->cellCoordinatesToDocument(range).contains(position)) { // Context menu with the right mouse button. if (event->button() == Qt::RightButton) { // Setup the context menu. setPopupActionList(d->popupActionList()); event->ignore(); return 0; // Act directly; no further strategy needed. } hitSelection = true; break; } } // In which cell did the user click? double xpos; double ypos; const int col = this->selection()->activeSheet()->leftColumn(position.x(), xpos); const int row = this->selection()->activeSheet()->topRow(position.y(), ypos); // Check boundaries. if (col > maxCol() || row > maxRow()) { kDebug(36005) << "col or row is out of range:" << "col:" << col << " row:" << row; } else { // Context menu with the right mouse button. if (event->button() == Qt::RightButton) { selection()->initialize(QPoint(col, row), selection()->activeSheet()); // Setup the context menu. setPopupActionList(d->popupActionList()); event->ignore(); return 0; // Act directly; no further strategy needed. } else { const Cell cell = Cell(selection()->activeSheet(), col, row).masterCell(); SheetView* const sheetView = this->sheetView(selection()->activeSheet()); // Filter button hit. const double offsetX = canvas()->canvasController()->canvasOffsetX(); const double offsetY = canvas()->canvasController()->canvasOffsetY(); const QPointF p1 = QPointF(xpos, ypos) - offset(); // the shape offset, not the scrolling one. const QSizeF s1(cell.width(), cell.height()); const QRectF cellRect = canvas()->viewConverter()->documentToView(QRectF(p1, s1)); const QRect cellViewRect = cellRect.translated(offsetX, offsetY).toRect(); if (sheetView->cellView(col, row).hitTestFilterButton(cell, cellViewRect, event->pos())) { Database database = cell.database(); database.showPopup(canvas()->canvasWidget(), cell, cellViewRect); return 0; // Act directly; no further strategy needed. } // Hyperlink hit. QString url; const CellView cellView = sheetView->cellView(col, row); if (selection()->activeSheet()->layoutDirection() == Qt::RightToLeft) { url = cellView.testAnchor(cell, cell.width() - position.x() + xpos, position.y() - ypos); } else { url = cellView.testAnchor(cell, position.x() - xpos, position.y() - ypos); } if (!url.isEmpty()) { return new HyperlinkStrategy(this, event->point, event->modifiers(), url, cellView.textRect()); } } } // Drag & drop, if the selected area was hit. const bool controlPressed = event->modifiers() & Qt::ControlModifier; if (hitSelection && !controlPressed && !selection()->referenceSelectionMode()) { return new DragAndDropStrategy(this, event->point, event->modifiers()); } return new SelectionStrategy(this, event->point, event->modifiers()); } void CellToolBase::selectionChanged(const Region& region) { Q_UNUSED(region); if (!d->optionWidget) { return; } // Update the editor, if the reference selection is enabled. if (editor() && selection()->referenceSelectionMode()) { - editor()->updateChoice(); + // First, update the formula expression. This will send a signal with + // the new expression to the external editor, which does not have focus + // yet (the canvas has). If it would have, it would also send a signal + // to inform the embedded editor about a changed text. + editor()->selectionChanged(); + // Focus the embedded or external editor after updating the expression. + focusEditorRequested(); return; } // State of manual page breaks before columns/rows. bool columnBreakChecked = false; bool columnBreakEnabled = false; bool rowBreakChecked = false; bool rowBreakEnabled = false; const Region::ConstIterator end(selection()->constEnd()); for (Region::ConstIterator it = selection()->constBegin(); it != end; ++it) { const Sheet *const sheet = (*it)->sheet(); const QRect range = (*it)->rect(); const int column = range.left(); const int row = range.top(); columnBreakChecked |= sheet->columnFormat(column)->hasPageBreak(); columnBreakEnabled |= (column != 1); rowBreakChecked |= sheet->rowFormat(row)->hasPageBreak(); rowBreakEnabled |= (row != 1); } action("format_break_before_column")->setChecked(columnBreakChecked); action("format_break_before_column")->setEnabled(columnBreakEnabled); action("format_break_before_row")->setChecked(rowBreakChecked); action("format_break_before_row")->setEnabled(rowBreakEnabled); const Cell cell = Cell(selection()->activeSheet(), selection()->cursor()); if (!cell) { return; } d->updateEditor(cell); d->updateActions(cell); if (selection()->activeSheet()->isProtected()) { const Style style = cell.style(); if (style.notProtected()) { if (selection()->isSingular()) { if (!action("bold")->isEnabled()) { d->setProtectedActionsEnabled(true); } } else { // more than one cell if (action("bold")->isEnabled()) { d->setProtectedActionsEnabled(false); } } } else { if (action("bold")->isEnabled()) { d->setProtectedActionsEnabled(false); } } } } void CellToolBase::scrollToCell(const QPoint &location) { Sheet *const sheet = selection()->activeSheet(); // Adjust the maximum accessed column and row for the scrollbars. sheetView(sheet)->updateAccessedCellRange(location); // The cell geometry expanded by some pixels in each direction. const Cell cell = Cell(sheet, location).masterCell(); const double xpos = sheet->columnPosition(cell.cellPosition().x()); const double ypos = sheet->rowPosition(cell.cellPosition().y()); const double pixelWidth = canvas()->viewConverter()->viewToDocumentX(1); const double pixelHeight = canvas()->viewConverter()->viewToDocumentY(1); QRectF rect(xpos, ypos, cell.width(), cell.height()); rect.adjust(-2*pixelWidth, -2*pixelHeight, +2*pixelWidth, +2*pixelHeight); rect = rect & QRectF(QPointF(0.0, 0.0), sheet->documentSize()); // Scroll to cell. canvas()->canvasController()->ensureVisible(rect, true); } CellEditor* CellToolBase::editor() const { return d->cellEditor; } void CellToolBase::setLastEditorWithFocus(Editor editor) { d->lastEditorWithFocus = editor; } bool CellToolBase::createEditor(bool clear, bool focus) { const Cell cell(selection()->activeSheet(), selection()->marker()); if (selection()->activeSheet()->isProtected() && !cell.style().notProtected()) return false; if (!editor()) { d->cellEditor = new CellEditor(this, canvas()->canvasWidget()); d->cellEditor->setEditorFont(cell.style().font(), true, canvas()->viewConverter()); connect(d->cellEditor, SIGNAL(textChanged(const QString &)), d->optionWidget->editor(), SLOT(setText(const QString &))); connect(d->optionWidget->editor(), SIGNAL(textChanged(const QString &)), d->cellEditor, SLOT(setText(const QString &))); d->optionWidget->applyButton()->setEnabled(true); d->optionWidget->cancelButton()->setEnabled(true); double w = cell.width(); double h = cell.height(); double min_w = cell.width(); double min_h = cell.height(); double xpos = selection()->activeSheet()->columnPosition(selection()->marker().x()); xpos += canvas()->viewConverter()->viewToDocumentX(canvas()->canvasController()->canvasOffsetX()); Qt::LayoutDirection sheetDir = selection()->activeSheet()->layoutDirection(); bool rtlText = cell.displayText().isRightToLeft(); // if sheet and cell direction don't match, then the editor's location // needs to be shifted backwards so that it's right above the cell's text if (w > 0 && ((sheetDir == Qt::RightToLeft && !rtlText) || (sheetDir == Qt::LeftToRight && rtlText))) xpos -= w - min_w; // paint editor above correct cell if sheet direction is RTL if (sheetDir == Qt::RightToLeft) { double dwidth = canvas()->viewConverter()->viewToDocumentX(canvas()->canvasWidget()->width()); double w2 = qMax(w, min_w); xpos = dwidth - w2 - xpos; } double ypos = selection()->activeSheet()->rowPosition(selection()->marker().y()); ypos += canvas()->viewConverter()->viewToDocumentY(canvas()->canvasController()->canvasOffsetY()); // Setup the editor's palette. const Style style = cell.effectiveStyle(); QPalette editorPalette(editor()->palette()); QColor color = style.fontColor(); if (!color.isValid()) color = canvas()->canvasWidget()->palette().text().color(); editorPalette.setColor(QPalette::Text, color); color = style.backgroundColor(); if (!color.isValid()) color = editorPalette.base().color(); editorPalette.setColor(QPalette::Background, color); editor()->setPalette(editorPalette); // apply (table shape) offset xpos += offset().x(); ypos += offset().y(); const QRectF rect(xpos + 0.5, ypos + 0.5, w - 0.5, h - 0.5); //needed to circumvent rounding issue with height/width const QRectF zoomedRect = canvas()->viewConverter()->documentToView(rect); editor()->setGeometry(zoomedRect.toRect().adjusted(1, 1, -1, -1)); editor()->setMinimumSize(QSize((int)canvas()->viewConverter()->documentToViewX(min_w) - 1, (int)canvas()->viewConverter()->documentToViewY(min_h) - 1)); editor()->show(); // Laurent 2001-12-05 // Don't add focus when we create a new editor and // we select text in edit widget otherwise we don't delete // selected text. if (focus) editor()->setFocus(); // clear the selection rectangle selection()->update(); } if (!clear && !cell.isNull()) editor()->setText(cell.userInput()); return true; } void CellToolBase::deleteEditor(bool saveChanges, bool expandMatrix) { if (!d->cellEditor) { return; } + const QString userInput = editor()->toPlainText(); editor()->hide(); // Delete the cell editor first and after that update the document. // That means we get a synchronous repaint after the cell editor // widget is gone. Otherwise we may get painting errors. delete d->cellEditor; d->cellEditor = 0; delete d->formulaDialog; d->formulaDialog = 0; if (saveChanges) { - applyUserInput(expandMatrix); + applyUserInput(userInput, expandMatrix); } else { selection()->update(); } d->optionWidget->applyButton()->setEnabled(false); d->optionWidget->cancelButton()->setEnabled(false); canvas()->canvasWidget()->setFocus(); } void CellToolBase::activeSheetChanged(Sheet* sheet) { #ifdef NDEBUG Q_UNUSED(sheet); #else Q_ASSERT(selection()->activeSheet() == sheet); #endif if (!selection()->referenceSelectionMode()) { return; } if (editor()) { if (selection()->originSheet() != selection()->activeSheet()) { editor()->hide(); } else { editor()->show(); } - editor()->updateChoice(); } + focusEditorRequested(); } void CellToolBase::updateEditor() { if (!d->optionWidget) { return; } const Cell cell = Cell(selection()->activeSheet(), selection()->cursor()); if (!cell) { return; } d->updateEditor(cell); } void CellToolBase::focusEditorRequested() { + // Nothing to do, if not in editing mode. + if (!editor()) { + return; + } // If we are in editing mode, we redirect the focus to the CellEditor or ExternalEditor. // This screws up though (David) - if (editor()) { + if (selection()->originSheet() != selection()->activeSheet()) { + // Always focus the external editor, if not on the origin sheet. + d->optionWidget->editor()->setFocus(); + } else { + // Focus the last active editor, if on the origin sheet. if (d->lastEditorWithFocus == EmbeddedEditor) { editor()->setFocus(); } else { d->optionWidget->editor()->setFocus(); } } } -void CellToolBase::applyUserInput(bool expandMatrix) +void CellToolBase::applyUserInput(const QString &userInput, bool expandMatrix) { - QString text = d->optionWidget->editor()->toPlainText(); + QString text = userInput; if (!text.isEmpty() && text.at(0) == '=') { //a formula int openParenthese = text.count('('); int closeParenthese = text.count(')'); int diff = qAbs(openParenthese - closeParenthese); if (openParenthese > closeParenthese) { for (int i = 0; i < diff;i++) { text += ')'; } } } DataManipulator* command = new DataManipulator(); command->setSheet(selection()->activeSheet()); command->setValue(Value(text)); command->setParsing(true); command->setExpandMatrix(expandMatrix); command->add(expandMatrix ? *selection() : Region(selection()->cursor(), selection()->activeSheet())); command->execute(canvas()); if (expandMatrix && selection()->isSingular()) selection()->initialize(*command); Cell cell = Cell(selection()->activeSheet(), selection()->marker()); if (cell.value().isString() && !text.isEmpty() && !text.at(0).isDigit() && !cell.isFormula()) { selection()->activeSheet()->map()->addStringCompletion(text); } } void CellToolBase::documentReadWriteToggled(bool readWrite) { if (!d->optionWidget) { return; } d->setProtectedActionsEnabled(readWrite); } void CellToolBase::sheetProtectionToggled(bool protect) { if (!d->optionWidget) { return; } d->setProtectedActionsEnabled(!protect); } void CellToolBase::cellStyle() { CellFormatDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::setDefaultStyle() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setDefault(); command->add(*selection()); command->execute(canvas()); } void CellToolBase::styleDialog() { Map* const map = selection()->activeSheet()->map(); StyleManager* const styleManager = map->styleManager(); StyleManagerDialog dialog(canvas()->canvasWidget(), selection(), styleManager); dialog.exec(); static_cast(action("setStyle"))->setItems(styleManager->styleNames()); if (selection()->activeSheet()) map->addDamage(new CellDamage(selection()->activeSheet(), Region(1, 1, maxCol(), maxRow()), CellDamage::Appearance)); canvas()->canvasWidget()->update(); } void CellToolBase::setStyle(const QString& stylename) { kDebug() << "CellToolBase::setStyle(" << stylename << ")"; if (selection()->activeSheet()->map()->styleManager()->style(stylename)) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setParentName(stylename); command->add(*selection()); command->execute(canvas()); } } void CellToolBase::createStyleFromCell() { QPoint p(selection()->marker()); Cell cell = Cell(selection()->activeSheet(), p.x(), p.y()); bool ok = false; QString styleName(""); while (true) { styleName = KInputDialog::getText(i18n("Create Style From Cell"), i18n("Enter name:"), styleName, &ok, canvas()->canvasWidget()); if (!ok) // User pushed an OK button. return; styleName = styleName.trimmed(); if (styleName.length() < 1) { KMessageBox::sorry(canvas()->canvasWidget(), i18n("The style name cannot be empty.")); continue; } if (selection()->activeSheet()->map()->styleManager()->style(styleName) != 0) { KMessageBox::sorry(canvas()->canvasWidget(), i18n("A style with this name already exists.")); continue; } break; } const Style cellStyle = cell.style(); CustomStyle* style = new CustomStyle(styleName); style->merge(cellStyle); selection()->activeSheet()->map()->styleManager()->insertStyle(style); cell.setStyle(*style); QStringList functionList(static_cast(action("setStyle"))->items()); functionList.push_back(styleName); static_cast(action("setStyle"))->setItems(functionList); } void CellToolBase::bold(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontBold(enable); command->add(*selection()); command->execute(canvas()); if (editor()) { const Cell cell = Cell(selection()->activeSheet(), selection()->marker()); editor()->setEditorFont(cell.style().font(), true, canvas()->viewConverter()); } } void CellToolBase::underline(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontUnderline(enable); command->add(*selection()); command->execute(canvas()); if (editor()) { const Cell cell = Cell(selection()->activeSheet(), selection()->marker()); editor()->setEditorFont(cell.style().font(), true, canvas()->viewConverter()); } } void CellToolBase::strikeOut(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontStrike(enable); command->add(*selection()); command->execute(canvas()); if (editor()) { const Cell cell = Cell(selection()->activeSheet(), selection()->marker()); editor()->setEditorFont(cell.style().font(), true, canvas()->viewConverter()); } } void CellToolBase::italic(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontItalic(enable); command->add(*selection()); command->execute(canvas()); if (editor()) { const Cell cell = Cell(selection()->activeSheet(), selection()->marker()); editor()->setEditorFont(cell.style().font(), true, canvas()->viewConverter()); } } void CellToolBase::font(const QString& font) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontFamily(font.toLatin1()); command->add(*selection()); command->execute(canvas()); // Don't leave the focus in the toolbars combo box ... if (editor()) { const Style style = Cell(selection()->activeSheet(), selection()->marker()).style(); editor()->setEditorFont(style.font(), true, canvas()->viewConverter()); - editor()->setFocus(); + focusEditorRequested(); } else { canvas()->canvasWidget()->setFocus(); } } void CellToolBase::fontSize(int size) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontSize(size); command->add(*selection()); command->execute(canvas()); // Don't leave the focus in the toolbars combo box ... if (editor()) { const Cell cell(selection()->activeSheet(), selection()->marker()); editor()->setEditorFont(cell.style().font(), true, canvas()->viewConverter()); - editor()->setFocus(); + focusEditorRequested(); } else { canvas()->canvasWidget()->setFocus(); } } void CellToolBase::increaseFontSize() { const Style style = Cell(selection()->activeSheet(), selection()->marker()).style(); const int size = style.fontSize(); StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontSize(size + 1); command->add(*selection()); command->execute(canvas()); } void CellToolBase::decreaseFontSize() { const Style style = Cell(selection()->activeSheet(), selection()->marker()).style(); const int size = style.fontSize(); StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Font")); command->setFontSize(size - 1); command->add(*selection()); command->execute(canvas()); } void CellToolBase::changeTextColor(const KoColor &color) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Text Color")); command->setFontColor(color.toQColor()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::alignLeft(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Horizontal Alignment")); command->setHorizontalAlignment(enable ? Style::Left : Style::HAlignUndefined); command->add(*selection()); command->execute(canvas()); } void CellToolBase::alignRight(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Horizontal Alignment")); command->setHorizontalAlignment(enable ? Style::Right : Style::HAlignUndefined); command->add(*selection()); command->execute(canvas()); } void CellToolBase::alignCenter(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Horizontal Alignment")); command->setHorizontalAlignment(enable ? Style::Center : Style::HAlignUndefined); command->add(*selection()); command->execute(canvas()); } void CellToolBase::alignTop(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Vertical Alignment")); command->setVerticalAlignment(enable ? Style::Top : Style::VAlignUndefined); command->add(*selection()); command->execute(canvas()); } void CellToolBase::alignBottom(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Vertical Alignment")); command->setVerticalAlignment(enable ? Style::Bottom : Style::VAlignUndefined); command->add(*selection()); command->execute(canvas()); } void CellToolBase::alignMiddle(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Vertical Alignment")); command->setVerticalAlignment(enable ? Style::Middle : Style::VAlignUndefined); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderLeft() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Border")); if (selection()->activeSheet()->layoutDirection() == Qt::RightToLeft) command->setRightBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); else command->setLeftBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderRight() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Border")); if (selection()->activeSheet()->layoutDirection() == Qt::RightToLeft) command->setLeftBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); else command->setRightBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderTop() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Border")); command->setTopBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderBottom() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Border")); command->setBottomBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderAll() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Border")); command->setTopBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setBottomBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setLeftBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setRightBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setHorizontalPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setVerticalPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderRemove() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Border")); command->setTopBorderPen(QPen(Qt::NoPen)); command->setBottomBorderPen(QPen(Qt::NoPen)); command->setLeftBorderPen(QPen(Qt::NoPen)); command->setRightBorderPen(QPen(Qt::NoPen)); command->setHorizontalPen(QPen(Qt::NoPen)); command->setVerticalPen(QPen(Qt::NoPen)); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderOutline() { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Border")); command->setTopBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setBottomBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setLeftBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setRightBorderPen(QPen(canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->add(*selection()); command->execute(canvas()); } void CellToolBase::borderColor(const KoColor &color) { BorderColorCommand* command = new BorderColorCommand(); command->setSheet(selection()->activeSheet()); command->setColor(color.toQColor()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::wrapText(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Wrap Text")); command->setMultiRow(enable); command->setVerticalText(false); command->setAngle(0); command->add(*selection()); command->execute(canvas()); } void CellToolBase::verticalText(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Vertical Text")); command->setVerticalText(enable); command->setMultiRow(false); command->setAngle(0); command->add(*selection()); command->execute(canvas()); } void CellToolBase::increaseIndentation() { IndentationCommand* command = new IndentationCommand(); command->setSheet(selection()->activeSheet()); command->add(*selection()); if (!command->execute()) delete command; } void CellToolBase::decreaseIndentation() { IndentationCommand* command = new IndentationCommand(); command->setSheet(selection()->activeSheet()); command->setReverse(true); command->add(*selection()); if (!command->execute()) delete command; } void CellToolBase::changeAngle() { AngleDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::percent(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Format Percent")); command->setFormatType(enable ? Format::Percentage : Format::Generic); command->add(*selection()); command->execute(canvas()); } void CellToolBase::currency(bool enable) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Format Money")); command->setFormatType(enable ? Format::Money : Format::Generic); command->setPrecision(enable ? selection()->activeSheet()->map()->calculationSettings()->locale()->fracDigits() : 0); command->add(*selection()); command->execute(canvas()); } void CellToolBase::increasePrecision() { PrecisionCommand* command = new PrecisionCommand(); command->setSheet(selection()->activeSheet()); command->add(*selection()); if (!command->execute()) delete command; } void CellToolBase::decreasePrecision() { PrecisionCommand* command = new PrecisionCommand(); command->setSheet(selection()->activeSheet()); command->setReverse(true); command->add(*selection()); if (!command->execute()) delete command; } void CellToolBase::toUpperCase() { CaseManipulator* command = new CaseManipulator; command->setSheet(selection()->activeSheet()); command->setText(i18n("Switch to uppercase")); command->changeMode(CaseManipulator::Upper); command->add(*selection()); command->execute(canvas()); } void CellToolBase::toLowerCase() { CaseManipulator* command = new CaseManipulator; command->setSheet(selection()->activeSheet()); command->setText(i18n("Switch to lowercase")); command->changeMode(CaseManipulator::Lower); command->add(*selection()); command->execute(canvas()); } void CellToolBase::firstLetterToUpperCase() { CaseManipulator* command = new CaseManipulator; command->setSheet(selection()->activeSheet()); command->setText(i18n("First letter uppercase")); command->changeMode(CaseManipulator::FirstUpper); command->add(*selection()); command->execute(canvas()); } void CellToolBase::changeBackgroundColor(const KoColor &color) { StyleCommand* command = new StyleCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Change Background Color")); command->setBackgroundColor(color.toQColor()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::mergeCells() { // sanity check if (selection()->activeSheet()->isProtected()) { return; } if (selection()->activeSheet()->map()->isProtected()) { return; } MergeCommand* const command = new MergeCommand(); command->setSheet(selection()->activeSheet()); command->setSelection(selection()); command->setHorizontalMerge(false); command->setVerticalMerge(false); command->add(*selection()); command->execute(canvas()); } void CellToolBase::mergeCellsHorizontal() { // sanity check if (selection()->activeSheet()->isProtected()) { return; } if (selection()->activeSheet()->map()->isProtected()) { return; } MergeCommand* const command = new MergeCommand(); command->setSheet(selection()->activeSheet()); command->setHorizontalMerge(true); command->setVerticalMerge(false); command->setSelection(selection()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::mergeCellsVertical() { // sanity check if (selection()->activeSheet()->isProtected()) { return; } if (selection()->activeSheet()->map()->isProtected()) { return; } MergeCommand* const command = new MergeCommand(); command->setSheet(selection()->activeSheet()); command->setHorizontalMerge(false); command->setVerticalMerge(true); command->setSelection(selection()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::dissociateCells() { // sanity check if (selection()->activeSheet()->isProtected()) { return; } if (selection()->activeSheet()->map()->isProtected()) { return; } MergeCommand* const command = new MergeCommand(); command->setSheet(selection()->activeSheet()); command->setReverse(true); command->setSelection(selection()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::resizeColumn() { if (selection()->isRowSelected()) KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); else { ResizeColumn dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } } void CellToolBase::insertColumn() { InsertDeleteColumnManipulator* command = new InsertDeleteColumnManipulator(); command->setSheet(selection()->activeSheet()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::deleteColumn() { InsertDeleteColumnManipulator* command = new InsertDeleteColumnManipulator(); command->setSheet(selection()->activeSheet()); command->setReverse(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::hideColumn() { if (selection()->isRowSelected()) { KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); return; } HideShowManipulator* command = new HideShowManipulator(); command->setSheet(selection()->activeSheet()); command->setManipulateColumns(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::showColumn() { if (selection()->isRowSelected()) { KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); return; } HideShowManipulator* command = new HideShowManipulator(); command->setSheet(selection()->activeSheet()); command->setManipulateColumns(true); command->setReverse(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::slotShowColumnDialog() { ShowColRow dialog(canvas()->canvasWidget(), selection(), ShowColRow::Column); dialog.exec(); } void CellToolBase::equalizeColumn() { if (selection()->isRowSelected()) KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); else { const QRect range = selection()->lastRange(); const ColumnFormat* columnFormat = selection()->activeSheet()->columnFormat(range.left()); double size = columnFormat->width(); if (range.left() == range.right()) return; for (int i = range.left() + 1; i <= range.right(); ++i) size = qMax(selection()->activeSheet()->columnFormat(i)->width(), size); if (size != 0.0) { ResizeColumnManipulator* command = new ResizeColumnManipulator(); command->setSheet(selection()->activeSheet()); command->setSize(qMax(2.0, size)); command->add(*selection()); if (!command->execute()) delete command; } else { // hide HideShowManipulator* command = new HideShowManipulator(); command->setSheet(selection()->activeSheet()); command->setManipulateColumns(true); command->add(*selection()); if (!command->execute()) delete command; } } } void CellToolBase::adjustColumn() { AdjustColumnRowManipulator* command = new AdjustColumnRowManipulator(); command->setSheet(selection()->activeSheet()); command->setAdjustColumn(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::resizeRow() { if (selection()->isColumnSelected()) KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); else { ResizeRow dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } } void CellToolBase::insertRow() { InsertDeleteRowManipulator* command = new InsertDeleteRowManipulator(); command->setSheet(selection()->activeSheet()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::deleteRow() { InsertDeleteRowManipulator* command = new InsertDeleteRowManipulator(); command->setSheet(selection()->activeSheet()); command->setReverse(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::hideRow() { if (selection()->isColumnSelected()) { KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); return; } HideShowManipulator* command = new HideShowManipulator(); command->setSheet(selection()->activeSheet()); command->setManipulateRows(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::showRow() { if (selection()->isColumnSelected()) { KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); return; } HideShowManipulator* command = new HideShowManipulator(); command->setSheet(selection()->activeSheet()); command->setManipulateRows(true); command->setReverse(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::slotShowRowDialog() { ShowColRow dialog(canvas()->canvasWidget(), selection(), ShowColRow::Row); dialog.exec(); } void CellToolBase::equalizeRow() { if (selection()->isColumnSelected()) KMessageBox::error(canvas()->canvasWidget(), i18n("Area is too large.")); else { const QRect range = selection()->lastRange(); const RowFormat* rowFormat = selection()->activeSheet()->rowFormat(range.top()); double size = rowFormat->height(); if (range.top() == range.bottom()) return; for (int i = range.top() + 1; i <= range.bottom(); ++i) size = qMax(selection()->activeSheet()->rowFormat(i)->height(), size); if (size != 0.0) { ResizeRowManipulator* command = new ResizeRowManipulator(); command->setSheet(selection()->activeSheet()); command->setSize(qMax(2.0, size)); command->add(*selection()); if (!command->execute()) delete command; } else { // hide HideShowManipulator* command = new HideShowManipulator(); command->setSheet(selection()->activeSheet()); command->setManipulateRows(true); command->add(*selection()); if (!command->execute()) delete command; } } } void CellToolBase::adjustRow() { AdjustColumnRowManipulator* command = new AdjustColumnRowManipulator(); command->setSheet(selection()->activeSheet()); command->setAdjustRow(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::adjust() { AdjustColumnRowManipulator* command = new AdjustColumnRowManipulator(); command->setSheet(selection()->activeSheet()); command->setAdjustColumn(true); command->setAdjustRow(true); command->add(*selection()); command->execute(canvas()); } void CellToolBase::insertCells() { InsertDialog dialog(canvas()->canvasWidget(), selection(), InsertDialog::Insert); dialog.exec(); } void CellToolBase::deleteCells() { InsertDialog dialog(canvas()->canvasWidget(), selection(), InsertDialog::Remove); dialog.exec(); } void CellToolBase::clearAll() { DeleteCommand* command = new DeleteCommand(); command->setSheet(selection()->activeSheet()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::clearContents() { // TODO Stefan: Actually this check belongs into the command! if (selection()->activeSheet()->areaIsEmpty(*selection())) return; DataManipulator* command = new DataManipulator(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Clear Text")); // parsing gets set only so that parseUserInput is called as it should be, // no actual parsing shall be done command->setParsing(true); command->setValue(Value("")); command->add(*selection()); command->execute(canvas()); } void CellToolBase::comment() { CommentDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::clearComment() { // TODO Stefan: Actually this check belongs into the command! if (selection()->activeSheet()->areaIsEmpty(*selection(), Sheet::Comment)) return; CommentCommand* command = new CommentCommand(); command->setSheet(selection()->activeSheet()); command->setText(i18n("Remove Comment")); command->setComment(QString()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::conditional() { ConditionalDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::clearConditionalStyles() { // TODO Stefan: Actually this check belongs into the command! if (selection()->activeSheet()->areaIsEmpty(*selection(), Sheet::ConditionalCellAttribute)) return; CondtionCommand* command = new CondtionCommand(); command->setSheet(selection()->activeSheet()); command->setConditionList(QLinkedList()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::insertHyperlink() { selection()->emitAboutToModify(); QPoint marker(selection()->marker()); Cell cell(selection()->activeSheet(), marker); LinkDialog* dialog = new LinkDialog(canvas()->canvasWidget(), selection()); dialog->setWindowTitle(i18n("Insert Link")); if (!cell.isNull()) { dialog->setText(cell.userInput()); if (!cell.link().isEmpty()) { dialog->setWindowTitle(i18n("Edit Link")); dialog->setLink(cell.link()); } } if (dialog->exec() == KDialog::Accepted) { cell = Cell(selection()->activeSheet(), marker); LinkCommand* command = new LinkCommand(cell, dialog->text(), dialog->link()); canvas()->addCommand(command); //refresh editWidget selection()->emitModified(); } delete dialog; } void CellToolBase::clearHyperlink() { QPoint marker(selection()->marker()); Cell cell(selection()->activeSheet(), marker); if (!cell) return; if (cell.link().isEmpty()) return; LinkCommand* command = new LinkCommand(cell, QString(), QString()); canvas()->addCommand(command); selection()->emitModified(); } void CellToolBase::validity() { ValidityDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::clearValidity() { // TODO Stefan: Actually this check belongs into the command! if (selection()->activeSheet()->areaIsEmpty(*selection(), Sheet::Validity)) return; ValidityCommand* command = new ValidityCommand(); command->setSheet(selection()->activeSheet()); command->setValidity(Validity()); // empty object removes validity command->add(*selection()); command->execute(canvas()); } void CellToolBase::sort() { if (selection()->isSingular()) { KMessageBox::error(canvas()->canvasWidget(), i18n("You must select multiple cells.")); return; } SortDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::sortInc() { if (selection()->isSingular()) { KMessageBox::error(canvas()->canvasWidget(), i18n("You must select multiple cells.")); return; } SortManipulator* command = new SortManipulator(); command->setSheet(selection()->activeSheet()); // Entire row(s) selected ? Or just one row ? Sort by columns if yes. QRect range = selection()->lastRange(); bool sortCols = selection()->isRowSelected(); sortCols = sortCols || (range.top() == range.bottom()); command->setSortRows(!sortCols); command->addSortBy(0, true); // by first one, ascending order command->add(*selection()); command->execute(canvas()); selection()->emitModified(); } void CellToolBase::sortDec() { if (selection()->isSingular()) { KMessageBox::error(canvas()->canvasWidget(), i18n("You must select multiple cells.")); return; } SortManipulator* command = new SortManipulator(); command->setSheet(selection()->activeSheet()); // Entire row(s) selected ? Or just one row ? Sort by rows if yes. QRect range = selection()->lastRange(); bool sortCols = selection()->isRowSelected(); sortCols = sortCols || (range.top() == range.bottom()); command->setSortRows(!sortCols); command->addSortBy(0, false); // by first one, descending order command->add(*selection()); command->execute(canvas()); selection()->emitModified(); } void CellToolBase::autoFilter() { AutoFilterCommand* command = new AutoFilterCommand(); command->setSheet(selection()->activeSheet()); command->add(*selection()); command->execute(canvas()); } void CellToolBase::fillLeft() { FillManipulator* command = new FillManipulator(); command->setSheet(selection()->activeSheet()); command->setDirection(FillManipulator::Left); command->add(*selection()); command->execute(canvas()); } void CellToolBase::fillRight() { FillManipulator* command = new FillManipulator(); command->setSheet(selection()->activeSheet()); command->setDirection(FillManipulator::Right); command->add(*selection()); command->execute(canvas()); } void CellToolBase::fillUp() { FillManipulator* command = new FillManipulator(); command->setSheet(selection()->activeSheet()); command->setDirection(FillManipulator::Up); command->add(*selection()); command->execute(canvas()); } void CellToolBase::fillDown() { FillManipulator* command = new FillManipulator(); command->setSheet(selection()->activeSheet()); command->setDirection(FillManipulator::Down); command->add(*selection()); command->execute(canvas()); } void CellToolBase::autoSum() { selection()->emitAboutToModify(); //Get the selected range and remove the current cell from it(as that is //where the result of the autosum will be stored - perhaps change //this behaviour??) QRect sel = selection()->lastRange(); if (sel.height() > 1) { if (selection()->marker().y() == sel.top()) sel.setTop(sel.top() + 1); if (selection()->marker().y() == sel.bottom()) sel.setBottom(sel.bottom() - 1); } else { if (sel.width() > 1) { if (selection()->marker().x() == sel.left()) sel.setLeft(sel.left() + 1); if (selection()->marker().x() == sel.right()) sel.setRight(sel.right() - 1); } else { sel = QRect(); // only 1 cell selected // try to automagically find cells the user wants to sum up int start = -1, end = -1; if ((selection()->marker().y() > 1) && Cell(selection()->activeSheet(), selection()->marker().x(), selection()->marker().y() - 1).value().isNumber()) { // check cells above the current one start = end = selection()->marker().y() - 1; for (start--; (start > 0) && Cell(selection()->activeSheet(), selection()->marker().x(), start).value().isNumber(); start--) ; const Region region(QRect(QPoint(selection()->marker().x(), start + 1), QPoint(selection()->marker().x(), end)), selection()->activeSheet()); const QString str = region.name(selection()->activeSheet()); createEditor(); editor()->setText("=SUM(" + str + ')'); editor()->setCursorPosition(5 + str.length()); return; } else if ((selection()->marker().x() > 1) && Cell(selection()->activeSheet(), selection()->marker().x() - 1, selection()->marker().y()).value().isNumber()) { // check cells to the left of the current one start = end = selection()->marker().x() - 1; for (start--; (start > 0) && Cell(selection()->activeSheet(), start, selection()->marker().y()).value().isNumber(); start--) ; const Region region(QRect(QPoint(start + 1, selection()->marker().y()), QPoint(end, selection()->marker().y())), selection()->activeSheet()); const QString str = region.name(selection()->activeSheet()); createEditor(); editor()->setText("=SUM(" + str + ')'); editor()->setCursorPosition(5 + str.length()); return; } } } if ((sel.width() > 1) && (sel.height() > 1)) sel = QRect(); createEditor(); const Region region(sel, selection()->activeSheet()); if (region.isValid()) { editor()->setText("=SUM(" + region.name(selection()->activeSheet()) + ')'); deleteEditor(true); } else { selection()->startReferenceSelection(); editor()->setText("=SUM()"); editor()->setCursorPosition(5); } } void CellToolBase::insertSeries() { selection()->emitAboutToModify(); SeriesDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::insertSpecialChar() { QString fontFamily = Cell(selection()->activeSheet(), selection()->marker()).style().fontFamily(); QChar c = ' '; if (d->specialCharDialog == 0) { d->specialCharDialog = new CharSelectDia(canvas()->canvasWidget(), "SpecialCharDialog", fontFamily, c, false); connect(d->specialCharDialog, SIGNAL(insertChar(QChar, const QString&)), this, SLOT(specialChar(QChar, const QString&))); connect(d->specialCharDialog, SIGNAL(finished()), this, SLOT(specialCharDialogClosed())); } d->specialCharDialog->show(); } void CellToolBase::specialCharDialogClosed() { if (d->specialCharDialog) { disconnect(d->specialCharDialog, SIGNAL(insertChar(QChar, const QString&)), this, SLOT(specialChar(QChar, const QString&))); disconnect(d->specialCharDialog, SIGNAL(finished()), this, SLOT(specialCharDialogClosed())); d->specialCharDialog->deleteLater(); d->specialCharDialog = 0; } } void CellToolBase::specialChar(QChar character, const QString& fontName) { const Style style = Cell(selection()->activeSheet(), selection()->marker()).style(); if (style.fontFamily() != fontName) { Style newStyle; newStyle.setFontFamily(fontName); selection()->activeSheet()->cellStorage()->setStyle(Region(selection()->marker()), newStyle); } QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, QString(character)); if (!editor()) { createEditor(); } QApplication::sendEvent(editor(), &keyEvent); } void CellToolBase::insertFormula() { if (! d->formulaDialog) { if (! createEditor()) return; d->formulaDialog = new FormulaDialog(canvas()->canvasWidget(), selection(), editor()); } d->formulaDialog->show(); // dialog deletes itself later } void CellToolBase::insertFromDatabase() { #ifndef QT_NO_SQL selection()->emitAboutToModify(); QStringList str = QSqlDatabase::drivers(); if (str.isEmpty()) { KMessageBox::error(canvas()->canvasWidget(), i18n("No database drivers available. To use this feature you need " "to install the necessary Qt 3 database drivers.")); return; } DatabaseDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); #endif } void CellToolBase::insertFromTextfile() { selection()->emitAboutToModify(); CSVDialog dialog(canvas()->canvasWidget(), selection(), CSVDialog::File); dialog.setDecimalSymbol(selection()->activeSheet()->map()->calculationSettings()->locale()->decimalSymbol()); dialog.setThousandsSeparator(selection()->activeSheet()->map()->calculationSettings()->locale()->thousandsSeparator()); if (!dialog.canceled()) dialog.exec(); } void CellToolBase::insertFromClipboard() { selection()->emitAboutToModify(); CSVDialog dialog(canvas()->canvasWidget(), selection(), CSVDialog::Clipboard); dialog.setDecimalSymbol(selection()->activeSheet()->map()->calculationSettings()->locale()->decimalSymbol()); dialog.setThousandsSeparator(selection()->activeSheet()->map()->calculationSettings()->locale()->thousandsSeparator()); if (!dialog.canceled()) dialog.exec(); } void CellToolBase::textToColumns() { selection()->emitAboutToModify(); QRect area = selection()->lastRange(); area.setRight(area.left()); // only use the first column Region oldSelection = *selection(); // store selection()->initialize(area); CSVDialog dialog(canvas()->canvasWidget(), selection(), CSVDialog::Column); dialog.setDecimalSymbol(selection()->activeSheet()->map()->calculationSettings()->locale()->decimalSymbol()); dialog.setThousandsSeparator(selection()->activeSheet()->map()->calculationSettings()->locale()->thousandsSeparator()); if (!dialog.canceled()) dialog.exec(); else selection()->initialize(oldSelection); } void CellToolBase::sortList() { ListDialog dialog(canvas()->canvasWidget()); dialog.exec(); } void CellToolBase::consolidate() { selection()->emitAboutToModify(); ConsolidateDialog * dialog = new ConsolidateDialog(canvas()->canvasWidget(), selection()); dialog->show(); // dialog deletes itself later } void CellToolBase::goalSeek() { selection()->emitAboutToModify(); GoalSeekDialog* dialog = new GoalSeekDialog(canvas()->canvasWidget(), selection()); dialog->show(); // dialog deletes itself later } void CellToolBase::subtotals() { if ((selection()->lastRange().width() < 2) || (selection()->lastRange().height() < 2)) { KMessageBox::error(canvas()->canvasWidget(), i18n("You must select multiple cells.")); return; } SubtotalDialog dialog(canvas()->canvasWidget(), selection()); if (dialog.exec()) { selection()->initialize(QRect(dialog.selection().topLeft(), dialog.selection().bottomRight())); selection()->emitModified(); } } void CellToolBase::setAreaName() { AddNamedAreaDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::namedAreaDialog() { NamedAreaDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::formulaSelection(const QString& expression) { if (expression == i18n("Others...")) { insertFormula(); return; } createEditor(); FormulaDialog* dialog = new FormulaDialog(canvas()->canvasWidget(), selection(), editor(), expression); dialog->show(); // dialog deletes itself later } void CellToolBase::edit() { if (editor()) return; createEditor(false /* keep content */); } void CellToolBase::cut() { if (!editor()) { selection()->activeSheet()->cutSelection(selection()); } else { editor()->cut(); } selection()->emitModified(); } void CellToolBase::copy() const { Selection* selection = const_cast(this)->selection(); if (!editor()) { selection->activeSheet()->copySelection(selection); } else { editor()->copy(); } } bool CellToolBase::paste() { if (!selection()->activeSheet()->map()->isReadWrite()) // don't paste into a read only document return false; const QMimeData* mimeData = QApplication::clipboard()->mimeData(QClipboard::Clipboard); if (mimeData->hasFormat("application/vnd.oasis.opendocument.spreadsheet")) { QByteArray returnedTypeMime = "application/vnd.oasis.opendocument.spreadsheet"; QByteArray arr = mimeData->data(returnedTypeMime); if (arr.isEmpty()) return false; QBuffer buffer(&arr); KoStore * store = KoStore::createStore(&buffer, KoStore::Read); KoOdfReadStore odfStore(store); KoXmlDocument doc; QString errorMessage; bool ok = odfStore.loadAndParse("content.xml", doc, errorMessage); if (!ok) { kError(32001) << "Error parsing content.xml: " << errorMessage << endl; return false; } KoOdfStylesReader stylesReader; KoXmlDocument stylesDoc; (void)odfStore.loadAndParse("styles.xml", stylesDoc, errorMessage); // Load styles from style.xml stylesReader.createStyleMap(stylesDoc, true); // Also load styles from content.xml stylesReader.createStyleMap(doc, false); // from KSpreadDoc::loadOdf: KoXmlElement content = doc.documentElement(); KoXmlElement realBody(KoXml::namedItemNS(content, KoXmlNS::office, "body")); if (realBody.isNull()) { kDebug(36005) << "Invalid OASIS OpenDocument file. No office:body tag found."; return false; } KoXmlElement body = KoXml::namedItemNS(realBody, KoXmlNS::office, "spreadsheet"); if (body.isNull()) { kError(36005) << "No office:spreadsheet found!" << endl; KoXmlElement childElem; QString localName; forEachElement(childElem, realBody) { localName = childElem.localName(); } return false; } KoOdfLoadingContext context(stylesReader, store); Q_ASSERT(!stylesReader.officeStyle().isNull()); //load in first selection()->activeSheet()->map()->styleManager()->loadOdfStyleTemplate(stylesReader); // all goes to workbook bool result = selection()->activeSheet()->map()->loadOdf(body, context); if (!result) return false; selection()->activeSheet()->map()->namedAreaManager()->loadOdf(body); } if (!editor()) { //kDebug(36005) <<"Pasting. Rect=" << selection()->lastRange() <<" bytes"; PasteCommand *const command = new PasteCommand(); command->setSheet(selection()->activeSheet()); command->add(*selection()); command->setMimeData(QApplication::clipboard()->mimeData()); command->setPasteFC(true); command->execute(canvas()); d->updateEditor(Cell(selection()->activeSheet(), selection()->cursor())); } else { editor()->paste(); } selection()->emitModified(); return true; } void CellToolBase::specialPaste() { SpecialPasteDialog dialog(canvas()->canvasWidget(), selection()); if (dialog.exec()) { selection()->emitModified(); } } void CellToolBase::pasteWithInsertion() { const QMimeData *const mimeData = QApplication::clipboard()->mimeData(); if (!PasteCommand::unknownShiftDirection(mimeData)) { PasteCommand *const command = new PasteCommand(); command->setSheet(selection()->activeSheet()); command->add(*selection()); command->setMimeData(mimeData); command->setInsertionMode(PasteCommand::ShiftCells); command->execute(canvas()); } else { PasteInsertDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } d->updateEditor(Cell(selection()->activeSheet(), selection()->cursor())); } void CellToolBase::selectAll() { selection()->selectAll(); } void CellToolBase::find() { FindDlg dialog(canvas()->canvasWidget(), "Find", d->findOptions, d->findStrings); dialog.setHasSelection(!selection()->isSingular()); dialog.setHasCursor(true); if (KFindDialog::Accepted != dialog.exec()) return; // Save for next time d->findOptions = dialog.options(); d->findStrings = dialog.findHistory(); d->typeValue = dialog.searchType(); d->directionValue = dialog.searchDirection(); // Create the KFind object delete d->find; delete d->replace; d->find = new KFind(dialog.pattern(), dialog.options(), canvas()->canvasWidget()); d->replace = 0; d->replaceCommand = 0; d->searchInSheets.currentSheet = selection()->activeSheet(); d->searchInSheets.firstSheet = d->searchInSheets.currentSheet; initFindReplace(); findNext(); } // Initialize a find or replace operation, using d->find or d->replace, // and d->findOptions. void CellToolBase::initFindReplace() { KFind* findObj = d->find ? d->find : d->replace; Q_ASSERT(findObj); connect(findObj, SIGNAL(highlight(const QString &, int, int)), this, SLOT(slotHighlight(const QString &, int, int))); connect(findObj, SIGNAL(findNext()), this, SLOT(findNext())); bool bck = d->findOptions & KFind::FindBackwards; Sheet* currentSheet = d->searchInSheets.currentSheet; QRect region = (d->findOptions & KFind::SelectedText) ? selection()->lastRange() : QRect(1, 1, currentSheet->cellStorage()->columns(), currentSheet->cellStorage()->rows()); // All cells int colStart = !bck ? region.left() : region.right(); int colEnd = !bck ? region.right() : region.left(); int rowStart = !bck ? region.top() : region.bottom(); int rowEnd = !bck ? region.bottom() : region.top(); d->findLeftColumn = region.left(); d->findRightColumn = region.right(); d->findTopRow = region.top(); d->findBottomRow = region.bottom(); d->findStart = QPoint(colStart, rowStart); d->findPos = (d->findOptions & KFind::FromCursor) ? selection()->marker() : d->findStart; d->findEnd = QPoint(colEnd, rowEnd); //kDebug() << d->findPos <<" to" << d->findEnd; //kDebug() <<"leftcol=" << d->findLeftColumn <<" rightcol=" << d->findRightColumn; } void CellToolBase::findNext() { KFind* findObj = d->find ? d->find : d->replace; if (!findObj) { find(); return; } KFind::Result res = KFind::NoMatch; Cell cell = findNextCell(); bool forw = !(d->findOptions & KFind::FindBackwards); while (res == KFind::NoMatch && !cell.isNull()) { if (findObj->needData()) { if (d->typeValue == FindOption::Note) findObj->setData(cell.comment()); else findObj->setData(cell.userInput()); d->findPos = QPoint(cell.column(), cell.row()); //kDebug() <<"setData(cell" << d->findPos << ')'; } // Let KFind inspect the text fragment, and display a dialog if a match is found if (d->find) res = d->find->find(); else res = d->replace->replace(); if (res == KFind::NoMatch) { // Go to next cell, skipping unwanted cells if (d->directionValue == FindOption::Row) { if (forw) ++d->findPos.rx(); else --d->findPos.rx(); } else { if (forw) ++d->findPos.ry(); else --d->findPos.ry(); } cell = findNextCell(); } } if (res == KFind::NoMatch) { //emitUndoRedo(); //removeHighlight(); if (findObj->shouldRestart()) { d->findOptions &= ~KFind::FromCursor; d->findPos = d->findStart; findObj->resetCounts(); findNext(); } else { // done, close the 'find next' dialog if (d->find) d->find->closeFindNextDialog(); else { canvas()->addCommand(d->replaceCommand); d->replaceCommand = 0; d->replace->closeReplaceNextDialog(); } } } } Cell CellToolBase::nextFindValidCell(int col, int row) { Cell cell = Cell(d->searchInSheets.currentSheet, col, row); if (cell.isDefault() || cell.isPartOfMerged() || cell.isFormula()) cell = Cell(); if (d->typeValue == FindOption::Note && !cell.isNull() && cell.comment().isEmpty()) cell = Cell(); return cell; } Cell CellToolBase::findNextCell() { // cellStorage()->firstInRow / cellStorage()->nextInRow would be faster at doing that, // but it doesn't seem to be easy to combine it with 'start a column d->find.x()'... Sheet* sheet = d->searchInSheets.currentSheet; Cell cell; bool forw = !(d->findOptions & KFind::FindBackwards); int col = d->findPos.x(); int row = d->findPos.y(); int maxRow = sheet->cellStorage()->rows(); // kDebug() <<"findNextCell starting at" << col << ',' << row <<" forw=" << forw; if (d->directionValue == FindOption::Row) { while (!cell && row != d->findEnd.y() && (forw ? row < maxRow : row >= 0)) { while (!cell && (forw ? col <= d->findRightColumn : col >= d->findLeftColumn)) { cell = nextFindValidCell(col, row); if (forw) ++col; else --col; } if (!cell.isNull()) break; // Prepare looking in the next row if (forw) { col = d->findLeftColumn; ++row; } else { col = d->findRightColumn; --row; } //kDebug() <<"next row:" << col << ',' << row; } } else { while (!cell && (forw ? col <= d->findRightColumn : col >= d->findLeftColumn)) { while (!cell && row != d->findEnd.y() && (forw ? row < maxRow : row >= 0)) { cell = nextFindValidCell(col, row); if (forw) ++row; else --row; } if (!cell.isNull()) break; // Prepare looking in the next col if (forw) { row = d->findTopRow; ++col; } else { row = d->findBottomRow; --col; } //kDebug() <<"next row:" << col << ',' << row; } } // if (!cell) // No more next cell - TODO go to next sheet (if not looking in a selection) // (and make d->findEnd(max, max) in that case...) // kDebug() <<" returning" << cell; return cell; } void CellToolBase::findPrevious() { KFind* findObj = d->find ? d->find : d->replace; if (!findObj) { find(); return; } //kDebug() <<"findPrevious"; int opt = d->findOptions; bool forw = !(opt & KFind::FindBackwards); if (forw) d->findOptions = (opt | KFind::FindBackwards); else d->findOptions = (opt & ~KFind::FindBackwards); findNext(); d->findOptions = opt; // restore initial options } void CellToolBase::replace() { SearchDlg dialog(canvas()->canvasWidget(), "Replace", d->findOptions, d->findStrings, d->replaceStrings); dialog.setHasSelection(!selection()->isSingular()); dialog.setHasCursor(true); if (KReplaceDialog::Accepted != dialog.exec()) return; d->findOptions = dialog.options(); d->findStrings = dialog.findHistory(); d->replaceStrings = dialog.replacementHistory(); d->typeValue = dialog.searchType(); delete d->find; delete d->replace; d->find = 0; // NOTE Stefan: Avoid beginning of line replacements with nothing which // will lead to an infinite loop (Bug #125535). The reason // for this is unclear to me, but who cares and who would // want to do something like this, häh?! if (dialog.pattern() == "^" && dialog.replacement().isEmpty()) return; d->replace = new KReplace(dialog.pattern(), dialog.replacement(), dialog.options()); d->searchInSheets.currentSheet = selection()->activeSheet(); d->searchInSheets.firstSheet = d->searchInSheets.currentSheet; initFindReplace(); connect(d->replace, SIGNAL(replace(const QString &, int, int, int)), this, SLOT(slotReplace(const QString &, int, int, int))); d->replaceCommand = new QUndoCommand(i18n("Replace")); findNext(); #if 0 // Refresh the editWidget // TODO - after a replacement only? Cell cell = Cell(activeSheet(), selection()->marker()); if (cell.userInput() != 0) d->editWidget->setText(cell.userInput()); else d->editWidget->setText(""); #endif } void CellToolBase::slotHighlight(const QString &/*text*/, int /*matchingIndex*/, int /*matchedLength*/) { selection()->initialize(d->findPos); KDialog *dialog = 0; if (d->find) dialog = d->find->findNextDialog(); else dialog = d->replace->replaceNextDialog(); kDebug() << " baseDialog :" << dialog; QRect globalRect(d->findPos, d->findEnd); globalRect.moveTopLeft(canvas()->canvasWidget()->mapToGlobal(globalRect.topLeft())); KDialog::avoidArea(dialog, QRect(d->findPos, d->findEnd)); } void CellToolBase::slotReplace(const QString &newText, int, int, int) { if (d->typeValue == FindOption::Value) { DataManipulator* command = new DataManipulator(d->replaceCommand); command->setParsing(true); command->setSheet(d->searchInSheets.currentSheet); command->setValue(Value(newText)); command->add(Region(d->findPos, d->searchInSheets.currentSheet)); } else if (d->typeValue == FindOption::Note) { CommentCommand* command = new CommentCommand(d->replaceCommand); command->setComment(newText); command->setSheet(d->searchInSheets.currentSheet); command->add(Region(d->findPos, d->searchInSheets.currentSheet)); } } void CellToolBase::gotoCell() { GotoDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); scrollToCell(selection()->cursor()); } void CellToolBase::spellCheck() { SpellCheckCommand* command = new SpellCheckCommand(selection(), canvas()); command->start(); } void CellToolBase::inspector() { // useful to inspect objects Cell cell(selection()->activeSheet(), selection()->marker()); KSpread::Inspector* ins = new KSpread::Inspector(cell); ins->exec(); delete ins; } void CellToolBase::qTableView() { #ifndef NDEBUG KDialog* const dialog = new KDialog(canvas()->canvasWidget()); QTableView* const view = new QTableView(dialog); SheetModel* const model = new SheetModel(selection()->activeSheet()); view->setModel(model); dialog->setCaption("Read{Only,Write}TableModel Test"); dialog->setMainWidget(view); dialog->exec(); delete dialog; delete model; #endif } void CellToolBase::sheetFormat() { AutoFormatDialog dialog(canvas()->canvasWidget(), selection()); dialog.exec(); } void CellToolBase::listChoosePopupMenu() { if (!selection()->activeSheet()->map()->isReadWrite()) { return; } delete d->popupListChoose; d->popupListChoose = new QMenu(); const Sheet *const sheet = selection()->activeSheet(); const Cell cursorCell(sheet, selection()->cursor()); const QString text = cursorCell.userInput(); const CellStorage *const storage = sheet->cellStorage(); QStringList itemList; const Region::ConstIterator end(selection()->constEnd()); for (Region::ConstIterator it(selection()->constBegin()); it != end; ++it) { const QRect range = (*it)->rect(); if (cursorCell.column() < range.left() || cursorCell.column() > range.right()) { continue; // next range } Cell cell; if (range.top() == 1) { cell = storage->firstInColumn(cursorCell.column(), CellStorage::Values); } else { cell = storage->nextInColumn(cursorCell.column(), range.top() - 1, CellStorage::Values); } while (!cell.isNull() && cell.row() <= range.bottom()) { if (!cell.isPartOfMerged() && !(cell == cursorCell)) { const QString userInput = cell.userInput(); if (cell.value().isString() && userInput != text && !userInput.isEmpty()) { if (itemList.indexOf(userInput) == -1) { itemList.append(userInput); } } } cell = storage->nextInColumn(cell.column(), cell.row(), CellStorage::Values); } } for (QStringList::Iterator it = itemList.begin(); it != itemList.end();++it) { d->popupListChoose->addAction((*it)); } if (itemList.isEmpty()) { return; } double tx = selection()->activeSheet()->columnPosition(selection()->marker().x()); double ty = selection()->activeSheet()->rowPosition(selection()->marker().y()); double h = cursorCell.height(); const CellView cellView = sheetView(selection()->activeSheet())->cellView(selection()->marker().x(), selection()->marker().y()); if (cellView.obscuresCells()) { h = cellView.cellHeight(); } ty += h; if (selection()->activeSheet()->layoutDirection() == Qt::RightToLeft) { tx = canvas()->canvasWidget()->width() - tx; } QPoint p((int)tx, (int)ty); QPoint p2 = canvas()->canvasWidget()->mapToGlobal(p); if (selection()->activeSheet()->layoutDirection() == Qt::RightToLeft) { p2.setX(p2.x() - d->popupListChoose->sizeHint().width() + 1); } d->popupListChoose->popup(p2); connect(d->popupListChoose, SIGNAL(triggered(QAction*)), this, SLOT(listChooseItemSelected(QAction*))); } void CellToolBase::listChooseItemSelected(QAction* action) { const Cell cell(selection()->activeSheet(), selection()->marker()); if (action->text() == cell.userInput()) return; DataManipulator *command = new DataManipulator; command->setSheet(selection()->activeSheet()); command->setValue(Value(action->text())); command->setParsing(true); command->add(selection()->marker()); command->execute(canvas()); } void CellToolBase::documentSettingsDialog() { DocumentSettingsDialog dialog(selection(), canvas()->canvasWidget()); dialog.exec(); } void CellToolBase::breakBeforeColumn(bool enable) { PageBreakCommand *command = new PageBreakCommand(); command->setSheet(selection()->activeSheet()); command->setMode(PageBreakCommand::BreakBeforeColumn); command->setReverse(!enable); command->add(*selection()); command->execute(canvas()); } void CellToolBase::breakBeforeRow(bool enable) { PageBreakCommand *command = new PageBreakCommand(); command->setSheet(selection()->activeSheet()); command->setMode(PageBreakCommand::BreakBeforeRow); command->setReverse(!enable); command->add(*selection()); command->execute(canvas()); } diff --git a/kspread/ui/CellToolBase.h b/kspread/ui/CellToolBase.h index acd7cf0858..793c756190 100644 --- a/kspread/ui/CellToolBase.h +++ b/kspread/ui/CellToolBase.h @@ -1,289 +1,289 @@ /* This file is part of the KDE project Copyright 2006-2008 Stefan Nikolaus Copyright 2006 Raphael Langerhorst Copyright 2002-2004 Ariya Hidayat Copyright 1999-2003 Laurent Montel Copyright 2002-2003 Norbert Andres Copyright 2002-2003 Philipp Mueller Copyright 2002-2003 John Dailey Copyright 1999-2003 David Faure Copyright 1999-2001 Simon Hausmann Copyright 1998-2000 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSPREAD_CELL_TOOL_BASE #define KSPREAD_CELL_TOOL_BASE #include #include "Cell.h" #include "kspread_export.h" #include "Selection.h" class KoColor; namespace KSpread { class SheetView; /** * Abstract tool providing actions acting on cell ranges. */ class KSPREAD_EXPORT CellToolBase : public KoInteractionTool { Q_OBJECT public: /** * The editor type. */ enum Editor { EmbeddedEditor, ///< the embedded editor appearing in a cell ExternalEditor ///< the external editor located in the tool options }; /** * Constructor. */ CellToolBase(KoCanvasBase* canvas); /** * Destructor. */ virtual ~CellToolBase(); virtual void paint(QPainter &painter, const KoViewConverter &converter); void paintReferenceSelection(QPainter &painter, const QRectF &paintRect); void paintSelection(QPainter &painter, const QRectF &paintRect); virtual void mousePressEvent(KoPointerEvent* event); virtual void mouseMoveEvent(KoPointerEvent* event); virtual void mouseReleaseEvent(KoPointerEvent* event); virtual void mouseDoubleClickEvent(KoPointerEvent* event); virtual void keyPressEvent(QKeyEvent* event); virtual void inputMethodEvent(QInputMethodEvent * event); virtual Selection* selection() = 0; bool createEditor(bool clear = true, bool focus = true); CellEditor* editor() const; /** * Sets the editor \p type, which had the focus at last. */ void setLastEditorWithFocus(Editor type); /** * Scrolls to the cell located at \p location. */ void scrollToCell(const QPoint &location); public Q_SLOTS: virtual void activate(ToolActivation toolActivation, const QSet &shapes); virtual void deactivate(); void deleteEditor(bool saveChanges, bool expandMatrix = false); protected: void init(); virtual QWidget* createOptionWidget(); - void applyUserInput(bool expandMatrix = false); + void applyUserInput(const QString &userInput, bool expandMatrix = false); virtual KoInteractionStrategy* createStrategy(KoPointerEvent* event); /** * The shape offset in document coordinates. */ virtual QPointF offset() const = 0; virtual QSizeF size() const = 0; /** * The canvas scrolling offset in document coordinates. */ virtual QPointF canvasOffset() const = 0; virtual int maxCol() const = 0; virtual int maxRow() const = 0; virtual SheetView* sheetView(const Sheet* sheet) const = 0; protected Q_SLOTS: void selectionChanged(const Region&); void activeSheetChanged(Sheet*); void updateEditor(); void focusEditorRequested(); void documentReadWriteToggled(bool enable); void sheetProtectionToggled(bool enable); // -- cell style actions -- void cellStyle(); void setDefaultStyle(); void styleDialog(); void setStyle(const QString& name); void createStyleFromCell(); // -- font actions -- void bold(bool enable); void italic(bool enable); void underline(bool enable); void strikeOut(bool enable); void font(const QString& font); void fontSize(int size); void increaseFontSize(); void decreaseFontSize(); void changeTextColor(const KoColor &); // -- horizontal alignment actions -- void alignLeft(bool enable); void alignRight(bool enable); void alignCenter(bool enable); // -- vertical alignment actions -- void alignTop(bool enable); void alignMiddle(bool enable); void alignBottom(bool enable); // -- border actions -- void borderLeft(); void borderRight(); void borderTop(); void borderBottom(); void borderAll(); void borderRemove(); void borderOutline(); void borderColor(const KoColor &); // -- text layout actions -- void wrapText(bool enable); void verticalText(bool enable); void increaseIndentation(); void decreaseIndentation(); void changeAngle(); // -- value format actions -- void percent(bool enable); void currency(bool enable); void increasePrecision(); void decreasePrecision(); // -- misc style attribute actions -- void toUpperCase(); void toLowerCase(); void firstLetterToUpperCase(); void changeBackgroundColor(const KoColor &); // -- cell merging actions -- void mergeCells(); void mergeCellsHorizontal(); void mergeCellsVertical(); void dissociateCells(); // -- column & row actions -- void resizeColumn(); void insertColumn(); void deleteColumn(); void hideColumn(); void showColumn(); void slotShowColumnDialog(); void equalizeColumn(); void adjustColumn(); void resizeRow(); void insertRow(); void deleteRow(); void hideRow(); void showRow(); void slotShowRowDialog(); void equalizeRow(); void adjustRow(); void adjust(); // -- cell insert/remove actions -- void insertCells(); void deleteCells(); // -- cell content actions -- void clearAll(); void clearContents(); void comment(); void clearComment(); void conditional(); void clearConditionalStyles(); void insertHyperlink(); void clearHyperlink(); void validity(); void clearValidity(); // -- sorting/filtering action -- void sort(); void sortInc(); void sortDec(); void autoFilter(); // -- fill actions -- void fillLeft(); void fillRight(); void fillUp(); void fillDown(); void autoSum(); // -- data insert actions -- void insertSeries(); void insertFormula(); void insertSpecialChar(); void specialChar(QChar character, const QString& fontName); void specialCharDialogClosed(); void insertFromDatabase(); void insertFromTextfile(); void insertFromClipboard(); void textToColumns(); void sortList(); void consolidate(); void goalSeek(); void subtotals(); void setAreaName(); void namedAreaDialog(); void formulaSelection(const QString& expression); // -- general editing actions -- void edit(); virtual void cut(); virtual void copy() const; virtual bool paste(); void specialPaste(); void pasteWithInsertion(); void selectAll(); void find(); void findNext(); void findPrevious(); void replace(); void initFindReplace(); Cell findNextCell(); /** * Called by find/replace (findNext) when it found a match */ void slotHighlight(const QString &text, int matchingIndex, int matchedLength); /** * Called when replacing text in a cell */ void slotReplace(const QString &newText, int, int, int); Cell nextFindValidCell(int col, int row); // -- misc actions -- void gotoCell(); void spellCheck(); void inspector(); void qTableView(); void sheetFormat(); void listChoosePopupMenu(); void listChooseItemSelected(QAction*); void documentSettingsDialog(); void breakBeforeColumn(bool); void breakBeforeRow(bool); private: Q_DISABLE_COPY(CellToolBase) class Private; Private * const d; }; } // namespace KSpread #endif // KSPREAD_CELL_TOOL_BASE diff --git a/kspread/ui/CellToolBase_p.cpp b/kspread/ui/CellToolBase_p.cpp index 649adc303a..6afe1697d6 100644 --- a/kspread/ui/CellToolBase_p.cpp +++ b/kspread/ui/CellToolBase_p.cpp @@ -1,1381 +1,1391 @@ /* This file is part of the KDE project Copyright 2006-2008 Stefan Nikolaus Copyright 2006 Robert Knight Copyright 2006 Inge Wallin Copyright 1999-2002,2004 Laurent Montel Copyright 2002-2005 Ariya Hidayat Copyright 1999-2004 David Faure Copyright 2004-2005 Meni Livne Copyright 2001-2003 Philipp Mueller Copyright 2002-2003 Norbert Andres Copyright 2003 Hamish Rodda Copyright 2003 Joseph Wenninger Copyright 2003 Lukas Tinkl Copyright 2000-2002 Werner Trobin Copyright 2002 Harri Porten Copyright 2002 John Dailey Copyright 2002 Daniel Naber Copyright 1999-2000 Torben Weis Copyright 1999-2000 Stephan Kulow Copyright 2000 Bernd Wuebben Copyright 2000 Wilco Greven Copyright 2000 Simon Hausmann Copyright 1999 Boris Wedl Copyright 1999 Reginald Stadlbauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or(at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "CellToolBase_p.h" #include "CellToolBase.h" // KSpread #include "ApplicationSettings.h" #include "CalculationSettings.h" #include "CellStorage.h" #include "Map.h" #include "RowColumnFormat.h" #include "Selection.h" #include "Sheet.h" // commands #include "commands/DataManipulators.h" #include "commands/StyleCommand.h" // ui #include "ui/CellToolOptionWidget.h" #include "ui/SheetView.h" // KOffice #include #include #include #include // KDE #include #include #include // Qt #include #include #include #include using namespace KSpread; void CellToolBase::Private::updateEditor(const Cell& cell) { const Cell& theCell = cell.isPartOfMerged() ? cell.masterCell() : cell; const Style style = theCell.style(); if (q->selection()->activeSheet()->isProtected() && style.hideFormula()) { optionWidget->editor()->setPlainText(theCell.displayText()); } else if (q->selection()->activeSheet()->isProtected() && style.hideAll()) { optionWidget->editor()->clear(); } else { optionWidget->editor()->setPlainText(theCell.userInput()); } } #define ACTION_EXEC( name, command ) { \ QAction *a = q->action(name); \ const bool blocked = a->blockSignals(true); \ a->command; \ a->blockSignals(blocked); \ } void CellToolBase::Private::updateActions(const Cell& cell) { const Style style = cell.style(); // -- font actions -- ACTION_EXEC("bold", setChecked(style.bold())); ACTION_EXEC("italic", setChecked(style.italic())); ACTION_EXEC("underline", setChecked(style.underline())); ACTION_EXEC("strikeOut", setChecked(style.strikeOut())); static_cast(q->action("font"))->setFont(style.fontFamily()); static_cast(q->action("fontSize"))->setFontSize(style.fontSize()); // -- horizontal alignment actions -- ACTION_EXEC("alignLeft", setChecked(style.halign() == Style::Left)); ACTION_EXEC("alignCenter", setChecked(style.halign() == Style::Center)); ACTION_EXEC("alignRight", setChecked(style.halign() == Style::Right)); // -- vertical alignment actions -- ACTION_EXEC("alignTop", setChecked(style.valign() == Style::Top)); ACTION_EXEC("alignMiddle", setChecked(style.valign() == Style::Middle)); ACTION_EXEC("alignBottom", setChecked(style.valign() == Style::Bottom)); ACTION_EXEC("verticalText", setChecked(style.verticalText())); ACTION_EXEC("wrapText", setChecked(style.wrapText())); Format::Type ft = style.formatType(); ACTION_EXEC("percent", setChecked(ft == Format::Percentage)); ACTION_EXEC("currency", setChecked(ft == Format::Money)); const bool showFormulas = q->selection()->activeSheet()->getShowFormula(); q->action("alignLeft")->setEnabled(showFormulas); q->action("alignCenter")->setEnabled(showFormulas); q->action("alignRight")->setEnabled(showFormulas); if (!q->selection()->activeSheet()->isProtected() || style.notProtected()) { q->action("clearComment")->setEnabled(!cell.comment().isEmpty()); q->action("decreaseIndentation")->setEnabled(style.indentation() > 0.0); } // Now, activate/deactivate some actions depending on what is selected. if (!q->selection()->activeSheet()->isProtected()) { const bool colSelected = q->selection()->isColumnSelected(); const bool rowSelected = q->selection()->isRowSelected(); // -- column & row actions -- q->action("resizeCol")->setEnabled(!rowSelected); q->action("insertColumn")->setEnabled(!rowSelected); q->action("deleteColumn")->setEnabled(!rowSelected); q->action("hideColumn")->setEnabled(!rowSelected); q->action("equalizeCol")->setEnabled(!rowSelected); q->action("resizeRow")->setEnabled(!colSelected); q->action("deleteRow")->setEnabled(!colSelected); q->action("insertRow")->setEnabled(!colSelected); q->action("hideRow")->setEnabled(!colSelected); q->action("equalizeRow")->setEnabled(!colSelected); // -- data insert actions -- q->action("textToColumns")->setEnabled(!rowSelected); const bool simpleSelection = q->selection()->isSingular() || colSelected || rowSelected; q->action("sheetFormat")->setEnabled(!simpleSelection); q->action("sort")->setEnabled(!simpleSelection); q->action("sortDec")->setEnabled(!simpleSelection); q->action("sortInc")->setEnabled(!simpleSelection); q->action("mergeCells")->setEnabled(!simpleSelection); q->action("mergeCellsHorizontal")->setEnabled(!simpleSelection); q->action("mergeCellsVertical")->setEnabled(!simpleSelection); q->action("fillRight")->setEnabled(!simpleSelection); q->action("fillUp")->setEnabled(!simpleSelection); q->action("fillDown")->setEnabled(!simpleSelection); q->action("fillLeft")->setEnabled(!simpleSelection); q->action("createStyleFromCell")->setEnabled(simpleSelection); // just from one cell const bool contiguousSelection = q->selection()->isContiguous(); q->action("subtotals")->setEnabled(contiguousSelection); } } void CellToolBase::Private::setProtectedActionsEnabled(bool enable) { // Enable/disable actions. const QList actions = q->actions().values(); for (int i = 0; i < actions.count(); ++i) actions[i]->setEnabled(enable); optionWidget->formulaButton()->setEnabled(enable); optionWidget->editor()->setEnabled(enable); // These actions are always enabled. q->action("copy")->setEnabled(true); q->action("gotoCell")->setEnabled(true); q->action("edit_find")->setEnabled(true); q->action("edit_find_next")->setEnabled(true); q->action("edit_find_last")->setEnabled(true); } void CellToolBase::Private::processEnterKey(QKeyEvent* event) { // array is true, if ctrl+alt are pressed bool array = (event->modifiers() & Qt::AltModifier) && (event->modifiers() & Qt::ControlModifier); /* save changes to the current editor */ q->deleteEditor(true, array); /* use the configuration setting to see which direction we're supposed to move when enter is pressed. */ KSpread::MoveTo direction = q->selection()->activeSheet()->map()->settings()->moveToValue(); //if shift Button clicked inverse move direction if (event->modifiers() & Qt::ShiftModifier) { switch (direction) { case Bottom: direction = Top; break; case Top: direction = Bottom; break; case Left: direction = Right; break; case Right: direction = Left; break; case BottomFirst: direction = BottomFirst; break; case NoMovement: direction = NoMovement; break; } } /* never extend a selection with the enter key -- the shift key reverses direction, not extends the selection */ QRect r(moveDirection(direction, false)); event->accept(); // QKeyEvent } void CellToolBase::Private::processArrowKey(QKeyEvent *event) { /* NOTE: hitting the tab key also calls this function. Don't forget to account for it */ register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return; /* save changes to the current editor */ q->selection()->emitCloseEditor(true); KSpread::MoveTo direction = Bottom; bool makingSelection = event->modifiers() & Qt::ShiftModifier; switch (event->key()) { case Qt::Key_Down: direction = Bottom; break; case Qt::Key_Up: direction = Top; break; case Qt::Key_Left: if (sheet->layoutDirection() == Qt::RightToLeft) direction = Right; else direction = Left; break; case Qt::Key_Right: if (sheet->layoutDirection() == Qt::RightToLeft) direction = Left; else direction = Right; break; case Qt::Key_Tab: direction = Right; break; case Qt::Key_Backtab: //Shift+Tab moves to the left direction = Left; makingSelection = false; break; default: Q_ASSERT(false); break; } QRect r(moveDirection(direction, makingSelection)); event->accept(); // QKeyEvent } void CellToolBase::Private::processEscapeKey(QKeyEvent * event) { q->selection()->emitCloseEditor(false); // discard changes event->accept(); // QKeyEvent } bool CellToolBase::Private::processHomeKey(QKeyEvent* event) { register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return false; bool makingSelection = event->modifiers() & Qt::ShiftModifier; if (q->editor()) { // We are in edit mode -> go beginning of line - q->editor()->handleKeyPressEvent(event); + QApplication::sendEvent(q->editor(), event); return false; } else { QPoint destination; /* start at the first used cell in the row and cycle through the right until we find a cell that has some output text. But don't look past the current marker. The end result we want is to move to the left to the first cell with text, or just to the first column if there is no more text to the left. But why? In excel, home key sends you to the first column always. We might want to change to that behavior. */ if (event->modifiers() & Qt::ControlModifier) { /* ctrl + Home will always just send us to location (1,1) */ destination = QPoint(1, 1); } else { QPoint marker = q->selection()->marker(); Cell cell = sheet->cellStorage()->firstInRow(marker.y(), CellStorage::VisitContent); while (!cell.isNull() && cell.column() < marker.x() && cell.isEmpty()) { cell = sheet->cellStorage()->nextInRow(cell.column(), cell.row(), CellStorage::VisitContent); } int col = (!cell.isNull() ? cell.column() : 1); if (col == marker.x()) col = 1; destination = QPoint(col, marker.y()); } if (q->selection()->marker() == destination) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); event->accept(); // QKeyEvent } return true; } bool CellToolBase::Private::processEndKey(QKeyEvent *event) { register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return false; bool makingSelection = event->modifiers() & Qt::ShiftModifier; Cell cell; QPoint marker = q->selection()->marker(); if (q->editor()) { // We are in edit mode -> go end of line QApplication::sendEvent(q->editor(), event); return false; } else { // move to the last used cell in the row int col = 1; cell = sheet->cellStorage()->lastInRow(marker.y(), CellStorage::VisitContent); while (!cell.isNull() && cell.column() > q->selection()->marker().x() && cell.isEmpty()) { cell = sheet->cellStorage()->prevInRow(cell.column(), cell.row(), CellStorage::VisitContent); } col = (cell.isNull()) ? q->maxCol() : cell.column(); QPoint destination(col, marker.y()); if (destination == marker) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); event->accept(); // QKeyEvent } return true; } bool CellToolBase::Private::processPriorKey(QKeyEvent *event) { bool makingSelection = event->modifiers() & Qt::ShiftModifier; q->selection()->emitCloseEditor(true); // save changes QPoint marker = q->selection()->marker(); QPoint destination(marker.x(), qMax(1, marker.y() - 10)); if (destination == marker) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, q->selection()->activeSheet()); } q->scrollToCell(destination); event->accept(); // QKeyEvent return true; } bool CellToolBase::Private::processNextKey(QKeyEvent *event) { bool makingSelection = event->modifiers() & Qt::ShiftModifier; q->selection()->emitCloseEditor(true); // save changes QPoint marker = q->selection()->marker(); QPoint destination(marker.x(), qMax(1, marker.y() + 10)); if (marker == destination) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, q->selection()->activeSheet()); } q->scrollToCell(destination); event->accept(); // QKeyEvent return true; } void CellToolBase::Private::processDeleteKey(QKeyEvent* event) { register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return; // Delete is also a valid editing key, process accordingly if (!q->editor()) { // Switch to editing mode q->createEditor(); } - q->editor()->handleKeyPressEvent(event); + QApplication::sendEvent(q->editor(), event); // TODO Stefan: Actually this check belongs into the command! if (sheet->areaIsEmpty(*q->selection())) return; DataManipulator* command = new DataManipulator(); command->setSheet(sheet); command->setText(i18n("Clear Text")); // parsing gets set only so that parseUserInput is called as it should be, // no actual parsing shall be done command->setParsing(true); command->setValue(Value("")); command->add(*q->selection()); command->execute(); updateEditor(Cell(q->selection()->activeSheet(), q->selection()->cursor())); event->accept(); // QKeyEvent } void CellToolBase::Private::processF2Key(QKeyEvent* event) { optionWidget->editor()->setFocus(); if (q->editor()) { + // Update the cursor position of the external editor. QTextCursor textCursor = optionWidget->editor()->textCursor(); textCursor.setPosition(q->editor()->cursorPosition()); optionWidget->editor()->setTextCursor(textCursor); } event->accept(); // QKeyEvent } void CellToolBase::Private::processF4Key(QKeyEvent* event) { /* passes F4 to the editor (if any), which will process it */ if (q->editor()) { - q->editor()->handleKeyPressEvent(event); + QApplication::sendEvent(q->editor(), event); + // Update the cursor position of the external editor. QTextCursor textCursor = optionWidget->editor()->textCursor(); textCursor.setPosition(q->editor()->cursorPosition()); optionWidget->editor()->setTextCursor(textCursor); } } void CellToolBase::Private::processOtherKey(QKeyEvent *event) { register Sheet * const sheet = q->selection()->activeSheet(); // No null character ... if (event->text().isEmpty() || !q->selection()->activeSheet()->map()->isReadWrite() || !sheet || sheet->isProtected()) { event->accept(); // QKeyEvent } else { if (!q->editor()) { // Switch to editing mode q->createEditor(); } - q->editor()->handleKeyPressEvent(event); + // Send it to the embedded editor. + QApplication::sendEvent(q->editor(), event); } } bool CellToolBase::Private::processControlArrowKey(QKeyEvent *event) { register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return false; bool makingSelection = event->modifiers() & Qt::ShiftModifier; Cell cell; Cell lastCell; QPoint destination; bool searchThroughEmpty = true; int row; int col; QPoint marker = q->selection()->marker(); /* here, we want to move to the first or last cell in the given direction that is actually being used. Ignore empty cells and cells on hidden rows/columns */ switch (event->key()) { //Ctrl+Qt::Key_Up case Qt::Key_Up: cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.y() != 1)) { lastCell = cell; row = marker.y() - 1; cell = Cell(sheet, cell.column(), row); while ((!cell.isNull()) && (row > 0) && (!cell.isEmpty())) { if (!sheet->rowFormat(cell.row())->isHiddenOrFiltered()) { lastCell = cell; searchThroughEmpty = false; } row--; if (row > 0) cell = Cell(sheet, cell.column(), row); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->prevInColumn(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->rowFormat(cell.row())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->prevInColumn(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) row = 1; else row = cell.row(); while (sheet->rowFormat(row)->isHiddenOrFiltered()) { row++; } destination.setX(qBound(1, marker.x(), q->maxCol())); destination.setY(qBound(1, row, q->maxRow())); break; //Ctrl+Qt::Key_Down case Qt::Key_Down: cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.y() != q->maxRow())) { lastCell = cell; row = marker.y() + 1; cell = Cell(sheet, cell.column(), row); while ((!cell.isNull()) && (row < q->maxRow()) && (!cell.isEmpty())) { if (!(sheet->rowFormat(cell.row())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } row++; cell = Cell(sheet, cell.column(), row); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->nextInColumn(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->rowFormat(cell.row())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->nextInColumn(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) row = marker.y(); else row = cell.row(); while (sheet->rowFormat(row)->isHiddenOrFiltered()) { row--; } destination.setX(qBound(1, marker.x(), q->maxCol())); destination.setY(qBound(1, row, q->maxRow())); break; //Ctrl+Qt::Key_Left case Qt::Key_Left: if (sheet->layoutDirection() == Qt::RightToLeft) { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != q->maxCol())) { lastCell = cell; col = marker.x() + 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col < q->maxCol()) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col++; cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->nextInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->nextInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = marker.x(); else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col--; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } else { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != 1)) { lastCell = cell; col = marker.x() - 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col > 0) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col--; if (col > 0) cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->prevInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->prevInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = 1; else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col++; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } break; //Ctrl+Qt::Key_Right case Qt::Key_Right: if (sheet->layoutDirection() == Qt::RightToLeft) { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != 1)) { lastCell = cell; col = marker.x() - 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col > 0) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col--; if (col > 0) cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->prevInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->prevInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = 1; else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col++; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } else { cell = Cell(sheet, marker.x(), marker.y()); if ((!cell.isNull()) && (!cell.isEmpty()) && (marker.x() != q->maxCol())) { lastCell = cell; col = marker.x() + 1; cell = Cell(sheet, col, cell.row()); while ((!cell.isNull()) && (col < q->maxCol()) && (!cell.isEmpty())) { if (!(sheet->columnFormat(cell.column())->isHiddenOrFiltered())) { lastCell = cell; searchThroughEmpty = false; } col++; cell = Cell(sheet, col, cell.row()); } cell = lastCell; } if (searchThroughEmpty) { cell = sheet->cellStorage()->nextInRow(marker.x(), marker.y(), CellStorage::VisitContent); while ((!cell.isNull()) && (cell.isEmpty() || (sheet->columnFormat(cell.column())->isHiddenOrFiltered()))) { cell = sheet->cellStorage()->nextInRow(cell.column(), cell.row(), CellStorage::VisitContent); } } if (cell.isNull()) col = marker.x(); else col = cell.column(); while (sheet->columnFormat(col)->isHiddenOrFiltered()) { col--; } destination.setX(qBound(1, col, q->maxCol())); destination.setY(qBound(1, marker.y(), q->maxRow())); } break; } if (marker == destination) return false; if (makingSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); return true; } bool CellToolBase::Private::formatKeyPress(QKeyEvent * _ev) { if (!(_ev->modifiers() & Qt::ControlModifier)) return false; int key = _ev->key(); if (key != Qt::Key_Exclam && key != Qt::Key_At && key != Qt::Key_Ampersand && key != Qt::Key_Dollar && key != Qt::Key_Percent && key != Qt::Key_AsciiCircum && key != Qt::Key_NumberSign) return false; StyleCommand* command = new StyleCommand(); command->setSheet(q->selection()->activeSheet()); switch (_ev->key()) { case Qt::Key_Exclam: command->setText(i18n("Number Format")); command->setFormatType(Format::Number); command->setPrecision(2); break; case Qt::Key_Dollar: command->setText(i18n("Currency Format")); command->setFormatType(Format::Money); command->setPrecision(q->selection()->activeSheet()->map()->calculationSettings()->locale()->fracDigits()); break; case Qt::Key_Percent: command->setText(i18n("Percentage Format")); command->setFormatType(Format::Percentage); break; case Qt::Key_At: command->setText(i18n("Time Format")); command->setFormatType(Format::SecondeTime); break; case Qt::Key_NumberSign: command->setText(i18n("Date Format")); command->setFormatType(Format::ShortDate); break; case Qt::Key_AsciiCircum: command->setText(i18n("Scientific Format")); command->setFormatType(Format::Scientific); break; case Qt::Key_Ampersand: command->setText(i18n("Change Border")); command->setTopBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setBottomBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setLeftBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); command->setRightBorderPen(QPen(q->canvas()->resourceManager()->foregroundColor().toQColor(), 1, Qt::SolidLine)); break; default: delete command; return false; } command->add(*q->selection()); command->execute(); _ev->accept(); // QKeyEvent return true; } QRect CellToolBase::Private::moveDirection(KSpread::MoveTo direction, bool extendSelection) { kDebug(36005) << "Canvas::moveDirection"; register Sheet * const sheet = q->selection()->activeSheet(); if (!sheet) return QRect(); QPoint destination; QPoint cursor = q->selection()->cursor(); QPoint cellCorner = cursor; Cell cell(sheet, cursor.x(), cursor.y()); /* cell is either the same as the marker, or the cell that is forced obscuring the marker cell */ if (cell.isPartOfMerged()) { cell = cell.masterCell(); cellCorner = QPoint(cell.column(), cell.row()); } /* how many cells must we move to get to the next cell? */ int offset = 0; const RowFormat *rl = 0; const ColumnFormat *cl = 0; switch (direction) /* for each case, figure out how far away the next cell is and then keep going one row/col at a time after that until a visible row/col is found NEVER use cell.column() or cell.row() -- it might be a default cell */ { case Bottom: offset = cell.mergedYCells() - (cursor.y() - cellCorner.y()) + 1; rl = sheet->rowFormat(cursor.y() + offset); while (((cursor.y() + offset) <= q->maxRow()) && rl->isHiddenOrFiltered()) { offset++; rl = sheet->rowFormat(cursor.y() + offset); } destination = QPoint(cursor.x(), qMin(cursor.y() + offset, q->maxRow())); break; case Top: offset = (cellCorner.y() - cursor.y()) - 1; rl = sheet->rowFormat(cursor.y() + offset); while (((cursor.y() + offset) >= 1) && rl->isHiddenOrFiltered()) { offset--; rl = sheet->rowFormat(cursor.y() + offset); } destination = QPoint(cursor.x(), qMax(cursor.y() + offset, 1)); break; case Left: offset = (cellCorner.x() - cursor.x()) - 1; cl = sheet->columnFormat(cursor.x() + offset); while (((cursor.x() + offset) >= 1) && cl->isHiddenOrFiltered()) { offset--; cl = sheet->columnFormat(cursor.x() + offset); } destination = QPoint(qMax(cursor.x() + offset, 1), cursor.y()); break; case Right: offset = cell.mergedXCells() - (cursor.x() - cellCorner.x()) + 1; cl = sheet->columnFormat(cursor.x() + offset); while (((cursor.x() + offset) <= q->maxCol()) && cl->isHiddenOrFiltered()) { offset++; cl = sheet->columnFormat(cursor.x() + offset); } destination = QPoint(qMin(cursor.x() + offset, q->maxCol()), cursor.y()); break; case BottomFirst: offset = cell.mergedYCells() - (cursor.y() - cellCorner.y()) + 1; rl = sheet->rowFormat(cursor.y() + offset); while (((cursor.y() + offset) <= q->maxRow()) && rl->isHiddenOrFiltered()) { ++offset; rl = sheet->rowFormat(cursor.y() + offset); } destination = QPoint(1, qMin(cursor.y() + offset, q->maxRow())); break; case NoMovement: destination = cursor; break; } if (extendSelection) { q->selection()->update(destination); } else { q->selection()->initialize(destination, sheet); } q->scrollToCell(destination); updateEditor(Cell(q->selection()->activeSheet(), q->selection()->cursor())); return QRect(cursor, destination); } void CellToolBase::Private::paintSelection(QPainter &painter, const QRectF &viewRect) { if (q->selection()->referenceSelection() || q->editor()) { return; } Sheet *const sheet = q->selection()->activeSheet(); // save the painter state painter.save(); // disable antialiasing painter.setRenderHint(QPainter::Antialiasing, false); // Extend the clip rect by one in each direction to avoid artefacts caused by rounding errors. // TODO Stefan: This unites the region's rects. May be bad. Check! painter.setClipRegion(painter.clipRegion().boundingRect().adjusted(-1, -1, 1, 1)); QLineF line; QPen pen(QApplication::palette().text().color(), q->canvas()->viewConverter()->viewToDocumentX(2.0)); painter.setPen(pen); const KSpread::Selection* selection = q->selection(); const QRect currentRange = selection->extendToMergedAreas(QRect(selection->anchor(), selection->marker())); Region::ConstIterator end(selection->constEnd()); for (Region::ConstIterator it(selection->constBegin()); it != end; ++it) { const QRect range = (*it)->isAll() ? (*it)->rect() : selection->extendToMergedAreas((*it)->rect()); // Only the active element (the one with the anchor) will be drawn with a border const bool current = (currentRange == range); double positions[4]; bool paintSides[4]; retrieveMarkerInfo(range, viewRect, positions, paintSides); double left = positions[0]; double top = positions[1]; double right = positions[2]; double bottom = positions[3]; if (sheet->layoutDirection() == Qt::RightToLeft) { // The painter's origin is translated by the negative canvas offset. // viewRect.left() is the canvas offset. Add it once to the // coordinates. Then, the upper left corner of the canvas has to // match the correct document position, which is the scrolling // offset (viewRect.left()) plus the width of the visible area // (viewRect.width()); that's the right border (left+width). const qreal offset = /*2 * viewRect.left() +*/ viewRect.width(); left = offset - positions[2]; right = offset - positions[0]; } bool paintLeft = paintSides[0]; bool paintTop = paintSides[1]; bool paintRight = paintSides[2]; bool paintBottom = paintSides[3]; if (sheet->layoutDirection() == Qt::RightToLeft) { paintLeft = paintSides[2]; paintRight = paintSides[0]; } const double unzoomedPixelX = q->canvas()->viewConverter()->viewToDocumentX(1.0); const double unzoomedPixelY = q->canvas()->viewConverter()->viewToDocumentY(1.0); // get the transparent selection color QColor selectionColor(QApplication::palette().highlight().color()); selectionColor.setAlpha(127); if (current) { // save old clip region const QRegion clipRegion = painter.clipRegion(); // clip out the cursor region const QRect cursor = QRect(selection->cursor(), selection->cursor()); const QRect extCursor = selection->extendToMergedAreas(cursor); QRectF cursorRect = sheet->cellCoordinatesToDocument(extCursor); if (sheet->layoutDirection() == Qt::RightToLeft) { // See comment above. const qreal offset = /*2 * viewRect.left() +*/ viewRect.width(); const qreal left = offset - cursorRect.right(); const qreal right = offset - cursorRect.left(); cursorRect.setLeft(left); cursorRect.setRight(right); } cursorRect.adjust(unzoomedPixelX, unzoomedPixelY, unzoomedPixelX, unzoomedPixelY); painter.setClipRegion(clipRegion.subtracted(cursorRect.toRect())); // draw the transparent selection background painter.fillRect(QRectF(left, top, right - left, bottom - top), selectionColor); // restore clip region painter.setClipRegion(clipRegion); } else { // draw the transparent selection background painter.fillRect(QRectF(left, top, right - left, bottom - top), selectionColor); } if (paintTop) { line = QLineF(left, top, right, top); painter.drawLine(line); } if (selection->activeSheet()->layoutDirection() == Qt::RightToLeft) { if (paintRight) { line = QLineF(right, top, right, bottom); painter.drawLine(line); } if (paintLeft && paintBottom && current) { /* then the 'handle' in the bottom left corner is visible. */ line = QLineF(left, top, left, bottom - 3 * unzoomedPixelY); painter.drawLine(line); line = QLineF(left + 4 * unzoomedPixelX, bottom, right + unzoomedPixelY, bottom); painter.drawLine(line); painter.fillRect(QRectF(left - 2 * unzoomedPixelX, bottom - 2 * unzoomedPixelY, 5 * unzoomedPixelX, 5 * unzoomedPixelY), painter.pen().color()); } else { if (paintLeft) { line = QLineF(left, top, left, bottom); painter.drawLine(line); } if (paintBottom) { line = QLineF(left, bottom, right, bottom); painter.drawLine(line); } } } else { // activeSheet()->layoutDirection() == Qt::LeftToRight if (paintLeft) { line = QLineF(left, top, left, bottom); painter.drawLine(line); } if (paintRight && paintBottom && current) { /* then the 'handle' in the bottom right corner is visible. */ line = QLineF(right, top, right, bottom - 3 * unzoomedPixelY); painter.drawLine(line); line = QLineF(left, bottom, right - 3 * unzoomedPixelX, bottom); painter.drawLine(line); painter.fillRect(QRectF(right - 2 * unzoomedPixelX, bottom - 2 * unzoomedPixelX, 5 * unzoomedPixelX, 5 * unzoomedPixelY), painter.pen().color()); } else { if (paintRight) { line = QLineF(right, top, right, bottom); painter.drawLine(line); } if (paintBottom) { line = QLineF(left, bottom, right, bottom); painter.drawLine(line); } } } } // restore painter state painter.restore(); } void CellToolBase::Private::paintReferenceSelection(QPainter &painter, const QRectF &viewRect) { Q_UNUSED(viewRect); if (!q->selection()->referenceSelection()) { return; } // save painter state painter.save(); // Define the reference selection handle. const qreal pixelX = q->canvas()->viewConverter()->viewToDocumentX(1); const qreal pixelY = q->canvas()->viewConverter()->viewToDocumentY(1); const QRectF handleArea(-3 * pixelX, -3 * pixelY, 6 * pixelX, 6 * pixelY); + // A list of already found regions to color the same region with the same color. + QSet alreadyFoundRegions; // The colors for the referenced ranges and the color index. const QList colors = q->selection()->colors(); int index = 0; // Iterate over the referenced ranges. const Region::ConstIterator end(q->selection()->constEnd()); for (Region::ConstIterator it(q->selection()->constBegin()); it != end; ++it) { Sheet *const sheet = (*it)->sheet(); // Only paint ranges or cells on the current sheet if (sheet != q->selection()->activeSheet()) { index++; continue; } + // Only paint a reference once. + if (alreadyFoundRegions.contains((*it)->name())) { + continue; + } + alreadyFoundRegions.insert((*it)->name()); const QRect range = q->selection()->extendToMergedAreas((*it)->rect()); QRectF area = sheet->cellCoordinatesToDocument(range); // Convert region from sheet coordinates to canvas coordinates for use with the painter // retrieveMarkerInfo(region,viewRect,positions,paintSides); // Now adjust the highlight rectangle is slightly inside the cell borders (this means // that multiple highlighted cells look nicer together as the borders do not clash) area.adjust(pixelX, pixelY, -pixelX, -pixelY); // The current color. const QColor color = colors[index++ % colors.size()]; // Paint the reference range's outline. if ((*it)->sheet()->layoutDirection() == Qt::RightToLeft) { // See comment in paintSelection(). const qreal offset = /*2 * viewRect.left() +*/ viewRect.width(); const qreal left = offset - area.right(); const qreal right = offset - area.left(); area.setLeft(left); area.setRight(right); } painter.setBrush(QBrush()); painter.setPen(color); painter.drawRect(area); // Now draw the size grip (the little rectangle on the bottom right-hand corner of // the range which the user can click and drag to resize the region) painter.setPen(Qt::white); painter.setBrush(color); const bool rtl = sheet->layoutDirection() == Qt::RightToLeft; const QPointF corner(rtl ? area.bottomLeft() : area.bottomRight()); painter.drawRect(handleArea.translated(corner)); } // restore painter state painter.restore(); } void CellToolBase::Private::retrieveMarkerInfo(const QRect &cellRange, const QRectF &viewRect, double positions[], bool paintSides[]) { // Everything is in document coordinates here. // The layout direction, which is view dependent, is applied afterwards. const Sheet* sheet = q->selection()->activeSheet(); const QRectF visibleRect = sheet->cellCoordinatesToDocument(cellRange); /* these vars are used for clarity, the array for simpler function arguments */ qreal left = visibleRect.left(); qreal top = visibleRect.top(); qreal right = visibleRect.right(); qreal bottom = visibleRect.bottom(); /* left, top, right, bottom */ paintSides[0] = (viewRect.left() <= left) && (left <= viewRect.right()) && (bottom >= viewRect.top()) && (top <= viewRect.bottom()); paintSides[1] = (viewRect.top() <= top) && (top <= viewRect.bottom()) && (right >= viewRect.left()) && (left <= viewRect.right()); paintSides[2] = (viewRect.left() <= right) && (right <= viewRect.right()) && (bottom >= viewRect.top()) && (top <= viewRect.bottom()); paintSides[3] = (viewRect.top() <= bottom) && (bottom <= viewRect.bottom()) && (right >= viewRect.left()) && (left <= viewRect.right()); positions[0] = qMax(left, viewRect.left()); positions[1] = qMax(top, viewRect.top()); positions[2] = qMin(right, viewRect.right()); positions[3] = qMin(bottom, viewRect.bottom()); } QList CellToolBase::Private::popupActionList() const { QList actions; const Cell cell = Cell(q->selection()->activeSheet(), q->selection()->marker()); const bool isProtected = !q->selection()->activeSheet()->map()->isReadWrite() || (q->selection()->activeSheet()->isProtected() && !(cell.style().notProtected() && q->selection()->isSingular())); if (!isProtected) { actions.append(q->action("cellStyle")); actions.append(popupMenuActions["separator1"]); actions.append(q->action("cut")); } actions.append(q->action("copy")); if (!isProtected) { actions.append(q->action("paste")); actions.append(q->action("specialPaste")); actions.append(q->action("pasteWithInsertion")); actions.append(popupMenuActions["separator2"]); actions.append(q->action("clearAll")); actions.append(q->action("adjust")); actions.append(q->action("setDefaultStyle")); actions.append(q->action("setAreaName")); if (!q->selection()->isColumnOrRowSelected()) { actions.append(popupMenuActions["separator3"]); actions.append(popupMenuActions["insertCell"]); actions.append(popupMenuActions["deleteCell"]); } else if (q->selection()->isColumnSelected()) { actions.append(q->action("resizeCol")); actions.append(popupMenuActions["adjustColumn"]); actions.append(popupMenuActions["separator4"]); actions.append(popupMenuActions["insertColumn"]); actions.append(popupMenuActions["deleteColumn"]); actions.append(q->action("hideColumn")); q->action("showSelColumns")->setEnabled(false); const ColumnFormat* columnFormat; Region::ConstIterator endOfList = q->selection()->constEnd(); for (Region::ConstIterator it = q->selection()->constBegin(); it != endOfList; ++it) { QRect range = (*it)->rect(); int col; for (col = range.left(); col < range.right(); ++col) { columnFormat = q->selection()->activeSheet()->columnFormat(col); if (columnFormat->isHidden()) { q->action("showSelColumns")->setEnabled(true); actions.append(q->action("showSelColumns")); break; } } if (range.left() > 1 && col == range.right()) { bool allHidden = true; for (col = 1; col < range.left(); ++col) { columnFormat = q->selection()->activeSheet()->columnFormat(col); allHidden &= columnFormat->isHidden(); } if (allHidden) { q->action("showSelColumns")->setEnabled(true); actions.append(q->action("showSelColumns")); break; } } else { break; } } } else if (q->selection()->isRowSelected()) { actions.append(q->action("resizeRow")); actions.append(popupMenuActions["adjustRow"]); actions.append(popupMenuActions["separator5"]); actions.append(popupMenuActions["insertRow"]); actions.append(popupMenuActions["deleteRow"]); actions.append(q->action("hideRow")); q->action("showSelRows")->setEnabled(false); const RowFormat* rowFormat; Region::ConstIterator endOfList = q->selection()->constEnd(); for (Region::ConstIterator it = q->selection()->constBegin(); it != endOfList; ++it) { QRect range = (*it)->rect(); int row; for (row = range.top(); row < range.bottom(); ++row) { rowFormat = q->selection()->activeSheet()->rowFormat(row); if (rowFormat->isHidden()) { q->action("showSelRows")->setEnabled(true); actions.append(q->action("showSelRows")); break; } } if (range.top() > 1 && row == range.bottom()) { bool allHidden = true; for (row = 1; row < range.top(); ++row) { rowFormat = q->selection()->activeSheet()->rowFormat(row); allHidden &= rowFormat->isHidden(); } if (allHidden) { q->action("showSelRows")->setEnabled(true); actions.append(q->action("showSelRows")); break; } } else { break; } } } actions.append(popupMenuActions["separator6"]); actions.append(q->action("comment")); if (!cell.comment().isEmpty()) { actions.append(q->action("clearComment")); } if (testListChoose(q->selection())) { actions.append(popupMenuActions["separator7"]); actions.append(popupMenuActions["listChoose"]); } } return actions; } void CellToolBase::Private::createPopupMenuActions() { QAction* action = 0; for (int i = 1; i <= 7; ++i) { action = new QAction(q); action->setSeparator(true); popupMenuActions.insert(QString("separator%1").arg(i), action); } action = new KAction(KIcon("insertcell"), i18n("Insert Cells..."), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(insertCells())); popupMenuActions.insert("insertCell", action); action = new KAction(KIcon("removecell"), i18n("Delete Cells..."), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(deleteCells())); popupMenuActions.insert("deleteCell", action); action = new KAction(KIcon("adjustcol"), i18n("Adjust Column"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(adjustColumn())); popupMenuActions.insert("adjustColumn", action); action = new KAction(KIcon("insert_table_col"), i18n("Insert Columns"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(insertColumn())); popupMenuActions.insert("insertColumn", action); action = new KAction(KIcon("delete_table_col"), i18n("Delete Columns"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(deleteColumn())); popupMenuActions.insert("deleteColumn", action); action = new KAction(KIcon("adjustrow"), i18n("Adjust Row"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(adjustRow())); popupMenuActions.insert("adjustRow", action); action = new KAction(KIcon("insert_table_row"), i18n("Insert Rows"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(insertRow())); popupMenuActions.insert("insertRow", action); action = new KAction(KIcon("delete_table_row"), i18n("Delete Rows"), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(deleteRow())); popupMenuActions.insert("deleteRow", action); action = new KAction(i18n("Selection List..."), q); connect(action, SIGNAL(triggered(bool)), q, SLOT(listChoosePopupMenu())); popupMenuActions.insert("listChoose", action); } bool CellToolBase::Private::testListChoose(Selection *selection) const { const Sheet *const sheet = selection->activeSheet(); const Cell cursorCell(sheet, selection->cursor()); const CellStorage *const storage = sheet->cellStorage(); const Region::ConstIterator end(selection->constEnd()); for (Region::ConstIterator it(selection->constBegin()); it != end; ++it) { const QRect range = (*it)->rect(); if (cursorCell.column() < range.left() || cursorCell.column() > range.right()) { continue; // next range } Cell cell; if (range.top() == 1) { cell = storage->firstInColumn(cursorCell.column(), CellStorage::Values); } else { cell = storage->nextInColumn(cursorCell.column(), range.top() - 1, CellStorage::Values); } while (!cell.isNull() && cell.row() <= range.bottom()) { if (cell.isDefault() || cell.isPartOfMerged() || cell.isFormula() || cell.isTime() || cell.isDate() || cell.value().isNumber() || cell.value().asString().isEmpty() || (cell == cursorCell)) { cell = storage->nextInColumn(cell.column(), cell.row(), CellStorage::Values); continue; } if (cell.userInput() != cursorCell.userInput()) { return true; } cell = storage->nextInColumn(cell.column(), cell.row(), CellStorage::Values); } } return false; } diff --git a/kspread/ui/Editors.cpp b/kspread/ui/Editors.cpp index 830455c669..38fd5637d3 100644 --- a/kspread/ui/Editors.cpp +++ b/kspread/ui/Editors.cpp @@ -1,1823 +1,1863 @@ /* This file is part of the KDE project Copyright 1999-2006 The KSpread Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Editors.h" // KSpread #include "ApplicationSettings.h" #include "Cell.h" #include "CellStorage.h" #include "CellToolBase.h" #include "Formula.h" #include "Functions.h" #include "Map.h" #include "NamedAreaManager.h" #include "Selection.h" #include "Sheet.h" #include "Style.h" // KOffice #include #include #include // KDE #include #include #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "commands/NamedAreaCommand.h" using namespace KSpread; /***************************************************************************** * * FormulaEditorHighlighter * ****************************************************************************/ namespace KSpread { class FormulaEditorHighlighter::Private { public: Private() { selection = 0; tokens = Tokens(); rangeCount = 0; rangeChanged = false; } // source for cell reference checking Selection* selection; Tokens tokens; uint rangeCount; bool rangeChanged; }; FormulaEditorHighlighter::FormulaEditorHighlighter(QTextEdit* textEdit, Selection* selection) : QSyntaxHighlighter(textEdit) , d(new Private) { d->selection = selection; } FormulaEditorHighlighter::~FormulaEditorHighlighter() { delete d; } const Tokens& FormulaEditorHighlighter::formulaTokens() const { return d->tokens; } void FormulaEditorHighlighter::highlightBlock(const QString& text) { // reset syntax highlighting setFormat(0, text.length(), QApplication::palette().text().color()); // save the old ones to identify range changes Tokens oldTokens = d->tokens; // interpret the text as formula // we accept invalid/incomplete formulas Formula f; d->tokens = f.scan(text); QFont editorFont = document()->defaultFont(); QFont font; uint oldRangeCount = d->rangeCount; d->rangeCount = 0; QList colors = d->selection->colors(); QList alreadyFoundRanges; + Sheet *const originSheet = d->selection->originSheet(); + Map *const map = originSheet->map(); + for (int i = 0; i < d->tokens.count(); ++i) { Token token = d->tokens[i]; Token::Type type = token.type(); switch (type) { case Token::Cell: case Token::Range: { // don't compare, if we have already found a change if (!d->rangeChanged && i < oldTokens.count() && token.text() != oldTokens[i].text()) { d->rangeChanged = true; } - Region newRange(token.text()); + const Region newRange(token.text(), map, originSheet); + if (!newRange.isValid()) { + continue; + } - if (!alreadyFoundRanges.contains(newRange.name())) { + int index = alreadyFoundRanges.indexOf(newRange.name()); + if (index == -1) /* not found */ { alreadyFoundRanges.append(newRange.name()); - d->rangeCount++; + index = alreadyFoundRanges.count() - 1; } - setFormat(token.pos() + 1, token.text().length(), colors[ alreadyFoundRanges.indexOf(newRange.name()) % colors.size()]); + const QColor color(colors[index % colors.size()]); + setFormat(token.pos() + 1, token.text().length(), color); + ++d->rangeCount; } break; case Token::Boolean: // True, False (also i18n-ized) /* font = QFont(editorFont); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font);*/ break; case Token::Identifier: // function name or named area*/ /* font = QFont(editorFont); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font);*/ break; case Token::Unknown: case Token::Integer: // 14, 3, 1977 case Token::Float: // 3.141592, 1e10, 5.9e-7 case Token::String: // "KOffice", "The quick brown fox..." case Token::Error: break; case Token::Operator: { // +, *, /, - switch (token.asOperator()) { case Token::LeftPar: case Token::RightPar: //Check where this brace is in relation to the cursor and highlight it if necessary. handleBrace(i); break; default: break; } } break; } } if (oldRangeCount != d->rangeCount) d->rangeChanged = true; } void FormulaEditorHighlighter::handleBrace(uint index) { const Token& token = d->tokens.at(index); QTextEdit* textEdit = qobject_cast(parent()); Q_ASSERT(textEdit); int cursorPos = textEdit->textCursor().position(); int distance = cursorPos - token.pos(); int opType = token.asOperator(); bool highlightBrace = false; //Check where the cursor is in relation to this left or right parenthesis token. //Only one pair of braces should be highlighted at a time, and if the cursor //is between two braces, the inner-most pair should be highlighted. if (opType == Token::LeftPar) { //If cursor is directly to the left of this left brace, highlight it if (distance == 1) highlightBrace = true; else //Cursor is directly to the right of this left brace, highlight it unless //there is another left brace to the right (in which case that should be highlighted instead as it //is the inner-most brace) if (distance == 2) if ((index == (uint)d->tokens.count() - 1) || (d->tokens.at(index + 1).asOperator() != Token::LeftPar)) highlightBrace = true; } else { //If cursor is directly to the right of this right brace, highlight it if (distance == 2) highlightBrace = true; else //Cursor is directly to the left of this right brace, so highlight it unless //there is another right brace to the left (in which case that should be highlighted instead as it //is the inner-most brace) if (distance == 1) if ((index == 0) || (d->tokens.at(index - 1).asOperator() != Token::RightPar)) highlightBrace = true; } if (highlightBrace) { QFont font = QFont(document()->defaultFont()); font.setBold(true); setFormat(token.pos() + 1, token.text().length(), font); int matching = findMatchingBrace(index); if (matching != -1) { Token matchingBrace = d->tokens.at(matching); setFormat(matchingBrace.pos() + 1 , matchingBrace.text().length() , font); } } } int FormulaEditorHighlighter::findMatchingBrace(int pos) { int depth = 0; int step = 0; Tokens tokens = d->tokens; //If this is a left brace we need to step forwards through the text to find the matching right brace, //otherwise, it is a right brace so we need to step backwards through the text to find the matching left //brace. if (tokens.at(pos).asOperator() == Token::LeftPar) step = 1; else step = -1; for (int index = pos ; (index >= 0) && (index < (int) tokens.count()) ; index += step) { if (tokens.at(index).asOperator() == Token::LeftPar) depth++; if (tokens.at(index).asOperator() == Token::RightPar) depth--; if (depth == 0) { return index; } } return -1; } uint FormulaEditorHighlighter::rangeCount() const { return d->rangeCount; } bool FormulaEditorHighlighter::rangeChanged() const { return d->rangeChanged; } void FormulaEditorHighlighter::resetRangeChanged() { d->rangeChanged = false; } } // namespace KSpread /***************************************************************************** * * FunctionCompletion * ****************************************************************************/ class FunctionCompletion::Private { public: CellEditor* editor; QFrame *completionPopup; KListWidget *completionListBox; QLabel* hintLabel; }; FunctionCompletion::FunctionCompletion(CellEditor* editor) : QObject(editor) , d(new Private) { d->editor = editor; d->hintLabel = 0; d->completionPopup = new QFrame(editor->topLevelWidget(), Qt::Popup); d->completionPopup->setFrameStyle(QFrame::Box | QFrame::Plain); d->completionPopup->setLineWidth(1); d->completionPopup->installEventFilter(this); d->completionPopup->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); QVBoxLayout *layout = new QVBoxLayout(d->completionPopup); layout->setMargin(0); layout->setSpacing(0); d->completionListBox = new KListWidget(d->completionPopup); d->completionPopup->setFocusProxy(d->completionListBox); d->completionListBox->setFrameStyle(QFrame::NoFrame); // d->completionListBox->setVariableWidth( true ); d->completionListBox->installEventFilter(this); connect(d->completionListBox, SIGNAL(currentRowChanged(int)), SLOT(itemSelected())); // When items are activated on single click, also change the help page on mouse-over, otherwise there is no (easy) way to get // the help (with the mouse) without inserting the function if (d->completionListBox->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, d->completionListBox)) { connect(d->completionListBox, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(itemSelected(QListWidgetItem*))); d->completionListBox->setMouseTracking(true); } connect(d->completionListBox, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(doneCompletion())); layout->addWidget(d->completionListBox); d->hintLabel = new QLabel(0, Qt::FramelessWindowHint | Qt::Tool | Qt::X11BypassWindowManagerHint); d->hintLabel->setFrameStyle(QFrame::Plain | QFrame::Box); d->hintLabel->setPalette(QToolTip::palette()); d->hintLabel->setWordWrap(true); d->hintLabel->hide(); } FunctionCompletion::~FunctionCompletion() { delete d->hintLabel; delete d; } void FunctionCompletion::itemSelected(QListWidgetItem* listItem) { QString item; if (listItem) { item = listItem->text(); } else { listItem = d->completionListBox->currentItem(); if (listItem) { item = listItem->text(); } } KSpread::FunctionDescription* desc; desc = KSpread::FunctionRepository::self()->functionInfo(item); if (!desc) { d->hintLabel->hide(); return; } const QStringList helpTexts = desc->helpText(); QString helpText = helpTexts.isEmpty() ? QString() : helpTexts.first(); if (helpText.isEmpty()) { d->hintLabel->hide(); return; } helpText.append("").prepend(""); d->hintLabel->setText(helpText); d->hintLabel->adjustSize(); // reposition nicely QPoint pos = d->editor->mapToGlobal(QPoint(d->editor->width(), 0)); pos.setY(pos.y() - d->hintLabel->height() - 1); d->hintLabel->move(pos); d->hintLabel->show(); d->hintLabel->raise(); // do not show it forever //QTimer::singleShot( 5000, d->hintLabel, SLOT( hide()) ); } bool FunctionCompletion::eventFilter(QObject *obj, QEvent *ev) { if (obj != d->completionPopup && obj != d->completionListBox) return false; if (ev->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(ev); switch (ke->key()) { case Qt::Key_Enter: case Qt::Key_Return: doneCompletion(); return true; case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Home: case Qt::Key_End: case Qt::Key_PageUp: case Qt::Key_PageDown: return false; default: d->hintLabel->hide(); d->completionPopup->close(); d->editor->setFocus(); QApplication::sendEvent(d->editor, ev); return true; } } if (ev->type() == QEvent::Close) { d->hintLabel->hide(); } if (ev->type() == QEvent::MouseButtonDblClick) { doneCompletion(); return true; } return false; } void FunctionCompletion::doneCompletion() { d->hintLabel->hide(); d->completionPopup->close(); d->editor->setFocus(); emit selectedCompletion(d->completionListBox->currentItem()->text()); } void FunctionCompletion::showCompletion(const QStringList &choices) { if (!choices.count()) return; d->completionListBox->clear(); d->completionListBox->addItems(choices); d->completionListBox->setCurrentItem(0); // size of the pop-up d->completionPopup->setMaximumHeight(100); d->completionPopup->resize(d->completionListBox->sizeHint() + QSize(d->completionListBox->verticalScrollBar()->width() + 4, d->completionListBox->horizontalScrollBar()->height() + 4)); int h = d->completionListBox->height(); int w = d->completionListBox->width(); QPoint pos = d->editor->globalCursorPosition(); // if popup is partially invisible, move to other position // FIXME check it if it works in Xinerama multihead int screen_num = QApplication::desktop()->screenNumber(d->completionPopup); QRect screen = QApplication::desktop()->screenGeometry(screen_num); if (pos.y() + h > screen.y() + screen.height()) pos.setY(pos.y() - h - d->editor->height()); if (pos.x() + w > screen.x() + screen.width()) pos.setX(screen.x() + screen.width() - w); d->completionPopup->move(pos); d->completionListBox->setFocus(); d->completionPopup->show(); } /**************************************************************************** * * CellEditor * ****************************************************************************/ class CellEditor::Private { public: CellToolBase* cellTool; - Cell cell; Selection* selection; KTextEdit* textEdit; FormulaEditorHighlighter* highlighter; FunctionCompletion* functionCompletion; QTimer* functionCompletionTimer; QPoint globalCursorPos; bool captureAllKeyEvents : 1; -bool checkChoice : 1; -bool updateChoice : 1; -bool updatingChoice : 1; - - int length; - uint fontLength; - uint length_namecell; - uint length_text; + bool selectionChangedLocked : 1; + int currentToken; - uint rangeCount; + +public: + void updateActiveSubRegion(const Tokens &tokens); + void rebuildSelection(); }; +void CellEditor::Private::updateActiveSubRegion(const Tokens &tokens) +{ + // Index of the token, at which the text cursor is positioned. + // For sub-regions it is the start range. + currentToken = 0; + + if (tokens.isEmpty()) { + selection->setActiveSubRegion(0, 0); // also set the active element + return; + } + + const int cursorPosition = textEdit->textCursor().position() - 1; // without '=' + kDebug() << "cursorPosition:" << cursorPosition << "textLength:" << textEdit->toPlainText().length() - 1; + + uint rangeCounter = 0; // counts the ranges in the sub-region + uint currentRange = 0; // range index denoting the current range + int regionStart = 0; // range index denoting the sub-region start + uint regionEnd = 0; // range index denoting the sub-region end + enum { Anywhere, InRegion, BeyondCursor } state = Anywhere; + + Token token; + Token::Type type; + // Search the current range the text cursor is positioned to. + // Determine the subregion start and end, in which the range is located. + for (int i = 0; i < tokens.count(); ++i) { + token = tokens[i]; + type = token.type(); + + // If not in a subregion, we may already quit the loop here. + if (state == Anywhere) { + // Already beyond the cursor position? + if (token.pos() > cursorPosition) { + state = BeyondCursor; + break; // for loop + } + } else if (state == InRegion) { + // Loop to the end of the subregion. + if (type == Token::Cell || type == Token::Range) { + regionEnd = rangeCounter++; + continue; // keep going until the referenced region ends + } + if (type == Token::Operator) { + if (tokens[i].asOperator() == Token::Semicolon) { + continue; // keep going until the referenced region ends + } + } + state = Anywhere; + continue; + } + + // Can the token be replaced by a reference? + switch (type) { + case Token::Cell: + case Token::Range: + if (state == Anywhere) { + currentToken = i; + regionStart = rangeCounter; + state = InRegion; + } + regionEnd = rangeCounter; // length = 1 + currentRange = ++rangeCounter; // point behind the last + continue; + case Token::Unknown: + case Token::Boolean: + case Token::Integer: + case Token::Float: + case Token::String: + case Token::Error: + // Set the active sub-region start to the next range but + // with a length of 0, which results in inserting a new range + // to the selection on calling Selection::initialize() or + // Selection::update(). + currentToken = i; + regionStart = rangeCounter; // position of the next range + regionEnd = rangeCounter - 1; // length = 0 + currentRange = rangeCounter; + continue; + case Token::Operator: + case Token::Identifier: + continue; + } + } + + // Cursor not reached? I.e. the cursor is placed at the last token's end. + if (state == Anywhere) { + token = tokens.last(); + type = token.type(); + // Check the last token. + // It was processed, but maybe a reference can be placed behind it. + // Check, if the token can be replaced by a reference. + switch (type) { + case Token::Operator: + // Possible to place a reference behind the operator? + switch (token.asOperator()) { + case Token::Plus: + case Token::Minus: + case Token::Asterisk: + case Token::Slash: + case Token::Caret: + case Token::LeftPar: + case Token::Semicolon: + case Token::Equal: + case Token::NotEqual: + case Token::Less: + case Token::Greater: + case Token::LessEqual: + case Token::GreaterEqual: + // Append new references by pointing behind the last. + currentToken = tokens.count(); + regionStart = rangeCounter; + regionEnd = rangeCounter - 1; // length = 0 + currentRange = rangeCounter; + break; + case Token::InvalidOp: + case Token::RightPar: + case Token::Comma: + case Token::Ampersand: + case Token::Percent: + case Token::CurlyBra: + case Token::CurlyKet: + case Token::Pipe: + // reference cannot be placed behind + break; + } + break; + case Token::Unknown: + case Token::Boolean: + case Token::Integer: + case Token::Float: + case Token::String: + case Token::Identifier: + case Token::Error: + // currentToken = tokens.count() - 1; // already set + // Set the active sub-region start to the end of the selection + // with a length of 0, which results in appending a new range + // to the selection on calling Selection::initialize() or + // Selection::update(). + regionStart = rangeCounter; + regionEnd = rangeCounter - 1; // length = 0 + currentRange = rangeCounter; + break; + case Token::Cell: + case Token::Range: + // currentToken = tokens.count() - 1; // already set + // Set the last range as active one. It is not a sub-region, + // otherwise the state would have been InRegion. + regionStart = rangeCounter - 1; + regionEnd = rangeCounter - 1; // length = 1 + currentRange = rangeCounter; // point behind the last + break; + } + } + + const int regionLength = regionEnd - regionStart + 1; + kDebug() << "currentRange:" << currentRange << "regionStart:" << regionStart + << "regionEnd:" << regionEnd << "regionLength:" << regionLength; + + selection->setActiveSubRegion(regionStart, regionLength, currentRange); +} + CellEditor::CellEditor(CellToolBase *cellTool, QWidget* parent) - : QWidget(parent) + : KTextEdit(parent) , d(new Private) { d->cellTool = cellTool; d->selection = cellTool->selection(); - d->cell = Cell(d->selection->activeSheet(), d->selection->marker()); - d->textEdit = new KTextEdit(this); + d->textEdit = this; d->globalCursorPos = QPoint(); d->captureAllKeyEvents = d->selection->activeSheet()->map()->settings()->captureAllArrowKeys(); - d->checkChoice = true; - d->updateChoice = true; - d->updatingChoice = false; - d->length = 0; - d->fontLength = 0; - d->length_namecell = 0; - d->length_text = 0; + d->selectionChangedLocked = false; d->currentToken = 0; - d->rangeCount = 0; - QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight, this); - layout->setMargin(0); - layout->setSpacing(0); - layout->addWidget(d->textEdit); - - d->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - d->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - d->textEdit->setFrameStyle(QFrame::NoFrame); - d->textEdit->setLineWidth(0); - d->textEdit->installEventFilter(this); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setFrameStyle(QFrame::NoFrame); + setLineWidth(0); + document()->setDocumentMargin(0); + // setMinimumHeight(fontMetrics().height()); - d->highlighter = new FormulaEditorHighlighter(d->textEdit, d->selection); + d->highlighter = new FormulaEditorHighlighter(this, d->selection); d->functionCompletion = new FunctionCompletion(this); d->functionCompletionTimer = new QTimer(this); connect(d->functionCompletion, SIGNAL(selectedCompletion(const QString&)), SLOT(functionAutoComplete(const QString&))); - connect(d->textEdit, SIGNAL(textChanged()), SLOT(checkFunctionAutoComplete())); + connect( this, SIGNAL( textChanged() ), SLOT( checkFunctionAutoComplete() ) ); connect(d->functionCompletionTimer, SIGNAL(timeout()), SLOT(triggerFunctionAutoComplete())); - if (!d->cell.style().wrapText()) - d->textEdit->setWordWrapMode(QTextOption::NoWrap); - else - d->textEdit->setWordWrapMode(QTextOption::WordWrap); + const Cell cell(d->selection->activeSheet(), d->selection->marker()); + const bool wrapText = cell.style().wrapText(); + d->textEdit->setWordWrapMode(wrapText ? QTextOption::WordWrap: QTextOption::NoWrap); #if 0 // FIXME Implement a completion aware KTextEdit. - d->textEdit->setCompletionMode(selection()->view()->doc()->completionMode()); - d->textEdit->setCompletionObject(&selection()->view()->doc()->map()->stringCompletion(), true); + setCompletionMode(selection()->view()->doc()->completionMode()); + setCompletionObject(&selection()->view()->doc()->map()->stringCompletion(), true); #endif - setFocusProxy(d->textEdit); - - connect(d->textEdit, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged())); - connect(d->textEdit, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); - connect(d->textEdit->document(), SIGNAL(modificationChanged(bool)), - this, SIGNAL(modificationChanged(bool))); -// connect( d->textEdit, SIGNAL(completionModeChanged( KGlobalSettings::Completion )),this,SLOT (slotCompletionModeChanged(KGlobalSettings::Completion))); - - // A choose should always start at the edited cell -// selection()->setChooseMarkerRow( selection()->d->selection->marker().y() ); -// selection()->setChooseMarkerColumn( selection()->d->selection->marker().x() ); + connect( this, SIGNAL( cursorPositionChanged() ), this, SLOT (slotCursorPositionChanged())); + connect( this, SIGNAL( textChanged() ), this, SLOT( slotTextChanged() ) ); +// connect( this, SIGNAL(completionModeChanged( KGlobalSettings::Completion )),this,SLOT (slotCompletionModeChanged(KGlobalSettings::Completion))); } CellEditor::~CellEditor() { if (selection()) selection()->endReferenceSelection(); - delete d->highlighter; - delete d->functionCompletion; - delete d->functionCompletionTimer; delete d; } -const Cell& CellEditor::cell() const -{ - return d->cell; -} - Selection* CellEditor::selection() const { return d->selection; } QPoint CellEditor::globalCursorPosition() const { return d->globalCursorPos; } void CellEditor::checkFunctionAutoComplete() { + // Nothing to do, if no focus or not in reference selection mode. + if (!hasFocus() || !selection()->referenceSelection()) { + return; + } + d->functionCompletionTimer->stop(); d->functionCompletionTimer->setSingleShot(true); d->functionCompletionTimer->start(2000); } void CellEditor::triggerFunctionAutoComplete() { // tokenize the expression (don't worry, this is very fast) - int curPos = d->textEdit->textCursor().position(); - QString subtext = d->textEdit->toPlainText().left(curPos); + int curPos = textCursor().position(); + QString subtext = toPlainText().left( curPos ); KSpread::Formula f; KSpread::Tokens tokens = f.scan(subtext); if (!tokens.valid()) return; if (tokens.count() < 1) return; KSpread::Token lastToken = tokens[ tokens.count()-1 ]; // last token must be an identifier if (!lastToken.isIdentifier()) return; QString id = lastToken.text(); if (id.length() < 1) return; // find matches in function names QStringList fnames = KSpread::FunctionRepository::self()->functionNames(); QStringList choices; for (int i = 0; i < fnames.count(); i++) if (fnames[i].startsWith(id, Qt::CaseInsensitive)) choices.append(fnames[i]); choices.sort(); // no match, don't bother with completion if (!choices.count()) return; // single perfect match, no need to give choices if (choices.count() == 1) if (choices[0].toLower() == id.toLower()) return; // present the user with completion choices d->functionCompletion->showCompletion(choices); } void CellEditor::functionAutoComplete(const QString& item) { if (item.isEmpty()) return; - QTextCursor textCursor = d->textEdit->textCursor(); + QTextCursor textCursor = this->textCursor(); int curPos = textCursor.position(); - QString subtext = text().left(curPos); + QString subtext = toPlainText().left( curPos ); KSpread::Formula f; KSpread::Tokens tokens = f.scan(subtext); if (!tokens.valid()) return; if (tokens.count() < 1) return; KSpread::Token lastToken = tokens[ tokens.count()-1 ]; if (!lastToken.isIdentifier()) return; - d->textEdit->blockSignals(true); + blockSignals( true ); + // Select the incomplete function name in order to replace it. textCursor.setPosition(lastToken.pos() + 1); textCursor.setPosition(lastToken.pos() + lastToken.text().length() + 1, QTextCursor::KeepAnchor); - d->textEdit->setTextCursor(textCursor); - d->textEdit->insertPlainText(item); - d->textEdit->blockSignals(false); - // call slotTextChanged to sync the text between the inline editor and the editor in the docker - slotTextChanged(); + setTextCursor( textCursor ); + blockSignals( false ); + // Replace the incomplete function name with the selected one. + insertPlainText(item); } void CellEditor::slotCursorPositionChanged() { - // kDebug() <<"position:" << cursorPosition()<referenceSelection()) { + return; + } + // NOTE On text changes KTextEdit::cursorPositionChanged() is triggered + // before KTextEdit::textChanged(). The text is already up-to-date. - // TODO Stefan: optimize this function! + // Save the global position for the function auto-completion popup. + d->globalCursorPos = mapToGlobal(cursorRect().bottomLeft()); - // turn choose mode on/off - if (!checkChoice()) - return; + // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called + // automatically on text changes, which does the update. + d->updateActiveSubRegion(d->highlighter->formulaTokens()); +} - d->globalCursorPos = d->textEdit->mapToGlobal(d->textEdit->cursorRect().bottomLeft()); +void CellEditor::Private::rebuildSelection() +{ + // Do not react on selection changes, that update the formula's expression, + // because the selection gets already build based on the current formula. + selectionChangedLocked = true; - d->highlighter->rehighlight(); + Sheet *const originSheet = selection->originSheet(); + Map *const map = originSheet->map(); - Tokens tokens = d->highlighter->formulaTokens(); - uint rangeCounter = 0; - uint currentRange = 0; - int regionStart = -1; - uint regionEnd = 0; - bool lastWasASemicolon = false; - d->currentToken = 0; - uint rangeCount = d->highlighter->rangeCount(); - d->rangeCount = rangeCount; + // Rebuild the reference selection by using the formula tokens. + Tokens tokens = highlighter->formulaTokens(); + selection->update(); // set the old cursor dirty; updates the editors + selection->clear(); - Token token; - Token::Type type; - // search the current token - // determine the subregion number, btw + //A list of regions which have already been highlighted on the spreadsheet. + //This is so that we don't end up highlighting the same region twice in two different + //colors. + QSet alreadyUsedRegions; + + int counter = 0; for (int i = 0; i < tokens.count(); ++i) { - if (tokens[i].pos() >= d->textEdit->textCursor().position() - 1) { // without '=' - /* kDebug() <<"token.pos >= cursor.pos";*/ - type = tokens[i].type(); - if (type == Token::Cell || type == Token::Range) { - if (lastWasASemicolon) { - regionEnd = rangeCounter++; - lastWasASemicolon = false; - continue; - } - } - if (type == Token::Operator && tokens[i].asOperator() == Token::Semicolon) { - lastWasASemicolon = true; - continue; - } - lastWasASemicolon = false; - break; - } - token = tokens[i]; - d->currentToken = i; + const Token token = tokens[i]; + const Token::Type type = token.type(); - type = token.type(); if (type == Token::Cell || type == Token::Range) { - if (!lastWasASemicolon) { - regionStart = rangeCounter; + const Region region(token.text(), map, originSheet); + + if (!region.isValid() || region.isEmpty()) { + continue; } - regionEnd = rangeCounter; - currentRange = rangeCounter++; - } - // semicolons are use as deliminiters in regions - if (type == Token::Operator) { - if (token.asOperator() == Token::Semicolon) { - lastWasASemicolon = true; - } else { - lastWasASemicolon = false; - // set the region start to the next element - regionStart = currentRange + 1; - regionEnd = regionStart - 1; // len = 0 + if (alreadyUsedRegions.contains(region.name())) { + continue; } - } - } + alreadyUsedRegions.insert(region.name()); -// kDebug() <<"regionStart =" << regionStart/* << endl*/ -// << ", regionEnd = " << regionEnd/* << endl*/ -// << ", currentRange = " << currentRange << endl; + const QRect range = region.firstRange(); + Sheet *const sheet = region.firstSheet(); - // only change the active sub region, if we have found one. - if (regionStart != -1) { - selection()->setActiveElement(currentRange); - selection()->setActiveSubRegion(regionStart, regionEnd - regionStart + 1); - } - - // triggered by keyboard action? - if (!d->updatingChoice) { - if (d->highlighter->rangeChanged()) { - d->highlighter->resetRangeChanged(); - - //selection()->blockSignals(true); - setUpdateChoice(false); - - Tokens tokens = d->highlighter->formulaTokens(); - selection()->update(); // set the old one dirty - selection()->clear(); - Region tmpRegion; - Region::ConstIterator it; - - //A list of regions which have already been highlighted on the spreadsheet. - //This is so that we don't end up highlighting the same region twice in two different - //colors. - QLinkedList alreadyUsedRegions; - - for (int i = 0; i < tokens.count(); ++i) { - Token token = tokens[i]; - Token::Type type = token.type(); - if (type == Token::Cell || type == Token::Range) { - Region region(token.text(), selection()->activeSheet()->map(), selection()->activeSheet()); - it = region.constBegin(); - - if (it != region.constEnd() && !alreadyUsedRegions.contains(region)) { - QRect r = (*it)->rect(); - - if (selection()->isEmpty()) - selection()->initialize((*it)->rect(), (*it)->sheet()); - else - selection()->extend((*it)->rect(), (*it)->sheet()); - - alreadyUsedRegions.append(region); - } - } - } - setUpdateChoice(true); - //selection()->blockSignals(false); + selection->initialize(range, sheet); + // Always append the next range by pointing behind the last item. + selection->setActiveSubRegion(++counter, 0); } } -} -void CellEditor::cut() -{ - d->textEdit->cut(); -} + // Set the active sub-region. + // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called + // automatically on text changes, which does the update. + updateActiveSubRegion(highlighter->formulaTokens()); -void CellEditor::paste() -{ - d->textEdit->paste(); -} - -void CellEditor::copy() -{ - d->textEdit->copy(); + selectionChangedLocked = false; } void CellEditor::setEditorFont(QFont const & font, bool updateSize, const KoViewConverter *viewConverter) { const qreal scaleY = POINT_TO_INCH(static_cast((KoDpi::dpiY()))); - d->textEdit->setFont(QFont(font.family(), viewConverter->documentToViewY(font.pointSizeF()) / scaleY)); + setFont(QFont(font.family(), viewConverter->documentToViewY(font.pointSizeF()) / scaleY)); if (updateSize) { - QFontMetrics fontMetrics(d->textEdit->font()); - d->fontLength = 0.0; // fontMetrics.width('x'); - int width = fontMetrics.width(d->textEdit->toPlainText()) + d->fontLength; + QFontMetrics fontMetrics(this->font()); + int width = fontMetrics.width(toPlainText()) + fontMetrics.averageCharWidth(); // don't make it smaller: then we would have to repaint the obscured cells if (width < this->width()) width = this->width(); int height = fontMetrics.height(); if (height < this->height()) height = this->height(); setGeometry(x(), y(), width, height); } } void CellEditor::slotCompletionModeChanged(KGlobalSettings::Completion _completion) { selection()->activeSheet()->map()->settings()->setCompletionMode(_completion); } void CellEditor::slotTextChanged() { - // Fix the position. - d->textEdit->verticalScrollBar()->setValue(4); - - //FIXME - text() may return richtext? - QString t = text(); - - if (t.length() > d->length) { - d->length = t.length(); - - QFontMetrics fm(d->textEdit->font()); - // - requiredWidth = width of text plus some spacer characters - int requiredWidth = fm.width(t) + (2 * fm.width('x')); - - //For normal single-row cells, the text editor must be expanded horizontally to - //allow the text to fit if the new text is too wide - //For multi-row (word-wrap enabled) cells, the text editor must expand vertically to - //allow for new rows of text & the width of the text editor is not affected - if (d->textEdit->wordWrapMode() == QTextOption::NoWrap) { - if (requiredWidth > width()) { - if (t.isRightToLeft()) { - setGeometry(x() - requiredWidth + width(), y(), requiredWidth, height()); - } else { - setGeometry(x(), y(), requiredWidth, height()); - } - } - } else { - int requiredHeight = d->textEdit->heightForWidth(width()); - - if (requiredHeight > height()) { - setGeometry(x(), y(), width(), requiredHeight); - } - } - - /* // allocate more space than needed. Otherwise it might be too slow - d->length = t.length(); - - // Too slow for long texts - // QFontMetrics fm( d->textEdit->font() ); - // int mw = fm.width( t ) + fm.width('x'); - int mw = d->fontLength * d->length; + // NOTE On text changes KTextEdit::cursorPositionChanged() is triggered + // before KTextEdit::textChanged(). - if (mw < width()) - mw = width(); - - if (t.isRightToLeft()) - setGeometry(x() - mw + width(), y(), mw, height()); - else - setGeometry(x(), y(), mw, height()); - - d->length -= 2; */ + // Fix the position. + verticalScrollBar()->setValue(1); + + const QString text = toPlainText(); + + const QFontMetricsF fontMetrics(font()); + // TODO Adjust size depending on which cells can be obscured (see CellView)? + // The following line would result in an unchanged width for cells with + // enabled word wrapping, but after the user input got applied it may + // obscure cells horizontally. + // Passing no flags will only change the height, if a manual line break is + // entered (Shift + Return). + // const int flags = wordWrapMode() == QTextOption::NoWrap ? 0 : Qt::TextWordWrap; + const QRectF rect = fontMetrics.boundingRect(this->rect(), 0, text); + const int requiredWidth = rect.width(); + const int requiredHeight = rect.height() - 1; // -1 to fit into a default cell + if (text.isRightToLeft()) { + setGeometry(x() - requiredWidth + width(), y(), requiredWidth, requiredHeight); + } else { + setGeometry(x(), y(), requiredWidth, requiredHeight); } - if ((cell().style().formatType()) == Format::Percentage) { - if ((t.length() == 1) && t[0].isDigit()) { - QString tmp = t + " %"; - d->textEdit->setPlainText(tmp); + // FIXME Stefan: Is this really wanted? The percent char does not get + // removed on applying the user input. If the style changes afterwards, + // the user input is still indicating a percent value. If the digit gets + // deleted while editing the percent char also stays. Disabling for now. +#if 0 // KSPREAD_WIP_EDITOR_OVERHAUL + const Cell cell(d->selection->activeSheet(), d->selection->marker()); + if ((cell.style().formatType()) == Format::Percentage) { + if ((text.length() == 1) && text[0].isDigit()) { + setPlainText(text + " %"); setCursorPosition(1); return; } } +#endif // KSPREAD_WIP_EDITOR_OVERHAUL - if (hasFocus()) // update the external editor, but only if we have focus - emit textChanged(d->textEdit->toPlainText()); - // selection()->view()->editWidget()->textCursor().setPosition( d->textEdit->cursorPosition() ); -} - -void CellEditor::setCheckChoice(bool state) -{ - d->checkChoice = state; -} - -bool CellEditor::checkChoice() -{ - if (!d->checkChoice) - return false; - -// // prevent recursion -// d->checkChoice = false; // TODO nescessary? - - d->length_namecell = 0; - d->currentToken = 0; - - QString text = d->textEdit->toPlainText(); - if (text[0] != '=') { - selection()->setReferenceSelectionMode(false); -// d->checkChoice = true; - return true; + // update the external editor, but only if we have focus + if (hasFocus()) { + emit textChanged(text); } - // switch to reference selection mode if we haven't yet - selection()->startReferenceSelection(); - - // if we don't have focus, we show highlighting, but don't do the rest - if (!hasFocus()) { - selection()->setReferenceSelectionMode(false); -// d->checkChoice = true; - return true; + // Enable/disable the reference selection. + if (!text.isEmpty() && text[0] == '=') { + selection()->startReferenceSelection(); + } else { + selection()->endReferenceSelection(); + return; } - int cur = d->textEdit->textCursor().position(); - - Tokens tokens = d->highlighter->formulaTokens(); - - if (tokens.count()) { // formula not empty? - Token token; - for (int i = 0; i < tokens.count(); ++i) { - if (tokens[i].pos() >= cur - 1) { // without '=' - break; - } - token = tokens[i]; - d->currentToken = i; - } - - Token::Type type = token.type(); - if (type == Token::Operator && token.asOperator() != Token::RightPar) { - selection()->setReferenceSelectionMode(true); - } else if (type == Token::Cell || type == Token::Range) { - d->length_namecell = token.text().length(); - selection()->setReferenceSelectionMode(true); - } else { - selection()->setReferenceSelectionMode(false); - } + // The expression highlighting got updated automatically. + // If a reference has changed since the last update ... + if (d->highlighter->rangeChanged()) { + // Reset the flag, that indicates range changes after text changes. + d->highlighter->resetRangeChanged(); + // Rebuild the reference selection by using the formula tokens. + d->rebuildSelection(); } - -// d->checkChoice = true; - - return true; } -void CellEditor::setUpdateChoice(bool state) +// Called on selection (and sheet) changes. +void CellEditor::selectionChanged() { - d->updateChoice = state; -} - -void CellEditor::updateChoice() -{ - if (!d->updateChoice) + if (d->selectionChangedLocked) { return; - - // prevent recursion - d->updatingChoice = true; + } Selection* choice = selection(); if (choice->isEmpty()) return; - if (!choice->activeElement()) - return; - - QString name_cell = choice->activeSubRegionName(); + const QString text = toPlainText(); + const int textLength = text.length(); + // Find the start text cursor position for the active sub-region within + // the formula's expression and determine the length of the sub-region. Tokens tokens = d->highlighter->formulaTokens(); uint start = 1; uint length = 0; if (!tokens.empty()) { if (d->currentToken < tokens.count()) { Token token = tokens[d->currentToken]; Token::Type type = token.type(); if (type == Token::Cell || type == Token::Range) { start = token.pos() + 1; // don't forget the '='! length = token.text().length(); - } else { - start = token.pos() + token.text().length() + 1; + // Iterate to the end of the sub-region. + for (int i = d->currentToken + 1; i < tokens.count(); ++i) { + token = tokens[i]; + type = token.type(); + switch (type) { + case Token::Cell: + case Token::Range: + length += token.text().length(); + continue; + case Token::Operator: + if (token.asOperator() == Token::Semicolon) { + ++length; + continue; + } + default: + break; + } + break; + } + start = token.pos() + 1; // don't forget the '='! + length = token.text().length(); } } else { // sanitize - d->currentToken = 0; + d->currentToken = tokens.count(); + start = textLength; } } - d->length_namecell = name_cell.length(); - d->length_text = text().length(); - - QString oldText = text(); - QString newText = oldText.left(start) + name_cell + oldText.right(d->length_text - start - length); - - setCheckChoice(false); - setText(newText); - setCheckChoice(true); - setCursorPosition(start + d->length_namecell); - - emit textChanged(d->textEdit->toPlainText()); - - d->updatingChoice = false; -} - -void CellEditor::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - d->textEdit->setGeometry(0, 0, width(), height()); + // Replace the formula's active sub-region with the selection's one. + const QString address = choice->activeSubRegionName(); + const QString newExpression = QString(text).replace(start, length, address); + // The expression highlighting gets updated automatically by the next call, + // even though signals are blocked (must be connected to QTextDocument). + blockSignals(true); + setText(newExpression, start + address.length()); + blockSignals(false); + + // Ranges have changed. + // Reset the flag, that indicates range changes after text changes. + d->highlighter->resetRangeChanged(); + // Mirror the behaviour of slotCursorPositionChanged(), but here the tokens + // are already up-to-date. + d->globalCursorPos = mapToGlobal(cursorRect().bottomLeft()); + // Set the active sub-region. + // Needs up-to-date tokens; QSyntaxHighlighter::rehighlight() gets called + // automatically on text changes, which does the update. + d->updateActiveSubRegion(d->highlighter->formulaTokens()); + + // Always emit, because this editor may be hidden or does not have focus, + // but the external one needs an update. + emit textChanged(toPlainText()); } -void CellEditor::handleKeyPressEvent(QKeyEvent * _ev) +void CellEditor::keyPressEvent(QKeyEvent *event) { - if (_ev->key() == Qt::Key_F4) { - if (d->textEdit == 0) { - QApplication::sendEvent(d->textEdit, _ev); - return; - } - + if (event->key() == Qt::Key_F4) { QRegExp exp("(\\$?)([a-zA-Z]+)(\\$?)([0-9]+)$"); - int cur = d->textEdit->textCursor().position(); + int cur = textCursor().position(); QString tmp, tmp2; int n = -1; // this is ugly, and sort of hack // FIXME rewrite to use the real Tokenizer unsigned i; for (i = 0; i < 10; i++) { - tmp = d->textEdit->toPlainText().left(cur + i); - tmp2 = d->textEdit->toPlainText().right(d->textEdit->toPlainText().length() - cur - i); + tmp = toPlainText().left( cur+i ); + tmp2 = toPlainText().right( toPlainText().length() - cur - i ); n = exp.indexIn(tmp); if (n >= 0) break; } if (n == -1) return; QString newPart; if ((exp.cap(1) == "$") && (exp.cap(3) == "$")) newPart = '$' + exp.cap(2) + exp.cap(4); else if ((exp.cap(1) != "$") && (exp.cap(3) != "$")) newPart = '$' + exp.cap(2) + '$' + exp.cap(4); else if ((exp.cap(1) == "$") && (exp.cap(3) != "$")) newPart = exp.cap(2) + '$' + exp.cap(4); else if ((exp.cap(1) != "$") && (exp.cap(3) == "$")) newPart = exp.cap(2) + exp.cap(4); QString newString = tmp.left(n); newString += newPart; cur = newString.length() - i; newString += tmp2; - d->textEdit->setPlainText(newString); + setPlainText(newString); setCursorPosition(cur); - _ev->accept(); // QKeyEvent - + event->accept(); // QKeyEvent return; } - - // Send the key event to the KLineEdit - QApplication::sendEvent(d->textEdit, _ev); + switch (event->key()) { + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_PageDown: + case Qt::Key_PageUp: + case Qt::Key_Tab: + case Qt::Key_Backtab: + // Forward left/right arrows to parent, so that pressing left/right + // in this editor leaves editing mode, unless this editor has been + // set to capture arrow key events. + if (d->captureAllKeyEvents) { + break; // pass to KTextEdit + } + // Send directly to the parent. + QApplication::sendEvent(parent(), event); + return; + case Qt::Key_Return: + case Qt::Key_Enter: + // Shift + Return: manual line wrap + if (event->modifiers() & Qt::ShiftModifier) { + break; // pass to KTextEdit + } + // Send directly to the parent. + QApplication::sendEvent(parent(), event); + return; + } + KTextEdit::keyPressEvent(event); } -void CellEditor::handleInputMethodEvent(QInputMethodEvent * _ev) +void CellEditor::focusInEvent(QFocusEvent *event) { - // send the IM event to the KLineEdit - QApplication::sendEvent(d->textEdit, _ev); + // If the focussing is user induced. + if (event->reason() != Qt::OtherFocusReason) { + kDebug() << "induced by user"; + d->cellTool->setLastEditorWithFocus(CellToolBase::EmbeddedEditor); + } + KTextEdit::focusInEvent(event); } -QString CellEditor::text() const +void CellEditor::focusOutEvent(QFocusEvent *event) { - return d->textEdit->toPlainText(); + KTextEdit::focusOutEvent(event); } void CellEditor::setText(const QString& text, int cursorPos) { - if (text == d->textEdit->toPlainText()) return; - - d->textEdit->setPlainText(text); - //Usability : It is usually more convenient if the cursor is positioned at the end of the text so it can - //be quickly deleted using the backspace key + if (text == toPlainText()) return; - // This also ensures that the caret is sized correctly for the text - if ((cursorPos < 0) || cursorPos > text.length()) cursorPos = text.length(); - setCursorPosition(cursorPos); + setPlainText(text); - if (d->fontLength == 0) { - QFontMetrics fm(d->textEdit->font()); - d->fontLength = fm.width('x'); + // Only update the cursor position, if a non-negative value was set. + // The default parameter value is -1, i.e. the cursor does not get touched. + if (cursorPos >= 0) { + if (cursorPos > text.length()) { + // Usability: It is usually more convenient, if the cursor is + // positioned at the end of the text so it can be quickly deleted + // using the backspace key. + cursorPos = text.length(); + } + setCursorPosition(cursorPos); } } int CellEditor::cursorPosition() const { - return d->textEdit->textCursor().position(); + return textCursor().position(); } void CellEditor::setCursorPosition(int pos) { - QTextCursor textCursor(d->textEdit->textCursor()); + QTextCursor textCursor( this->textCursor() ); textCursor.setPosition(pos); - d->textEdit->setTextCursor(textCursor); -// kDebug() << "pos" << pos << "textCursor" << d->textEdit->textCursor().position(); - // FIXME Stefan: The purpose of this connection is? - // Disabled to avoid cursor jumps to the end of the line on every key event. -// selection()->view()->editWidget()->setCursorPosition( pos ); + setTextCursor( textCursor ); } -bool CellEditor::eventFilter(QObject* obj, QEvent* ev) +// Called by the cell tool when setting the active element with a cell location. +void CellEditor::setActiveSubRegion(int index) { - // Only interested in KTextEdit - if (obj != d->textEdit) - return false; - - if (ev->type() == QEvent::FocusOut) { - d->cellTool->setLastEditorWithFocus(CellToolBase::EmbeddedEditor); - return false; - } - - if (ev->type() == QEvent::KeyPress || ev->type() == QEvent::KeyRelease) { - QKeyEvent* ke = static_cast(ev); - if (!(ke->modifiers() & Qt::ShiftModifier) || selection()->referenceSelectionMode()) { - switch (ke->key()) { - case Qt::Key_Return: - case Qt::Key_Enter: - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_PageDown: - case Qt::Key_PageUp: - case Qt::Key_Escape: - case Qt::Key_Tab: - // Send directly to canvas - QApplication::sendEvent(parent(), ev); - return true; - } - } - // End choosing. May be restarted by CellEditor::slotTextChanged. - // QKeyEvent::text() is empty, if a modifier was pressed/released. - if (ev->type() == QEvent::KeyPress && !ke->text().isEmpty()) { - selection()->setReferenceSelectionMode(false); - } - // Forward left/right arrows to parent, so that pressing left/right - // in this editor leaves editing mode, unless this editor has been set - // to capture arrow key events. Changed to this behaviour for - // consistancy with OO Calc & MS Office. - if ((!d->captureAllKeyEvents) && - ((ke->key() == Qt::Key_Left) || (ke->key() == Qt::Key_Right))) { - QApplication::sendEvent(parent(), ev); - return true; - } - } - return false; -} - -void CellEditor::setCursorToRange(uint pos) -{ - d->updatingChoice = true; - uint counter = 0; - Tokens tokens = d->highlighter->formulaTokens(); + index = qBound(0, index, (int)d->highlighter->rangeCount()); + int counter = 0; + bool subRegion = false; + const Tokens tokens = d->highlighter->formulaTokens(); for (int i = 0; i < tokens.count(); ++i) { - Token token = tokens[i]; - Token::Type type = token.type(); - if (type == Token::Cell || type == Token::Range) { - if (counter == pos) { - setCursorPosition(token.pos() + token.text().length() + 1); - } - counter++; + const Token token = tokens[i]; + switch (token.type()) { + case Token::Cell: + case Token::Range: + if (!subRegion) { + d->currentToken = i; + subRegion = true; + } + if (counter == index) { + setCursorPosition(token.pos() + token.text().length() + 1); + return; + } + ++counter; + continue; + case Token::Operator: + if (token.asOperator() == Token::Semicolon) { + if (subRegion) { + continue; + } + } + subRegion = false; + continue; + default: + subRegion = false; + continue; } } - d->updatingChoice = false; } + /***************************************************************************** * * LocationComboBox * ****************************************************************************/ LocationComboBox::LocationComboBox(CellToolBase *cellTool, QWidget *_parent) : KComboBox(true, _parent) , m_cellTool(cellTool) { setCompletionObject(&completionList, true); setCompletionMode(KGlobalSettings::CompletionAuto); insertItem(0, QString()); updateAddress(); Map *const map = cellTool->selection()->activeSheet()->map(); const QList areaNames = map->namedAreaManager()->areaNames(); for (int i = 0; i < areaNames.count(); ++i) slotAddAreaName(areaNames[i]); connect(this, SIGNAL(activated(const QString &)), this, SLOT(slotActivateItem())); } void LocationComboBox::updateAddress() { QString address; Selection *const selection = m_cellTool->selection(); const QList< QPair > names = selection->activeSheet()->cellStorage()->namedAreas(*selection); { QRect range; if (selection->isSingular()) range = QRect(selection->marker(), QSize(1, 1)); else range = selection->lastRange(); for (int i = 0; i < names.size(); i++) { if (names[i].first.toRect() == range) { address = names[i].second; } } } if (selection->activeSheet()->getLcMode()) { if (selection->isSingular()) { address = 'L' + QString::number(selection->marker().y()) + 'C' + QString::number(selection->marker().x()); } else { const QRect lastRange = selection->lastRange(); address = QString::number(lastRange.height()) + "Lx"; address += QString::number(lastRange.width()) + 'C'; } } else { address = selection->name(); } setItemText(0, address); setCurrentItem(0); lineEdit()->setText(address); } void LocationComboBox::slotAddAreaName(const QString &_name) { insertItem(count(), _name); addCompletionItem(_name); } void LocationComboBox::slotRemoveAreaName(const QString &_name) { for (int i = 0; i < count(); i++) { if (itemText(i) == _name) { removeItem(i); break; } } removeCompletionItem(_name); } void LocationComboBox::addCompletionItem( const QString &_item ) { if (completionList.items().contains(_item) == 0) { completionList.addItem(_item); kDebug(36005) << _item; } } void LocationComboBox::removeCompletionItem( const QString &_item ) { completionList.removeItem(_item); } void LocationComboBox::slotActivateItem() { if (activateItem()) { m_cellTool->scrollToCell(m_cellTool->selection()->cursor()); } } bool LocationComboBox::activateItem() { Selection *const selection = m_cellTool->selection(); // Set the focus back on the canvas. parentWidget()->setFocus(); const QString text = lineEdit()->text(); // check whether an already existing named area was entered Region region = selection->activeSheet()->map()->namedAreaManager()->namedArea(text); if (region.isValid()) { // TODO Stefan: Merge the sheet change into Selection. if (region.firstSheet() != selection->activeSheet()) { selection->emitVisibleSheetRequested(region.firstSheet()); } selection->initialize(region); return true; } // check whether a valid cell region was entered region = Region(text, selection->activeSheet()->map(), selection->activeSheet()); if (region.isValid()) { // TODO Stefan: Merge the sheet change into Selection. if (region.firstSheet() != selection->activeSheet()) { selection->emitVisibleSheetRequested(region.firstSheet()); } selection->initialize(region); return true; } // A name for an area entered? // FIXME Stefan: allow all characters bool validName = true; for (int i = 0; i < text.length(); ++i) { if (!text[i].isLetter()) { validName = false; break; } } if (validName) { NamedAreaCommand* command = new NamedAreaCommand(); command->setSheet(selection->activeSheet()); command->setAreaName(text); command->add(Region(selection->lastRange(), selection->activeSheet())); if (command->execute()) return true; else delete command; } return false; } void LocationComboBox::keyPressEvent( QKeyEvent * _ev ) { Selection *const selection = m_cellTool->selection(); // Do not handle special keys and accelerators. This is // done by KComboBox. if (_ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier)) { KComboBox::keyPressEvent(_ev); // Never allow that keys are passed on to the parent. _ev->accept(); // QKeyEvent return; } // Handle some special keys here. Eve switch (_ev->key()) { case Qt::Key_Return: case Qt::Key_Enter: { if (activateItem()) { m_cellTool->scrollToCell(selection->cursor()); return; } _ev->accept(); // QKeyEvent } break; // Escape pressed, restore original value case Qt::Key_Escape: updateAddress(); parentWidget()->setFocus(); _ev->accept(); // QKeyEvent break; default: KComboBox::keyPressEvent(_ev); // Never allow that keys are passed on to the parent. _ev->accept(); // QKeyEvent } } /**************************************************************** * * ExternalEditor * The editor that appears in the tool option widget and allows * to edit the cell's content. * ****************************************************************/ class ExternalEditor::Private { public: CellToolBase* cellTool; FormulaEditorHighlighter* highlighter; bool isArray; }; ExternalEditor::ExternalEditor(QWidget *parent) : KTextEdit(parent) , d(new Private) { d->cellTool = 0; d->highlighter = 0; d->isArray = false; setCurrentFont(KGlobalSettings::generalFont()); // Try to immitate KLineEdit regarding the margins and size. document()->setDocumentMargin(1); setMinimumHeight(fontMetrics().height() + 2 * frameWidth() + 1); connect(this, SIGNAL(textChanged()), this, SLOT(slotTextChanged())); + connect(this, SIGNAL(cursorPositionChanged()), + this, SLOT(slotCursorPositionChanged())); } ExternalEditor::~ExternalEditor() { delete d->highlighter; delete d; } QSize ExternalEditor::sizeHint() const { return KTextEdit::sizeHint(); // document()->size().toSize(); } void ExternalEditor::setCellTool(CellToolBase* cellTool) { d->cellTool = cellTool; d->highlighter = new FormulaEditorHighlighter(this, cellTool->selection()); } void ExternalEditor::applyChanges() { Q_ASSERT(d->cellTool); d->cellTool->deleteEditor(true, d->isArray); // save changes d->isArray = false; - d->cellTool->canvas()->canvasWidget()->setFocus(); } void ExternalEditor::discardChanges() { Q_ASSERT(d->cellTool); clear(); d->cellTool->deleteEditor(false); // discard changes - d->cellTool->canvas()->canvasWidget()->setFocus(); d->cellTool->selection()->update(); } void ExternalEditor::setText(const QString &text) { Q_ASSERT(d->cellTool); if (toPlainText() == text) { return; } + // This method is called from the embedded editor. Do not send signals back. + blockSignals(true); KTextEdit::setPlainText(text); QTextCursor textCursor = this->textCursor(); textCursor.setPosition(d->cellTool->editor()->cursorPosition()); setTextCursor(textCursor); + blockSignals(false); } void ExternalEditor::keyPressEvent(QKeyEvent *event) { Q_ASSERT(d->cellTool); if (!d->cellTool->selection()->activeSheet()->map()->isReadWrite()) { return; } // Create the embedded editor, if necessary. if (!d->cellTool->editor()) { d->cellTool->createEditor(false /* keep content */, false /* no focus */); } // the Enter and Esc key are handled by the embedded editor if ((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter) || (event->key() == Qt::Key_Escape)) { d->cellTool->editor()->setFocus(); QApplication::sendEvent(d->cellTool->editor(), event); event->accept(); return; } if (event->key() == Qt::Key_F2) { // Switch the focus back to the embedded editor. d->cellTool->editor()->setFocus(); } // call inherited handler KTextEdit::keyPressEvent(event); } void ExternalEditor::focusInEvent(QFocusEvent* event) { Q_ASSERT(d->cellTool); + // If the focussing is user induced. + if (event->reason() != Qt::OtherFocusReason) { + kDebug() << "induced by user"; + d->cellTool->setLastEditorWithFocus(CellToolBase::ExternalEditor); + } // when the external editor gets focus, create also the internal editor // this in turn means that ranges will be instantly highlighted right if (!d->cellTool->editor()) d->cellTool->createEditor(false /* keep content */, false /* no focus */); KTextEdit::focusInEvent(event); } void ExternalEditor::focusOutEvent(QFocusEvent* event) { Q_ASSERT(d->cellTool); - d->cellTool->setLastEditorWithFocus(CellToolBase::ExternalEditor); KTextEdit::focusOutEvent(event); } void ExternalEditor::slotTextChanged() { if (!hasFocus()) return; // only report change if we have focus emit textChanged(toPlainText()); + // Update the cursor position again, because this slot is invoked after + // slotCursorPositionChanged(). + if (d->cellTool->editor()) { + d->cellTool->editor()->setCursorPosition(textCursor().position()); + } +} + +void ExternalEditor::slotCursorPositionChanged() +{ + if (!hasFocus() || !d->cellTool->editor()) { + return; + } + // Suppress updates, if this slot got invoked by a text change. It is done + // later by slotTextChanged(). + if (d->cellTool->editor()->toPlainText() == toPlainText()) { + d->cellTool->editor()->setCursorPosition(textCursor().position()); + } } #if 0 // KSPREAD_DISCARD_FORMULA_BAR /**************************************************************** * * EditWidget * The line-editor that appears above the sheet and allows to * edit the cells content. * ****************************************************************/ EditWidget::EditWidget(QWidget *_parent, Canvas *_canvas, QAbstractButton *cancelButton, QAbstractButton *okButton) : KLineEdit(_parent) { m_pCanvas = _canvas; Q_ASSERT(m_pCanvas != 0); // Those buttons are created by the caller, so that they are inserted // properly in the layout - but they are then managed here. m_pCancelButton = cancelButton; m_pOkButton = okButton; m_isArray = false; installEventFilter(m_pCanvas); if (!m_pCanvas->doc()->isReadWrite() || !m_pCanvas->activeSheet()) setEnabled(false); QObject::connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(slotAbortEdit())); QObject::connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(slotDoneEdit())); setEditMode(false); // disable buttons setCompletionMode(m_pCanvas->doc()->completionMode()); setCompletionObject(&m_pCanvas->doc()->map()->stringCompletion(), true); } void EditWidget::showEditWidget(bool _show) { if (_show) { m_pCancelButton->show(); m_pOkButton->show(); show(); } else { m_pCancelButton->hide(); m_pOkButton->hide(); hide(); } } void EditWidget::slotAbortEdit() { m_pCanvas->deleteEditor(false /*discard changes*/); // will take care of the buttons } void EditWidget::slotDoneEdit() { m_pCanvas->deleteEditor(true /*keep changes*/, m_isArray); m_isArray = false; // will take care of the buttons } void EditWidget::keyPressEvent(QKeyEvent* _ev) { // Don't handle special keys and accelerators, except Enter ones if (((_ev->modifiers() & (Qt::AltModifier | Qt::ControlModifier)) || (_ev->modifiers() & Qt::ShiftModifier) || (_ev->key() == Qt::Key_Shift) || (_ev->key() == Qt::Key_Control)) && (_ev->key() != Qt::Key_Return) && (_ev->key() != Qt::Key_Enter)) { KLineEdit::keyPressEvent(_ev); _ev->accept(); return; } if (!m_pCanvas->doc()->isReadWrite()) return; if (!m_pCanvas->editor()) { // Start editing the current cell m_pCanvas->createEditor(true /* clear content */, false /* no focus */); } CellEditor * cellEditor = (CellEditor*) m_pCanvas->editor(); switch (_ev->key()) { case Qt::Key_Down: case Qt::Key_Up: case Qt::Key_Return: case Qt::Key_Enter: cellEditor->setText(text()); // Don't allow to start a chooser when pressing the arrow keys // in this widget, since only up and down would work anyway. // This is why we call slotDoneEdit now, instead of sending // to the canvas. //QApplication::sendEvent( m_pCanvas, _ev ); m_isArray = (_ev->modifiers() & Qt::AltModifier) && (_ev->modifiers() & Qt::ControlModifier); slotDoneEdit(); _ev->accept(); break; case Qt::Key_F2: cellEditor->setFocus(); cellEditor->setText(text()); cellEditor->setCursorPosition(cursorPosition()); break; default: KLineEdit::keyPressEvent(_ev); setFocus(); cellEditor->setCheckChoice(false); cellEditor->setText(text()); cellEditor->setCheckChoice(true); cellEditor->setCursorPosition(cursorPosition()); } } void EditWidget::setEditMode(bool mode) { m_pCancelButton->setEnabled(mode); m_pOkButton->setEnabled(mode); } void EditWidget::focusOutEvent(QFocusEvent* ev) { //kDebug(36001) <<"EditWidget lost focus"; // See comment about setLastEditorWithFocus m_pCanvas->setLastEditorWithFocus(Canvas::EditWidget); KLineEdit::focusOutEvent(ev); } void EditWidget::setText(const QString& t) { if (t == text()) // Why this? (David) return; KLineEdit::setText(t); } #endif /***************************************************************************** * * RegionSelector * ****************************************************************************/ class RegionSelector::Private { public: Selection* selection; QDialog* parentDialog; KDialog* dialog; KTextEdit* textEdit; QToolButton* button; FormulaEditorHighlighter* highlighter; DisplayMode displayMode; SelectionMode selectionMode; static RegionSelector* s_focussedSelector; }; RegionSelector* RegionSelector::Private::s_focussedSelector = 0; RegionSelector::RegionSelector(QWidget* parent) : QWidget(parent), d(new Private) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->displayMode = Widget; d->parentDialog = 0; d->selection = 0; d->dialog = 0; d->button = new QToolButton(this); d->button->setCheckable(true); d->button->setIcon(KIcon("selection")); d->highlighter = 0; d->textEdit = new KTextEdit(this); d->textEdit->setLineWrapMode(QTextEdit::NoWrap); d->textEdit->setWordWrapMode(QTextOption::NoWrap); d->textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); d->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); d->textEdit->setFixedHeight(d->button->height() - 2*d->textEdit->frameWidth()); // FIXME d->textEdit->setTabChangesFocus(true); QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->setSpacing(2); layout->addWidget(d->textEdit); layout->addWidget(d->button); d->button->installEventFilter(this); d->textEdit->installEventFilter(this); connect(d->button, SIGNAL(toggled(bool)), this, SLOT(switchDisplayMode(bool))); } RegionSelector::~RegionSelector() { d->selection->endReferenceSelection(); d->selection->setSelectionMode(Selection::MultipleCells); delete d; } void RegionSelector::setSelectionMode(SelectionMode mode) { d->selectionMode = mode; // TODO adjust selection } void RegionSelector::setSelection(Selection* selection) { d->selection = selection; d->highlighter = new FormulaEditorHighlighter(d->textEdit, d->selection); connect(d->selection, SIGNAL(changed(const Region&)), this, SLOT(choiceChanged())); } void RegionSelector::setDialog(QDialog* dialog) { d->parentDialog = dialog; } KTextEdit* RegionSelector::textEdit() const { return d->textEdit; } bool RegionSelector::eventFilter(QObject* object, QEvent* event) { if (event->type() == QEvent::Close) { if (object == d->dialog && d->button->isChecked()) { // TODO Stefan: handle as button click // d->button->toggle(); event->ignore(); return true; // eat it } } else if (event->type() == QEvent::FocusIn) { Private::s_focussedSelector = this; d->selection->startReferenceSelection(); if (d->selectionMode == SingleCell) { d->selection->setSelectionMode(Selection::SingleCell); } else { d->selection->setSelectionMode(Selection::MultipleCells); } // TODO Stefan: initialize choice } return QObject::eventFilter(object, event); } void RegionSelector::switchDisplayMode(bool state) { Q_UNUSED(state) kDebug() ; if (d->displayMode == Widget) { d->displayMode = Dialog; d->dialog = new KDialog(d->parentDialog->parentWidget(), Qt::Tool); d->dialog->resize(d->parentDialog->width(), 20); d->dialog->move(d->parentDialog->pos()); d->dialog->setButtons(0); d->dialog->setModal(false); if (d->selectionMode == SingleCell) { d->dialog->setCaption(i18n("Select Single Cell")); } else { // if ( d->selectionMode == MultipleCells ) d->dialog->setCaption(i18n("Select Multiple Cells")); } QWidget* widget = new QWidget(d->dialog); QHBoxLayout* layout = new QHBoxLayout(widget); layout->setMargin(0); layout->setSpacing(0); layout->addWidget(d->textEdit); layout->addWidget(d->button); d->dialog->setMainWidget(widget); d->dialog->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); d->dialog->installEventFilter(this); d->dialog->show(); d->parentDialog->hide(); } else { d->displayMode = Widget; layout()->addWidget(d->textEdit); layout()->addWidget(d->button); d->parentDialog->move(d->dialog->pos()); d->parentDialog->show(); delete d->dialog; d->dialog = 0; } } void RegionSelector::choiceChanged() { if (Private::s_focussedSelector != this) return; if (d->selection->isValid()) { QString area = d->selection->name(); d->textEdit->setPlainText(area); } } #include "Editors.moc" diff --git a/kspread/ui/Editors.h b/kspread/ui/Editors.h index 109a113bfa..3c52d30fd4 100644 --- a/kspread/ui/Editors.h +++ b/kspread/ui/Editors.h @@ -1,410 +1,397 @@ /* This file is part of the KDE project Copyright 1999-2006 The KSpread Team This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __EDITORS_H__ #define __EDITORS_H__ #include #include #include //Added by qt3to4: #include #include #include #include #include #include #include #include #include "kspread_export.h" class KTextEdit; class QFont; class QAbstractButton; class QTextEdit; class QListWidgetItem; class KoViewConverter; namespace KSpread { class Canvas; class Cell; class CellEditor; class CellToolBase; class LocationEditWidget; class Region; class Selection; class TextEdit; class Tokens; class View; /** * Colors cell references in formulas. Installed by CellEditor instances in * the constructor. */ class FormulaEditorHighlighter : public QSyntaxHighlighter { public: /** * Constructs a FormulaHighlighter to color-code cell references in a QTextEdit. * * @param textEdit The QTextEdit widget which the highlighter should operate on * @param selection The Selection object */ FormulaEditorHighlighter(QTextEdit* textEdit, Selection* selection); virtual ~FormulaEditorHighlighter(); /** * Called automatically by KTextEditor to highlight text when modified. */ virtual void highlightBlock(const QString& text); /** * */ const Tokens& formulaTokens() const; /** * */ uint rangeCount() const; /** * Returns true if any of the ranges or cells in the Formula.have changed since the * last call to @ref FormulaEditorHighlighter::rangeChanged() */ bool rangeChanged() const; /** * Sets the highlighter's range changed flag to false. */ void resetRangeChanged(); protected: /** * Returns the position of the brace matching the one found at position pos */ int findMatchingBrace(int pos); /** * Examines the brace (Token::LeftPar or Token::RightPar) operator token at the given index in the token vector * ( as returned by formulaTokens() ) and if the cursor is next to it, the token plus any matching brace will be highlighted */ void handleBrace(uint index); private: Q_DISABLE_COPY(FormulaEditorHighlighter) class Private; Private * const d; }; /** * Provides autocompletition facilities in formula editors. * When the user types in the first few characters of a * function name in a CellEditor which has a FunctionCompletion * object installed on it, the FunctionCompletion object * creates and displays a list of possible names which the user * can select from. If the user selects a function name from the list, * the @ref FunctionCompletion::selectedCompletion() signal is emitted */ class FunctionCompletion : public QObject { Q_OBJECT public: FunctionCompletion(CellEditor* editor); ~FunctionCompletion(); /** * Handles various keyboard and mouse actions which may occur on the autocompletion popup list */ bool eventFilter(QObject *o, QEvent *e); /** * Populates the autocompletion list box with the specified choices and shows it so that the user can view and select a function name. * @param choices A list of possible function names which match the characters that the user has already entered. */ void showCompletion(const QStringList &choices); public slots: /** * Hides the autocompletion list box if it is visible and emits the @ref selectedCompletion signal. */ void doneCompletion(); private slots: void itemSelected(QListWidgetItem* item = 0); signals: /** * Emitted, if the user selects a function name from the list. */ void selectedCompletion(const QString& item); private: class Private; Private * const d; FunctionCompletion(const FunctionCompletion&); FunctionCompletion& operator=(const FunctionCompletion&); }; /** * class CellEditor */ -class CellEditor : public QWidget +class CellEditor : public KTextEdit { Q_OBJECT public: - /** * Creates a new CellEditor. * \param cellTool the cell tool * \param parent the parent widget */ explicit CellEditor(CellToolBase *cellTool, QWidget *parent = 0); ~CellEditor(); - const Cell& cell() const; Selection* selection() const; - void handleKeyPressEvent(QKeyEvent* _ev); - void handleInputMethodEvent(QInputMethodEvent * _ev); void setEditorFont(QFont const & font, bool updateSize, const KoViewConverter *viewConverter); int cursorPosition() const; void setCursorPosition(int pos); - /** wrapper to KTextEdit::text() */ - QString text() const; - - /** wrapper to KTextEdit::cut() */ - void cut(); - /** wrapper to KTextEdit::paste() */ - void paste(); - /** wrapper to KTextEdit::copy() */ - void copy(); - QPoint globalCursorPosition() const; - bool checkChoice(); - void setCheckChoice(bool b); - - void updateChoice(); - void setUpdateChoice(bool); + /** + * Replaces the current formula token(/reference) with the name of the + * selection's active sub-region name. + * This is called after selection changes to sync the formula expression. + */ + void selectionChanged(); - void setCursorToRange(uint); + /** + * Activates the sub-region belonging to the \p index 'th range. + */ + void setActiveSubRegion(int index); Q_SIGNALS: void textChanged(const QString &text); - void modificationChanged(bool changed); public slots: void setText(const QString& text, int cursorPos = -1); private slots: void slotTextChanged(); void slotCompletionModeChanged(KGlobalSettings::Completion _completion); void slotCursorPositionChanged(); -protected: - void resizeEvent(QResizeEvent*); - /** - * Steals some key events from the KLineEdit and sends - * it to the KSpread::Canvas ( its parent ) instead. - */ - bool eventFilter(QObject* o, QEvent* e); +protected: // reimplementations + virtual void keyPressEvent(QKeyEvent *event); + virtual void focusInEvent(QFocusEvent *event); + virtual void focusOutEvent(QFocusEvent *event); protected slots: void checkFunctionAutoComplete(); void triggerFunctionAutoComplete(); void functionAutoComplete(const QString& item); private: Q_DISABLE_COPY(CellEditor) class Private; Private * const d; }; /** * \class LocationComboBox * \ingroup UI * The combobox, that shows the address string of the current cell selection. * * Depending on the sheet settings the address is displayed in normal form * (e.g. A1 or B1:C3) or in LC (Line/Column) form (e.g. L1xC1 or 3Lx2C). */ class LocationComboBox : public KComboBox { Q_OBJECT public: LocationComboBox(CellToolBase *cellTool, QWidget *parent = 0); void addCompletionItem(const QString &_item); void removeCompletionItem(const QString &_item); /** * Updates the address string according to the current cell selection * and the current address mode (normal or LC mode). */ void updateAddress(); public slots: void slotAddAreaName( const QString & ); void slotRemoveAreaName( const QString & ); protected: // reimplementations virtual void keyPressEvent(QKeyEvent *event); private Q_SLOTS: void slotActivateItem(); private: bool activateItem(); private: CellToolBase *m_cellTool; KCompletion completionList; }; class ExternalEditor : public KTextEdit { Q_OBJECT public: ExternalEditor(QWidget* parent = 0); ~ExternalEditor(); virtual QSize sizeHint() const; void setCellTool(CellToolBase* cellTool); Q_SIGNALS: void textChanged(const QString &text); public Q_SLOTS: void applyChanges(); void discardChanges(); void setText(const QString &text); protected: void keyPressEvent(QKeyEvent *event); void focusInEvent(QFocusEvent *event); void focusOutEvent(QFocusEvent *event); private slots: void slotTextChanged(); + void slotCursorPositionChanged(); private: Q_DISABLE_COPY(ExternalEditor) class Private; Private * const d; }; #if 0 // KSPREAD_DISCARD_FORMULA_BAR /** * The widget that appears above the sheet and allows to * edit the cells content. */ class EditWidget : public KLineEdit { Q_OBJECT public: EditWidget(QWidget *parent, Canvas *canvas, QAbstractButton *cancelButton, QAbstractButton *okButton); virtual void setText(const QString& t); // Go into edit mode (enable the buttons) void setEditMode(bool mode); void showEditWidget(bool _show); public slots: void slotAbortEdit(); void slotDoneEdit(); protected: virtual void keyPressEvent(QKeyEvent* _ev); virtual void focusOutEvent(QFocusEvent* ev); private: QAbstractButton* m_pCancelButton; QAbstractButton* m_pOkButton; Canvas* m_pCanvas; bool m_isArray; }; #endif /** * A minimizable line edit for choosing cell regions. * \author Stefan Nikolaus */ class KSPREAD_EXPORT RegionSelector : public QWidget { Q_OBJECT public: enum SelectionMode { SingleCell = 0, MultipleCells = 1 }; // TODO Stefan: merge with Selection::Mode enum DisplayMode { Widget, Dialog }; RegionSelector(QWidget* parent = 0); ~RegionSelector(); void setSelectionMode(SelectionMode mode); void setSelection(Selection* selection); void setDialog(QDialog* dialog); void setLabel(const QString& text); KTextEdit* textEdit() const; protected: bool eventFilter(QObject* obj, QEvent* event); protected Q_SLOTS: void switchDisplayMode(bool state); void choiceChanged(); private: Q_DISABLE_COPY(RegionSelector) class Private; Private * const d; }; } // namespace KSpread #endif diff --git a/kspread/ui/SelectionStrategy.cpp b/kspread/ui/SelectionStrategy.cpp index b603e98551..6de286c369 100644 --- a/kspread/ui/SelectionStrategy.cpp +++ b/kspread/ui/SelectionStrategy.cpp @@ -1,143 +1,153 @@ /* This file is part of the KDE project Copyright 2008 Stefan Nikolaus This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "SelectionStrategy.h" #include "CellToolBase.h" +#include "Editors.h" #include "kspread_limits.h" #include "Selection.h" #include "Sheet.h" #include #include #include using namespace KSpread; class SelectionStrategy::Private { public: Cell startCell; }; SelectionStrategy::SelectionStrategy(CellToolBase *cellTool, const QPointF documentPos, Qt::KeyboardModifiers modifiers) : AbstractSelectionStrategy(cellTool, documentPos, modifiers) , d(new Private) { d->startCell = Cell(); const KoShape* shape = tool()->canvas()->shapeManager()->selection()->firstSelectedShape(); const QPointF position = documentPos - (shape ? shape->position() : QPointF(0.0, 0.0)); Sheet *const sheet = this->selection()->activeSheet(); Selection *const selection = this->selection(); #if 0 // KSPREAD_WIP_DRAG_REFERENCE_SELECTION // Check, if the selected area was hit. bool hitSelection = false; const Region::ConstIterator end(selection->constEnd()); for (Region::ConstIterator it(selection->constBegin()); it != end; ++it) { // Only process ranges on the active sheet. if (sheet != (*it)->sheet()) { continue; } const QRect range = (*it)->rect(); if (sheet->cellCoordinatesToDocument(range).contains(position)) { hitSelection = true; break; } } #endif // KSPREAD_WIP_DRAG_REFERENCE_SELECTION // In which cell did the user click? double xpos; double ypos; const int col = sheet->leftColumn(position.x(), xpos); const int row = sheet->topRow(position.y(), ypos); // Check boundaries. if (col > KS_colMax || row > KS_rowMax) { kDebug(36005) << "col or row is out of range:" << "col:" << col << " row:" << row; } else { d->startCell = Cell(sheet, col, row); if (selection->referenceSelectionMode()) { selection->emitRequestFocusEditor(); const bool sizeGripHit = hitTestReferenceSizeGrip(tool()->canvas(), selection, position); const bool shiftPressed = modifiers & Qt::ShiftModifier; - if (sizeGripHit || shiftPressed) { + if (sizeGripHit) { + // FIXME The size grip is partly located in the adjacent cells. + // Activate the selection's location/range. + const int index = selection->setActiveElement(d->startCell); + // If successful, activate the editor's location/range. + if (index >= 0 && cellTool->editor()) { + cellTool->editor()->setActiveSubRegion(index); + } + selection->update(QPoint(col, row)); + } else if (shiftPressed) { selection->update(QPoint(col, row)); } else if (modifiers & Qt::ControlModifier) { // Extend selection, if control modifier is pressed. selection->extend(QPoint(col, row), sheet); #if 0 // KSPREAD_WIP_DRAG_REFERENCE_SELECTION } else if (hitSelection) { // start cell is already set above // No change; the range will be moved #endif // KSPREAD_WIP_DRAG_REFERENCE_SELECTION } else { selection->initialize(QPoint(col, row), sheet); } } else { selection->emitCloseEditor(true); if (modifiers & Qt::ControlModifier) { // Extend selection, if control modifier is pressed. selection->extend(QPoint(col, row), sheet); } else if (modifiers & Qt::ShiftModifier) { selection->update(QPoint(col, row)); } else { selection->initialize(QPoint(col, row), sheet); } } } tool()->repaintDecorations(); } SelectionStrategy::~SelectionStrategy() { delete d; } void SelectionStrategy::handleMouseMove(const QPointF &documentPos, Qt::KeyboardModifiers modifiers) { #if 0 // KSPREAD_WIP_DRAG_REFERENCE_SELECTION Q_UNUSED(modifiers); const KoShape* shape = tool()->canvas()->shapeManager()->selection()->firstSelectedShape(); const QPointF position = documentPos - (shape ? shape->position() : QPointF(0.0, 0.0)); Sheet *const sheet = selection()->activeSheet(); if (selection()->referenceSelectionMode()) { // In which cell did the user move? double xpos; double ypos; const int col = sheet->leftColumn(position.x(), xpos); const int row = sheet->topRow(position.y(), ypos); // Check boundaries. if (col > KS_colMax || row > KS_rowMax) { kDebug(36005) << "col or row is out of range:" << "col:" << col << " row:" << row; } else if (!(d->startCell == Cell(sheet, col, row))) { const QRect range = selection()->activeElement()->rect(); const QPoint offset = d->startCell.cellPosition() - range.topLeft(); const QPoint topLeft = QPoint(col, row) + offset; selection()->initialize(QRect(topLeft, range.size()), sheet); return; } } #endif // KSPREAD_WIP_DRAG_REFERENCE_SELECTION AbstractSelectionStrategy::handleMouseMove(documentPos, modifiers); }