| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 // The callback method type invoked when the selection changes. | |
| 6 typedef void SelectionCallback(); | |
| 7 | |
| 8 // The method type for the SelectionManager.applyToSelectedCells method. | |
| 9 typedef void SelectionApply(CellLocation location); | |
| 10 | |
| 11 class BoundingBox { | |
| 12 int height; | |
| 13 int left; | |
| 14 int top; | |
| 15 int width; | |
| 16 | |
| 17 BoundingBox(this.left, this.top, this.width, this.height) { } | |
| 18 } | |
| 19 | |
| 20 interface SelectionListener { | |
| 21 void onSelectionChanged(); | |
| 22 } | |
| 23 | |
| 24 class SelectionManager { | |
| 25 static ZoomTracker _zoomTracker; | |
| 26 | |
| 27 SelectionListener _listener; | |
| 28 int _originColumn; | |
| 29 int _originRow; | |
| 30 CellLocation _selectedCell; | |
| 31 CellLocation _selectionCorner; | |
| 32 DivElement _selectionDiv; | |
| 33 Spreadsheet _spreadsheet; | |
| 34 HtmlTable _table; | |
| 35 | |
| 36 CellLocation get selectedCell() => _selectedCell; | |
| 37 | |
| 38 void set selectedCell(CellLocation value) { | |
| 39 _selectedCell = value; | |
| 40 } | |
| 41 | |
| 42 CellLocation get selectionCorner() => _selectionCorner; | |
| 43 | |
| 44 void set selectionCorner(CellLocation value) { | |
| 45 _selectionCorner = value; | |
| 46 } | |
| 47 | |
| 48 Spreadsheet get spreadsheet() => _spreadsheet; | |
| 49 | |
| 50 SelectionManager(this._spreadsheet, SpreadsheetPresenter presenter, Window win
dow, this._table) | |
| 51 : _selectedCell = null, _selectionCorner = null, _originRow = 0, _originCo
lumn = 0 { | |
| 52 Document doc = document; | |
| 53 | |
| 54 Element spreadsheetElement = presenter.spreadsheetElement; | |
| 55 _selectionDiv = new Element.tag("div"); | |
| 56 _selectionDiv.id = "selection-${_spreadsheet.name}"; | |
| 57 _selectionDiv.attributes["class"] = "selection"; | |
| 58 _selectionDiv.style.setProperty("display", "none"); | |
| 59 spreadsheetElement.nodes.add(_selectionDiv); | |
| 60 | |
| 61 Element thumb = new Element.tag("div"); | |
| 62 thumb.id = "selection-thumb-${_spreadsheet.name}"; | |
| 63 thumb.attributes["class"] = "selection-thumb"; | |
| 64 _selectionDiv.nodes.add(thumb); | |
| 65 | |
| 66 // Update selection after changing zoom factor, to reduce problems with posi
tion precision | |
| 67 // Only create a single tracker per page | |
| 68 if (_zoomTracker == null) { | |
| 69 _zoomTracker = new ZoomTracker(window); | |
| 70 _zoomTracker.addListener((double oldZoom, double newZoom) { | |
| 71 updateSelection(); | |
| 72 }); | |
| 73 } | |
| 74 } | |
| 75 | |
| 76 // Move the selection by a given (row, col) amount | |
| 77 void advanceSelectedCell(int rows, int cols) { | |
| 78 if (_selectedCell != null) { | |
| 79 int row = Math.max(1, _selectedCell.row + rows); | |
| 80 int col = Math.max(1, _selectedCell.col + cols); | |
| 81 _selectedCell = new CellLocation(_spreadsheet, new RowCol(row, col)); | |
| 82 _selectionCorner = _selectedCell; | |
| 83 selectionChanged(); | |
| 84 } | |
| 85 } | |
| 86 | |
| 87 void applyToSelectedCells(SelectionApply apply) { | |
| 88 CellRange range = _getSelectionRange(_selectedCell, _selectionCorner); | |
| 89 if (range == null) { | |
| 90 return; | |
| 91 } | |
| 92 | |
| 93 range.forEach(apply); | |
| 94 } | |
| 95 | |
| 96 // Hide the selection marquee and empty the selection range | |
| 97 void clearSelection() { | |
| 98 _selectionDiv.style.setProperty("display", "none"); | |
| 99 _selectedCell = _selectionCorner = null; | |
| 100 selectionChanged(); | |
| 101 } | |
| 102 | |
| 103 // Return a BoundingBox for the given CellRange, clipped to the visible region
of the table | |
| 104 // TODO: deal with full row and/or column selection | |
| 105 Future<BoundingBox> getBoundingBoxForRange(CellRange r) { | |
| 106 // Modify the overlay for entire row/column selection | |
| 107 int minRow = r.minCorner.row; | |
| 108 int maxRow = r.maxCorner.row; | |
| 109 int minCol = r.minCorner.col; | |
| 110 int maxCol = r.maxCorner.col; | |
| 111 | |
| 112 // FIXME - provide the table size to this class in a better way | |
| 113 int maxVisibleRow = _table.numRows() - 1; | |
| 114 TableRowElement tableRow = _table.getRowElement(0); | |
| 115 int maxVisibleCol = tableRow.cells.length - 1; | |
| 116 | |
| 117 if (r.minCorner.row == 0 && r.maxCorner.row == 0) { | |
| 118 minRow = 1; | |
| 119 maxRow = maxVisibleRow + _originRow; | |
| 120 } | |
| 121 if (r.minCorner.col == 0 && r.maxCorner.col == 0) { | |
| 122 minCol = 1; | |
| 123 maxCol = maxVisibleCol + _originColumn; | |
| 124 } | |
| 125 | |
| 126 // Check whether the range is entirely outside the visible area | |
| 127 if (maxCol <= _originColumn) { | |
| 128 return null; | |
| 129 } | |
| 130 if (maxRow <= _originRow) { | |
| 131 return null; | |
| 132 } | |
| 133 if (minCol > _originColumn + maxVisibleCol) { | |
| 134 return null; | |
| 135 } | |
| 136 if (minRow > _originRow + maxVisibleRow) { | |
| 137 return null; | |
| 138 } | |
| 139 | |
| 140 final completer = new Completer<BoundingBox>(); | |
| 141 | |
| 142 // Clip the range to the visible region | |
| 143 int minPinned = _pin(minRow - _originRow, 1, maxVisibleRow); | |
| 144 TableRowElement minRowElmt = _table.getRowElement(minPinned); | |
| 145 TableCellElement minCellElmt = | |
| 146 minRowElmt.cells[_pin(minCol - _originColumn, 1, maxVisibleCol)]; | |
| 147 int maxPinned = _pin(maxRow - _originRow, 1, maxVisibleRow); | |
| 148 TableRowElement maxRowElmt = _table.getRowElement(maxPinned); | |
| 149 TableCellElement maxCellElmt = | |
| 150 maxRowElmt.cells[_pin(maxCol - _originColumn, 1, maxVisibleCol)]; | |
| 151 | |
| 152 // We need bounding box relative to the container which will be offset by | |
| 153 // css. | |
| 154 final tableRect = _table.rect; | |
| 155 final minCellElmtRect = minCellElmt.rect; | |
| 156 final maxCellElmtRect = maxCellElmt.rect; | |
| 157 | |
| 158 window.requestLayoutFrame(() { | |
| 159 ClientRect orgP = tableRect.value.bounding; | |
| 160 ClientRect minP = minCellElmtRect.value.bounding; | |
| 161 ClientRect maxP = maxCellElmtRect.value.bounding; | |
| 162 completer.complete(new BoundingBox( | |
| 163 (minP.left - orgP.left).toInt(), | |
| 164 (minP.top - orgP.top).toInt(), | |
| 165 (maxP.left - minP.left + maxCellElmtRect.value.client.width).toInt(), | |
| 166 (maxP.top - minP.top + maxCellElmtRect.value.client.height).toInt())); | |
| 167 }); | |
| 168 return completer.future; | |
| 169 } | |
| 170 | |
| 171 CellRange getSelectionRange() => _getSelectionRange(_selectedCell, _selectionC
orner); | |
| 172 | |
| 173 bool isColumnSelected(int column) { | |
| 174 CellRange r = _getSelectionRange(_selectedCell, _selectionCorner); | |
| 175 if (r == null) { | |
| 176 return false; | |
| 177 } | |
| 178 return r.minCorner.col <= column && r.maxCorner.col >= column; | |
| 179 } | |
| 180 | |
| 181 bool isRowSelected(int row) { | |
| 182 CellRange r = _getSelectionRange(_selectedCell, _selectionCorner); | |
| 183 if (r == null) { | |
| 184 return false; | |
| 185 } | |
| 186 return r.minCorner.row <= row && r.maxCorner.row >= row; | |
| 187 } | |
| 188 | |
| 189 bool isSelectionEmpty() => _selectedCell == null; | |
| 190 | |
| 191 bool isSingleCellSelection() => _selectedCell == _selectionCorner; | |
| 192 | |
| 193 void selectionChanged() { | |
| 194 if (_listener != null) { | |
| 195 _listener.onSelectionChanged(); | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 void setListener(SelectionListener listener) { | |
| 200 _listener = listener; | |
| 201 } | |
| 202 | |
| 203 // Called when the origin of the SpreadsheetPresenter changes | |
| 204 // The selection marquee is updated to the new physical position | |
| 205 // and the dependencies are redisplayed | |
| 206 void setOrigin(int originRow, int originColumn) { | |
| 207 if (originRow == _originRow && originColumn == _originColumn) { | |
| 208 return; | |
| 209 } | |
| 210 _originRow = originRow; | |
| 211 _originColumn = originColumn; | |
| 212 updateSelection(); | |
| 213 selectionChanged(); | |
| 214 } | |
| 215 | |
| 216 void setSelectedCell(int row, int col) { | |
| 217 _selectedCell = new CellLocation(_spreadsheet, new RowCol(row, col)); | |
| 218 _selectionCorner = _selectedCell; | |
| 219 selectionChanged(); | |
| 220 } | |
| 221 | |
| 222 // Draw or hide the selection marquee using current cell metrics | |
| 223 void updateSelection() { | |
| 224 CellRange r = _getSelectionRange(_selectedCell, _selectionCorner); | |
| 225 if (r == null) { | |
| 226 clearSelection(); | |
| 227 return; | |
| 228 } | |
| 229 | |
| 230 getBoundingBoxForRange(r).then((BoundingBox box) { | |
| 231 if (box != null) { | |
| 232 _selectionDiv.style.setProperty("left", HtmlUtils.toPx(box.left)); | |
| 233 _selectionDiv.style.setProperty("top", HtmlUtils.toPx(box.top)); | |
| 234 _selectionDiv.style.setProperty("width", HtmlUtils.toPx(box.width)); | |
| 235 _selectionDiv.style.setProperty("height", HtmlUtils.toPx(box.height)); | |
| 236 _selectionDiv.style.removeProperty("display"); | |
| 237 } else { | |
| 238 _selectionDiv.style.setProperty("display", "none"); | |
| 239 } | |
| 240 }); | |
| 241 } | |
| 242 | |
| 243 // Return the selection range for the given corners. | |
| 244 // If the first corner is on a row header, it is assumed that the entire | |
| 245 // row is selected, and the returned range has min.col == 0 and max.col == 0. | |
| 246 // If the first corner is on a column header, it is assumed that the entire | |
| 247 // column is selected, and the returned range has min.row == 0 and max.row ==
0. | |
| 248 CellRange _getSelectionRange(CellLocation corner1, CellLocation corner2) { | |
| 249 if (corner1 == null || corner2 == null) { | |
| 250 return null; | |
| 251 } | |
| 252 if (corner1.spreadsheet != corner2.spreadsheet) { | |
| 253 throw new RuntimeException("Invalid range: Corner spreadsheets do not matc
h."); | |
| 254 } | |
| 255 | |
| 256 int minRow = Math.min(corner1.row, corner2.row); | |
| 257 int maxRow = Math.max(corner1.row, corner2.row); | |
| 258 int minCol = Math.min(corner1.col, corner2.col); | |
| 259 int maxCol = Math.max(corner1.col, corner2.col); | |
| 260 | |
| 261 // If the initial mousedown was in the upper-left corner header cell, use th
e entire | |
| 262 // spreadsheet as the range. If the initial mousedown was on a header, use t
he entire | |
| 263 // set of rows/columns as the range. | |
| 264 if (corner1.col == 0 && corner1.row == 0) { | |
| 265 return new CellRange.spreadsheet(corner1.spreadsheet); | |
| 266 } else if (corner1.col == 0) { | |
| 267 return new CellRange.rows(corner1.spreadsheet, minRow, maxRow); | |
| 268 } else if (corner1.row == 0) { | |
| 269 return new CellRange.columns(corner1.spreadsheet, minCol, maxCol); | |
| 270 } | |
| 271 | |
| 272 return new CellRange(corner1.spreadsheet, new RowCol(minRow, minCol), | |
| 273 new RowCol(maxRow, maxCol)); | |
| 274 } | |
| 275 | |
| 276 // Return the value closest to x in the range [min, max] | |
| 277 int _pin(int x, int min, int max) => Math.max(Math.min(x, max), min); | |
| 278 } | |
| OLD | NEW |