diff --git a/src/Mod/Spreadsheet/App/PropertySheet.cpp b/src/Mod/Spreadsheet/App/PropertySheet.cpp index ac8b8cc8e7..6182619095 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.cpp +++ b/src/Mod/Spreadsheet/App/PropertySheet.cpp @@ -151,6 +151,38 @@ bool PropertySheet::isValidAlias(const std::string &candidate) return false; } +namespace +{ + +// A utility function that gets the range (the minimum and maximum row and column) out of a +// vector of cell addresses. Note that it's possible that neither cell in the tuple itself +// has data in it, but operating on all cells in the inclusive range specified by the tuple +// is guaranteed to include all cells in the passed-in vector, and no cells exist to the +// left, right, above, or below the block specified. +std::tuple extractRange(const std::vector &cells) +{ + CellAddress firstRowAndColumn; + CellAddress lastRowAndColumn; + for (const auto & cell : cells) { + int row = cell.row(); + int column = cell.col(); + if (row < firstRowAndColumn.row() || !firstRowAndColumn.isValid()) { + firstRowAndColumn.setRow(row); + } + if (column < firstRowAndColumn.col() || !firstRowAndColumn.isValid()) { + firstRowAndColumn.setCol(column); + } + if (row > lastRowAndColumn.row() || !lastRowAndColumn.isValid()) { + lastRowAndColumn.setRow(row); + } + if (column > lastRowAndColumn.col() || !lastRowAndColumn.isValid()) { + lastRowAndColumn.setCol(column); + } + } + return std::make_tuple(firstRowAndColumn, lastRowAndColumn); +} +}// namespace + std::vector PropertySheet::getUsedCells() const { std::vector usedSet; @@ -163,6 +195,12 @@ std::vector PropertySheet::getUsedCells() const return usedSet; } +std::tuple PropertySheet::getUsedRange() const +{ + auto usedCells = getUsedCells(); + return extractRange(usedCells); +} + std::vector PropertySheet::getNonEmptyCells() const { std::vector usedSet; @@ -177,6 +215,12 @@ std::vector PropertySheet::getNonEmptyCells() const return usedSet; } +std::tuple PropertySheet::getNonEmptyRange() const +{ + auto nonEmptyCells = getNonEmptyCells(); + return extractRange(nonEmptyCells); +} + void PropertySheet::setDirty(CellAddress address) { /* Merged cells will automatically force an update of the top left cell diff --git a/src/Mod/Spreadsheet/App/PropertySheet.h b/src/Mod/Spreadsheet/App/PropertySheet.h index 2109b385fd..32c1944695 100644 --- a/src/Mod/Spreadsheet/App/PropertySheet.h +++ b/src/Mod/Spreadsheet/App/PropertySheet.h @@ -115,8 +115,12 @@ public: std::vector getUsedCells() const; + std::tuple getUsedRange() const; + std::vector getNonEmptyCells() const; + std::tuple getNonEmptyRange() const; + Sheet * sheet() const { return owner; } const std::set & getDirty() { return dirty; } diff --git a/src/Mod/Spreadsheet/App/SheetPy.xml b/src/Mod/Spreadsheet/App/SheetPy.xml index f9a8348812..ede5106970 100644 --- a/src/Mod/Spreadsheet/App/SheetPy.xml +++ b/src/Mod/Spreadsheet/App/SheetPy.xml @@ -201,5 +201,32 @@ Get a list of the names of all cells with data in them. + + + + +getUsedRange() + +Get a the total range of the used cells in a sheet, as a pair of strings +representing the lowest row and column that are used, and the highest row and +column that are used (inclusive). Note that the actual first and last cell +of the block are not necessarily used. + + + + + + + +getNonEmptyRange() + +Get a the total range of the used cells in a sheet, as a pair of cell addresses +representing the lowest row and column that contain data, and the highest row and +column that contain data (inclusive). Note that the actual first and last cell +of the block do not necessarily contain anything. + + + + diff --git a/src/Mod/Spreadsheet/App/SheetPyImp.cpp b/src/Mod/Spreadsheet/App/SheetPyImp.cpp index 415be1afd7..7f8a7d9eaf 100644 --- a/src/Mod/Spreadsheet/App/SheetPyImp.cpp +++ b/src/Mod/Spreadsheet/App/SheetPyImp.cpp @@ -990,16 +990,34 @@ PyObject *SheetPy::getUsedCells(PyObject *args) return Py::new_reference_to(pyCellList); } +PyObject *SheetPy::getUsedRange(PyObject *args) +{ + auto usedRange = getSheetPtr()->getCells()->getUsedRange(); + Py::Tuple pyTuple(2); + pyTuple[0] = Py::String(std::get<0>(usedRange).toString()); + pyTuple[1] = Py::String(std::get<1>(usedRange).toString()); + return Py::new_reference_to(pyTuple); +} + PyObject *SheetPy::getNonEmptyCells(PyObject *args) { - auto usedCells = getSheetPtr()->getCells()->getNonEmptyCells(); + auto nonEmptyCells = getSheetPtr()->getCells()->getNonEmptyCells(); Py::List pyCellList; - for (const auto &cell : usedCells) { + for (const auto &cell : nonEmptyCells) { pyCellList.append(Py::String(cell.toString())); } return Py::new_reference_to(pyCellList); } +PyObject *SheetPy::getNonEmptyRange(PyObject *args) +{ + auto nonEmptyRange = getSheetPtr()->getCells()->getNonEmptyRange(); + Py::Tuple pyTuple(2); + pyTuple[0] = Py::String(std::get<0>(nonEmptyRange).toString()); + pyTuple[1] = Py::String(std::get<1>(nonEmptyRange).toString()); + return Py::new_reference_to(pyTuple); +} + // +++ custom attributes implementer ++++++++++++++++++++++++++++++++++++++++ diff --git a/src/Mod/Spreadsheet/TestSpreadsheet.py b/src/Mod/Spreadsheet/TestSpreadsheet.py index 743d5c72c7..a598bbae5a 100644 --- a/src/Mod/Spreadsheet/TestSpreadsheet.py +++ b/src/Mod/Spreadsheet/TestSpreadsheet.py @@ -1362,6 +1362,20 @@ class SpreadsheetCases(unittest.TestCase): non_empty_cells = sheet.getUsedCells() self.assertEqual(len(non_empty_cells), len(test_cells)) # Alignment counts as "used" + def testGetUsedRange(self): + sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') + test_cells = ['C5','Z3','D10','E20'] + for i,cell in enumerate(test_cells): + sheet.set(cell,str(i)) + used_range = sheet.getUsedRange() + self.assertEquals(used_range,("C3","Z20")) + + for i,cell in enumerate(test_cells): + sheet.set(cell,"") + sheet.setAlignment(cell,"center") + used_range = sheet.getUsedRange() + self.assertEquals(used_range,("C3","Z20")) + def testGetNonEmptyCells(self): sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') test_cells = ['B13','C14','D15'] @@ -1379,6 +1393,23 @@ class SpreadsheetCases(unittest.TestCase): non_empty_cells = sheet.getNonEmptyCells() self.assertEqual(len(non_empty_cells), 0) # Alignment does not count as "non-empty" + def testGetNonEmptyRange(self): + sheet = self.doc.addObject('Spreadsheet::Sheet','Spreadsheet') + test_cells = ['C5','Z3','D10','E20'] + for i,cell in enumerate(test_cells): + sheet.set(cell,str(i)) + non_empty_range = sheet.getNonEmptyRange() + self.assertEquals(non_empty_range,("C3","Z20")) + + for i,cell in enumerate(test_cells): + sheet.set(cell,"") + sheet.setAlignment(cell,"center") + more_cells = ['D10','X5','E10','K15'] + for i,cell in enumerate(more_cells): + sheet.set(cell,str(i)) + non_empty_range = sheet.getNonEmptyRange() + self.assertEquals(non_empty_range,("D5","X15")) + def tearDown(self): #closing doc FreeCAD.closeDocument(self.doc.Name)