/* 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. */ #include #include #include "kspread_canvas.h" #include "kspread_cell.h" #include "kspread_doc.h" #include "kspread_editors.h" #include "kspread_sheet.h" #include "kspread_view.h" #include "kspread_util.h" #include "selection.h" // TODO Stefan: Substract points in selections // TODO Stefan: KPart signal (kspread_events.h) using namespace KSpread; /*************************************************************************** class Selection::Private ****************************************************************************/ class Selection::Private { public: Private(View *v) { view = v; sheet = 0; anchor = TQPoint(1,1); cursor = TQPoint(1,1); marker = TQPoint(1,1); colors.push_back(TQt::red); colors.push_back(TQt::blue); colors.push_back(TQt::magenta); colors.push_back(TQt::darkRed); colors.push_back(TQt::darkGreen); colors.push_back(TQt::darkMagenta); colors.push_back(TQt::darkCyan); colors.push_back(TQt::darkYellow); multipleSelection = false; activeElement = Iterator(); activeSubRegionStart = 0; activeSubRegionLength = 0; } View* view; Sheet* sheet; TQPoint anchor; TQPoint cursor; TQPoint marker; TQValueList colors; bool multipleSelection : 1; Selection::Iterator activeElement; uint activeSubRegionStart; uint activeSubRegionLength; }; /*************************************************************************** class Selection ****************************************************************************/ namespace KSpread { Selection::Selection(View *view) : TQObject(view), Region(1,1) { d = new Private(view); d->activeSubRegionStart = 0; d->activeSubRegionLength = 1; } Selection::Selection(const Selection& selection) : TQObject(selection.d->view), Region() { /* kdDebug() << k_funcinfo << endl;*/ d = new Private(selection.d->view); d->sheet = selection.d->sheet; d->activeSubRegionStart = 0; d->activeSubRegionLength = cells().count(); } Selection::~Selection() { delete d; } void Selection::initialize(const TQPoint& point, Sheet* sheet) { if (!util_isPointValid(point)) return; if (!d->view->activeSheet()) return; if (!sheet) { if (d->sheet) { sheet = d->sheet; } else { sheet = d->view->activeSheet(); } } Region changedRegion(*this); changedRegion.add(extendToMergedAreas(TQRect(d->anchor,d->marker))); TQPoint topLeft(point); Cell* cell = d->view->activeSheet()->cellAt(point); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); topLeft = TQPoint(cell->column(), cell->row()); } d->anchor = topLeft; d->cursor = point; d->marker = topLeft; fixSubRegionDimension(); // TODO remove this sanity check Iterator it = cells().begin() += d->activeSubRegionStart + d->activeSubRegionLength; if (it != insert(it, topLeft, sheet/*, true*/)) { // if the point was inserted clearSubRegion(); } Element* element = *(cells().begin() += 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 = cells().begin(); if (changedRegion == *this) { emit changed(Region(topLeft, sheet)); return; } changedRegion.add(topLeft, sheet); emit changed(changedRegion); } void Selection::initialize(const TQRect& range, Sheet* sheet) { if (!util_isRectValid(range) || ( range == TQRect(0,0,1,1) )) return; if (!sheet) { if (d->sheet) { sheet = d->sheet; } else { sheet = d->view->activeSheet(); } } Region changedRegion(*this); changedRegion.add(extendToMergedAreas(TQRect(d->anchor,d->marker))); TQPoint topLeft(range.topLeft()); Cell* cell = d->view->activeSheet()->cellAt(topLeft); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); topLeft = TQPoint(cell->column(), cell->row()); } TQPoint bottomRight(range.bottomRight()); cell = d->view->activeSheet()->cellAt(bottomRight); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); bottomRight = TQPoint(cell->column(), cell->row()); } d->anchor = topLeft; d->cursor = bottomRight; d->marker = bottomRight; fixSubRegionDimension(); // TODO remove this sanity check Iterator it = cells().begin() += d->activeSubRegionStart + d->activeSubRegionLength; if (it != insert(it, TQRect(topLeft, bottomRight), sheet/*, true*/)) { // if the range was inserted clearSubRegion(); } Element* element = *(cells().begin() += 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 = cells().begin(); if (changedRegion == *this) { return; } changedRegion.add(TQRect(topLeft, bottomRight), sheet); emit changed(changedRegion); } void Selection::initialize(const Region& region, Sheet* sheet) { if (!region.isValid()) return; if (!sheet) { if (d->sheet) { sheet = d->sheet; } else { sheet = d->view->activeSheet(); } } Region changedRegion(*this); changedRegion.add(extendToMergedAreas(TQRect(d->anchor,d->marker))); // TODO Stefan: handle subregion insertion // TODO Stefan: handle obscured cells correctly clear(); 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()]); } TQPoint topLeft(cells().last()->rect().normalize().topLeft()); Cell* cell = d->view->activeSheet()->cellAt(topLeft); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); topLeft = TQPoint(cell->column(), cell->row()); } TQPoint bottomRight(cells().last()->rect().normalize().bottomRight()); cell = d->view->activeSheet()->cellAt(bottomRight); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); bottomRight = TQPoint(cell->column(), cell->row()); } d->anchor = topLeft; d->cursor = topLeft; d->marker = bottomRight; d->activeElement = --cells().end(); d->activeSubRegionStart = 0; d->activeSubRegionLength = cells().count(); if (changedRegion == *this) { return; } changedRegion.add( region ); emit changed(changedRegion); } void Selection::update() { emit changed(*this); } void Selection::update(const TQPoint& point) { uint count = cells().count(); if (cells().isEmpty()) { add(point); d->activeSubRegionLength += cells().count() - count; return; } if (d->activeElement == cells().end()) { // we're not empty, so this will not become again end() d->activeElement--; } Sheet* sheet = (*d->activeElement)->sheet(); if (sheet != d->view->activeSheet()) { extend(point); d->activeSubRegionLength += cells().count() - count; return; } TQPoint topLeft(point); Cell* cell = d->view->activeSheet()->cellAt(point); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); topLeft = TQPoint(cell->column(), cell->row()); } if (topLeft == d->marker) { return; } TQRect area1 = (*d->activeElement)->rect().normalize(); TQRect newRange = extendToMergedAreas(TQRect(d->anchor, topLeft)); Element* oldElement = *d->activeElement; // returns iterator to the next element or end Iterator it = cells().remove(d->activeElement); delete oldElement; // returns iterator to the new element (before 'it') or 'it' d->activeElement = insert(it, newRange, sheet, d->multipleSelection); 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().end()) { d->activeElement--; } TQRect area2 = (*d->activeElement)->rect().normalize(); 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 = TQMIN(area1.left(), area2.left()); int innerLeft = TQMAX(area1.left(), area2.left()); int farTop = TQMIN(area1.top(), area2.top()); int innerTop = TQMAX(area1.top(), area2.top()); int farRight = TQMAX(area1.right(), area2.right()); int innerRight = TQMIN(area1.right(), area2.right()); int farBottom = TQMAX(area1.bottom(), area2.bottom()); int innerBottom = TQMIN(area1.bottom(), area2.bottom()); if (newLeft) { changedRegion.add(TQRect(TQPoint(farLeft, innerTop), TQPoint(innerLeft-1, innerBottom))); if (newTop) { changedRegion.add(TQRect(TQPoint(farLeft, farTop), TQPoint(innerLeft-1, innerTop-1))); } if (newBottom) { changedRegion.add(TQRect(TQPoint(farLeft, innerBottom+1), TQPoint(innerLeft-1, farBottom))); } } if (newTop) { changedRegion.add(TQRect(TQPoint(innerLeft, farTop), TQPoint(innerRight, innerTop-1))); } if (newRight) { changedRegion.add(TQRect(TQPoint(innerRight+1, innerTop), TQPoint(farRight, innerBottom))); if (newTop) { changedRegion.add(TQRect(TQPoint(innerRight+1, farTop), TQPoint(farRight, innerTop-1))); } if (newBottom) { changedRegion.add(TQRect(TQPoint(innerRight+1, innerBottom+1), TQPoint(farRight, farBottom))); } } if (newBottom) { changedRegion.add(TQRect(TQPoint(innerLeft, innerBottom+1), TQPoint(innerRight, farBottom))); } d->marker = topLeft; d->cursor = point; emit changed(changedRegion); } void Selection::extend(const TQPoint& point, Sheet* sheet) { if (!util_isPointValid(point)) return; if (isEmpty()) { initialize(point, sheet); return; } if (d->activeElement == cells().end()) { // we're not empty, so this will not become again end() d->activeElement--; } if (!sheet) { if (d->sheet) { sheet = d->sheet; } else { sheet = d->view->activeSheet(); } } Region changedRegion = Region(extendToMergedAreas(TQRect(d->marker,d->marker))); TQPoint topLeft(point); Cell* cell = d->view->activeSheet()->cellAt(point); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); topLeft = TQPoint(cell->column(), cell->row()); } uint count = cells().count(); if (d->multipleSelection) { d->activeElement = insert(++d->activeElement, point, sheet, false); } else { eor(topLeft, sheet); d->activeElement = --cells().end(); } d->anchor = (*d->activeElement)->rect().topLeft(); d->cursor = (*d->activeElement)->rect().bottomRight(); d->marker = d->cursor; d->activeSubRegionLength += cells().count() - count; changedRegion.add(topLeft, sheet); changedRegion.add(*this); emit changed(changedRegion); } void Selection::extend(const TQRect& range, Sheet* sheet) { //See comment in Selection::initialize(const TQRect& range, Sheet* sheet) if (!util_isRectValid(range) || (range == TQRect(0,0,1,1))) return; if (isEmpty()) { initialize(range, sheet); return; } if (d->activeElement == cells().end()) { // we're not empty, so this will not become again end() d->activeElement--; } if (!sheet) { if (d->sheet) { sheet = d->sheet; } else { sheet = d->view->activeSheet(); } } TQPoint topLeft(range.topLeft()); Cell* cell = d->view->activeSheet()->cellAt(topLeft); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); topLeft = TQPoint(cell->column(), cell->row()); } TQPoint bottomRight(range.bottomRight()); cell = d->view->activeSheet()->cellAt(bottomRight); if (cell->isObscured() && cell->isPartOfMerged()) { cell = cell->obscuringCells().first(); bottomRight = TQPoint(cell->column(), cell->row()); } d->anchor = topLeft; d->cursor = topLeft; d->marker = bottomRight; uint count = cells().count(); Element* element; if (d->multipleSelection) { d->activeElement = insert(++d->activeElement, extendToMergedAreas(TQRect(topLeft, bottomRight)).normalize(), sheet, false); element = (d->activeElement == cells().end()) ? 0 : *d->activeElement; } else { element = add(extendToMergedAreas(TQRect(topLeft, bottomRight)).normalize(), sheet); d->activeElement = --cells().end(); } 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; emit changed(*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; emit changed(*this); } Selection::Element* Selection::eor(const TQPoint& point, Sheet* sheet) { if (isSingular()) { return Region::add(point, sheet); } return Region::eor(point, sheet); } const TQPoint& Selection::anchor() const { return d->anchor; } const TQPoint& Selection::cursor() const { return d->cursor; } const TQPoint& Selection::marker() const { return d->marker; } bool Selection::isSingular() const { return Region::isSingular(); } TQRect Selection::selectionHandleArea() const { int column, row; // complete rows/columns are selected, use the marker. if (isColumnOrRowSelected()) { column = d->marker.x(); row = d->marker.y(); } else { column = lastRange().right(); row = lastRange().bottom(); } const Cell* cell = d->view->activeSheet()->cellAt(column, row); double xpos = d->view->activeSheet()->dblColumnPos( column ); double ypos = d->view->activeSheet()->dblRowPos( row ); double width = cell->dblWidth( column ); double height = cell->dblHeight( row ); TQPoint rightBottom( d->view->doc()->zoomItX( xpos + width ), d->view->doc()->zoomItY( ypos + height ) ); TQRect handle( ( rightBottom.x() - 2 ), ( rightBottom.y() - 2 ), ( 5 ), ( 5 ) ); return handle; } TQString Selection::name(Sheet* sheet) const { return Region::name(sheet ? sheet : d->sheet); } void Selection::setSheet(Sheet* sheet) { d->sheet = sheet; } Sheet* Selection::sheet() const { return d->sheet; } void Selection::setActiveElement(const TQPoint& point) { uint counter = 0; Iterator end = cells().end(); for (Iterator it = cells().begin(); it != end; ++it) { TQRect range = (*it)->rect(); if (range.topLeft() == point || range.bottomRight() == point) { d->anchor = range.topLeft(); d->cursor = range.bottomRight(); d->marker = range.bottomRight(); d->activeElement = it; d->activeSubRegionStart = counter; d->activeSubRegionLength = 1; if (d->view->canvasWidget()->editor()) { d->view->canvasWidget()->editor()->setCursorToRange(counter); } } counter++; } } void Selection::setActiveElement(uint pos) { if (pos >= cells().count()) { kdDebug() << "Selection::setActiveElement: position exceeds list" << endl; d->activeElement = cells().begin(); return; } Iterator it = cells().begin() += pos; TQRect range = (*it)->rect(); d->anchor = range.topLeft(); d->cursor = range.bottomRight(); d->marker = range.bottomRight(); d->activeElement = it; } Region::Element* Selection::activeElement() const { return (d->activeElement == cells().end()) ? 0 : *d->activeElement; } void Selection::clear() { d->activeSubRegionStart = 0; d->activeSubRegionLength = 0; Region::clear(); d->activeElement = cells().begin(); } void Selection::clearSubRegion() { if (isEmpty()) { return; } // kdDebug() << *this << endl; // kdDebug() << d->activeSubRegionStart << endl; // kdDebug() << d->activeSubRegionLength << endl; Iterator it = cells().begin(); Iterator end = it += d->activeSubRegionStart; end += d->activeSubRegionLength; while (it != end) { /* kdDebug() << (*it)->name() << endl;*/ delete *it; it = cells().remove(it); } d->activeSubRegionLength = 0; d->activeElement = it; /* kdDebug() << "ENDE" << endl;*/ } void Selection::fixSubRegionDimension() { if (d->activeSubRegionStart > cells().count()) { kdDebug() << "Selection::fixSubRegionDimension: start position exceeds list" << endl; d->activeSubRegionStart = 0; d->activeSubRegionLength = cells().count(); return; } if (d->activeSubRegionStart + d->activeSubRegionLength > cells().count()) { kdDebug() << "Selection::fixSubRegionDimension: length exceeds list" << endl; d->activeSubRegionLength = cells().count() - d->activeSubRegionStart; return; } } void Selection::setActiveSubRegion(uint start, uint length) { // kdDebug() << k_funcinfo << endl; d->activeSubRegionStart = start; d->activeSubRegionLength = length; fixSubRegionDimension(); d->activeElement = cells().begin() += d->activeSubRegionStart; } TQString Selection::activeSubRegionName() const { // kdDebug() << k_funcinfo << endl; // kdDebug() << *this << endl; // kdDebug() << "start = " << d->activeSubRegionStart << ", len = " << d->activeSubRegionLength << endl; TQStringList names; Iterator it = cells().begin(); it += d->activeSubRegionStart; Iterator end = it; end += d->activeSubRegionLength; while (it != end) { names += (*it++)->name(d->sheet); } /* kdDebug() << "ENDE" << endl;*/ return names.isEmpty() ? "" : names.join(";"); } void Selection::setMultipleSelection(bool state) { d->multipleSelection = state; } const TQValueList& Selection::colors() const { return d->colors; } TQRect Selection::lastRange(bool extend) const { TQRect selection = TQRect(d->anchor, d->marker).normalize(); return extend ? extendToMergedAreas(selection) : selection; } TQRect Selection::selection(bool extend) const { TQRect selection = TQRect(d->anchor, d->marker).normalize(); return extend ? extendToMergedAreas(selection) : selection; } TQRect Selection::extendToMergedAreas(TQRect area) const { if (!d->view->activeSheet()) return area; area = area.normalize(); // TODO Stefan: avoid this const Cell *cell = d->view->activeSheet()->cellAt(area.left(), area.top()); if( Region::Range(area).isColumn() || Region::Range(area).isRow() ) { return area; } else if ( !(cell->isObscured() && 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 = d->view->activeSheet()->cellAt( x, y ); if( cell->doesMergeCells()) { right=TQMAX(right,cell->mergedXCells()+x); bottom=TQMAX(bottom,cell->mergedYCells()+y); } else if ( cell->isObscured() && cell->isPartOfMerged() ) { cell = cell->obscuringCells().first(); left=TQMIN(left,cell->column()); top=TQMIN(top,cell->row()); bottom=TQMAX(bottom,cell->row() + cell->mergedYCells()); right=TQMAX(right,cell->column() + cell->mergedXCells()); } } area.setCoords(left,top,right,bottom); } return area; } Selection::Region::Point* Selection::createPoint(const TQPoint& point) const { return new Point(point); } Selection::Region::Point* Selection::createPoint(const TQString& string) const { return new Point(string); } Selection::Region::Point* Selection::createPoint(const Point& point) const { return new Point(point); } Selection::Region::Range* Selection::createRange(const TQRect& rect) const { return new Range(rect); } Selection::Region::Range* Selection::createRange(const TQString& string) const { return new Range(string); } Selection::Region::Range* Selection::createRange(const Range& range) const { return new Range(range); } /*************************************************************************** class Point ****************************************************************************/ Selection::Point::Point(const TQPoint& point) : Region::Point(point), m_color(TQt::black), m_columnFixed(false), m_rowFixed(false) { } Selection::Point::Point(const TQString& string) : Region::Point(string), m_color(TQt::black), m_columnFixed(false), m_rowFixed(false) { if (!isValid()) { return; } uint p = 0; // Fixed? if (string[p++] == '$') { m_columnFixed = true; } //search for the first character != text int result = string.find( TQRegExp("[^A-Za-z]+"), p ); if (string[result] == '$') { m_rowFixed = true; } } /*************************************************************************** class Range ****************************************************************************/ Selection::Range::Range(const TQRect& range) : Region::Range(range), m_color(TQt::black), m_leftFixed(false), m_rightFixed(false), m_topFixed(false), m_bottomFixed(false) { } Selection::Range::Range(const TQString& string) : Region::Range(string), m_color(TQt::black), m_leftFixed(false), m_rightFixed(false), m_topFixed(false), m_bottomFixed(false) { if (!isValid()) { return; } int delimiterPos = string.find(':'); if (delimiterPos == -1) { return; } Selection::Point ul(string.left(delimiterPos)); Selection::Point lr(string.mid(delimiterPos + 1)); if (!ul.isValid() || !lr.isValid()) { return; } m_leftFixed = ul.columnFixed(); m_rightFixed = lr.columnFixed(); m_topFixed = ul.rowFixed(); m_bottomFixed = lr.rowFixed(); } } // namespace KSpread #include "selection.moc"