Changeset 238563 in webkit
- Timestamp:
- Nov 27, 2018, 11:41:17 AM (8 years ago)
- Location:
- trunk/Source/WebInspectorUI
- Files:
-
- 1 added
- 5 edited
-
ChangeLog (modified) (1 diff)
-
UserInterface/Base/Utilities.js (modified) (1 diff)
-
UserInterface/Controllers/SelectionController.js (added)
-
UserInterface/Main.html (modified) (1 diff)
-
UserInterface/Test.html (modified) (1 diff)
-
UserInterface/Views/Table.js (modified) (14 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebInspectorUI/ChangeLog
r238502 r238563 1 2018-11-27 Matt Baker <mattbaker@apple.com> 2 3 Web Inspector: Table selection should be handled by a SelectionController 4 https://bugs.webkit.org/show_bug.cgi?id=191977 5 <rdar://problem/46253093> 6 7 Reviewed by Devin Rousso. 8 9 Add a SelectionController class, which manages an IndexSet of selected 10 items, and provides operations for adding and removing items from the 11 selection. Complex behaviors such as shift-clicking to select a range of 12 items, and updating the selection using the keyboard, are forwarded to 13 the controller using special-purpose methods that accept DOM Event objects. 14 15 * UserInterface/Base/Utilities.js: 16 17 * UserInterface/Controllers/SelectionController.js: Added. 18 (WI.SelectionController): 19 (WI.SelectionController.prototype.get delegate): 20 (WI.SelectionController.prototype.get lastSelectedItem): 21 (WI.SelectionController.prototype.get selectedItems): 22 (WI.SelectionController.prototype.get allowsMultipleSelection): 23 (WI.SelectionController.prototype.set allowsMultipleSelection): 24 (WI.SelectionController.prototype.get numberOfItems): 25 (WI.SelectionController.prototype.hasSelectedItem): 26 (WI.SelectionController.prototype.selectItem): 27 (WI.SelectionController.prototype.deselectItem): 28 (WI.SelectionController.prototype.selectAll): 29 (WI.SelectionController.prototype.deselectAll): 30 (WI.SelectionController.prototype.removeSelectedItems): 31 (WI.SelectionController.prototype.reset): 32 (WI.SelectionController.prototype.didRemoveItem): 33 (WI.SelectionController.prototype.handleKeyDown): 34 (WI.SelectionController.prototype.handleItemMouseDown.normalizeRange): 35 (WI.SelectionController.prototype.handleItemMouseDown): 36 (WI.SelectionController.prototype._deselectAllAndSelect): 37 (WI.SelectionController.prototype._selectItemsFromArrowKey): 38 (WI.SelectionController.prototype._nextSelectableIndex): 39 (WI.SelectionController.prototype._previousSelectableIndex): 40 (WI.SelectionController.prototype._updateSelectedItems): 41 42 * UserInterface/Main.html: 43 * UserInterface/Test.html: 44 45 * UserInterface/Views/Table.js: 46 (WI.Table): 47 (WI.Table.prototype.get selectedRow): 48 (WI.Table.prototype.get selectedRows): 49 (WI.Table.prototype.get allowsMultipleSelection): 50 (WI.Table.prototype.set allowsMultipleSelection): 51 (WI.Table.prototype.isRowSelected): 52 (WI.Table.prototype.reloadData): 53 (WI.Table.prototype.selectRow): 54 (WI.Table.prototype.deselectRow): 55 (WI.Table.prototype.selectAll): 56 (WI.Table.prototype.deselectAll): 57 (WI.Table.prototype.removeRow): 58 (WI.Table.prototype.removeSelectedRows): 59 (WI.Table.prototype.selectionControllerSelectionDidChange): 60 (WI.Table.prototype.selectionControllerNumberOfItems): 61 (WI.Table.prototype.selectionControllerNextSelectableIndex): 62 (WI.Table.prototype.selectionControllerPreviousSelectableIndex): 63 (WI.Table.prototype._handleKeyDown): 64 (WI.Table.prototype._handleMouseDown): 65 (WI.Table.prototype._removeRows): 66 (WI.Table.prototype._toggleSelectedRowStyle): 67 (WI.Table.prototype._selectRowsFromArrowKey): Deleted. 68 (WI.Table.prototype._handleMouseDown.normalizeRange): Deleted. 69 (WI.Table.prototype._deselectAllAndSelect): Deleted. 70 (WI.Table.prototype._notifySelectionDidChange): Deleted. 71 (WI.Table.prototype._updateSelectedRows): Deleted. 72 1 73 2018-11-26 Devin Rousso <drousso@apple.com> 2 74 -
trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js
r238135 r238563 431 431 }); 432 432 433 Object.defineProperty(MouseEvent.prototype, "commandOrControlKey", 434 { 435 get() 436 { 437 return WI.Platform.name === "mac" ? this.metaKey : this.ctrlKey; 438 } 439 }); 440 433 441 Object.defineProperty(Array, "isTypedArray", 434 442 { -
trunk/Source/WebInspectorUI/UserInterface/Main.html
r238484 r238563 473 473 474 474 <script src="Views/View.js"></script> 475 476 <script src="Controllers/SelectionController.js"></script> 475 477 476 478 <script src="Views/ConsoleCommandView.js"></script> -
trunk/Source/WebInspectorUI/UserInterface/Test.html
r238484 r238563 244 244 <script src="Controllers/Formatter.js"></script> 245 245 <script src="Controllers/ResourceQueryController.js"></script> 246 <script src="Controllers/SelectionController.js"></script> 246 247 <script src="Workers/Formatter/FormatterContentBuilder.js"></script> 247 248 <script src="Views/CodeMirrorAdditions.js"></script> -
trunk/Source/WebInspectorUI/UserInterface/Views/Table.js
r238203 r238563 88 88 this._fillerHeight = 0; // Calculated in _resizeColumnsAndFiller. 89 89 90 this._shiftAnchorIndex = NaN; 91 this._selectedRowIndex = NaN; 92 this._allowsMultipleSelection = false; 93 this._selectedRows = new WI.IndexSet; 90 this._selectionController = new WI.SelectionController(this); 94 91 95 92 this._resizers = []; … … 126 123 get delegate() { return this._delegate; } 127 124 get rowHeight() { return this._rowHeight; } 128 get selectedRow() { return this._selectedRowIndex; } 125 126 get selectedRow() 127 { 128 return this._selectionController.lastSelectedItem; 129 } 129 130 130 131 get selectedRows() 131 132 { 132 return Array.from(this._select edRows);133 return Array.from(this._selectionController.selectedItems); 133 134 } 134 135 … … 221 222 get allowsMultipleSelection() 222 223 { 223 return this._ allowsMultipleSelection;224 return this._selectionController.allowsMultipleSelection; 224 225 } 225 226 226 227 set allowsMultipleSelection(flag) 227 228 { 228 if (this._allowsMultipleSelection === flag) 229 return; 230 231 this._allowsMultipleSelection = flag; 232 if (this._allowsMultipleSelection) 233 return; 234 235 if (this._selectedRows.size > 1) { 236 console.assert(this._selectedRowIndex >= 0); 237 this._selectedRows = new WI.IndexSet([this._selectedRowIndex]); 238 this._notifySelectionDidChange(); 239 } 229 this._selectionController.allowsMultipleSelection = flag; 240 230 } 241 231 242 232 isRowSelected(rowIndex) 243 233 { 244 return this._select edRows.has(rowIndex);234 return this._selectionController.hasSelectedItem(rowIndex); 245 235 } 246 236 … … 249 239 this._cachedRows.clear(); 250 240 251 this._shiftAnchorIndex = NaN; 252 this._selectedRowIndex = NaN; 253 this._selectedRows.clear(); 241 this._selectionController.reset(); 254 242 255 243 this._cachedNumberOfRows = NaN; … … 321 309 selectRow(rowIndex, extendSelection = false) 322 310 { 323 console.assert(!extendSelection || this._allowsMultipleSelection, "Cannot extend selection with multiple selection disabled."); 324 console.assert(rowIndex >= 0 && rowIndex < this.numberOfRows); 325 326 if (this.isRowSelected(rowIndex)) { 327 if (!extendSelection) 328 this._deselectAllAndSelect(rowIndex); 329 return; 330 } 331 332 if (!extendSelection && this._selectedRows.size) { 333 this._suppressNextSelectionDidChange = true; 334 this.deselectAll(); 335 } 336 337 this._shiftAnchorIndex = NaN; 338 this._selectedRowIndex = rowIndex; 339 this._selectedRows.add(rowIndex); 340 341 this._toggleSelectedRowStyle([this._selectedRowIndex], true); 342 343 this._notifySelectionDidChange(); 311 this._selectionController.selectItem(rowIndex, extendSelection); 344 312 } 345 313 346 314 deselectRow(rowIndex) 347 315 { 348 console.assert(rowIndex >= 0 && rowIndex < this.numberOfRows); 349 350 if (!this.isRowSelected(rowIndex)) 351 return; 352 353 this._toggleSelectedRowStyle([rowIndex], false); 354 355 this._selectedRows.delete(rowIndex); 356 357 if (this._shiftAnchorIndex === rowIndex) 358 this._shiftAnchorIndex = NaN; 359 360 if (this._selectedRowIndex === rowIndex) { 361 this._selectedRowIndex = NaN; 362 if (this._selectedRows.size) { 363 // Find selected row closest to deselected row. 364 let preceding = this._selectedRows.indexLessThan(rowIndex); 365 let following = this._selectedRows.indexGreaterThan(rowIndex); 366 367 if (isNaN(preceding)) 368 this._selectedRowIndex = following; 369 else if (isNaN(following)) 370 this._selectedRowIndex = preceding; 371 else { 372 if ((following - rowIndex) < (rowIndex - preceding)) 373 this._selectedRowIndex = following; 374 else 375 this._selectedRowIndex = preceding; 376 } 377 } 378 } 379 380 this._notifySelectionDidChange(); 316 this._selectionController.deselectItem(rowIndex); 381 317 } 382 318 383 319 selectAll() 384 320 { 385 if (!this.numberOfRows || !this._allowsMultipleSelection) 386 return; 387 388 if (this._selectedRows.size === this.numberOfRows) 389 return; 390 391 this._selectedRows.addRange(0, this.numberOfRows); 392 this._selectedRowIndex = this._selectedRows.size - 1; 393 394 for (let row of this._cachedRows.values()) 395 row.classList.add("selected"); 396 397 this._notifySelectionDidChange(); 321 this._selectionController.selectAll(); 398 322 } 399 323 400 324 deselectAll() 401 325 { 402 const rowIndex = NaN; 403 this._deselectAllAndSelect(rowIndex); 326 this._selectionController.deselectAll(); 404 327 } 405 328 … … 412 335 413 336 this._removeRows(new WI.IndexSet([rowIndex])); 337 this._selectionController.didRemoveItem(rowIndex); 414 338 } 415 339 416 340 removeSelectedRows() 417 341 { 418 let numberOfSelectedRows = this._selectedRows.size;419 if (!numberOfSelectedRows)420 return;421 422 // Try selecting the row following the selection.423 let lastSelectedRow = this._selectedRows.lastIndex;424 let rowToSelect = lastSelectedRow + 1;425 if (rowToSelect === this.numberOfRows) {426 // If no row exists after the last selected row, try selecting a427 // deselected row (hole) within the selection.428 let firstSelectedRow = this._selectedRows.firstIndex;429 if (lastSelectedRow - firstSelectedRow > numberOfSelectedRows) {430 rowToSelect = this._selectedRows.firstIndex + 1;431 while (this._selectedRows.has(rowToSelect))432 rowToSelect++;433 } else {434 // If the selection contains no holes, try selecting the row435 // preceding the selection.436 rowToSelect = firstSelectedRow > 0 ? firstSelectedRow - 1 : NaN;437 }438 }439 440 342 // Change the selection before removing rows. This matches the behavior 441 343 // of macOS Finder (in list and column modes) when removing selected items. 442 let oldSelectedRows = this._selectedRows.copy(); 443 this._deselectAllAndSelect(rowToSelect); 444 this._removeRows(oldSelectedRows); 344 let oldSelectedItems = this._selectionController.selectedItems.copy(); 345 346 this._selectionController.removeSelectedItems(); 347 348 if (!oldSelectedItems.equals(this._selectionController.selectedItems)) 349 this._removeRows(oldSelectedItems); 445 350 } 446 351 … … 666 571 this._cachedWidth = NaN; 667 572 this._cachedHeight = NaN; 573 } 574 575 // SelectionController delegate 576 577 selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems) 578 { 579 if (deselectedItems.size) 580 this._toggleSelectedRowStyle(deselectedItems, false); 581 if (selectedItems.size) 582 this._toggleSelectedRowStyle(selectedItems, true); 583 584 if (selectedItems.size === 1) { 585 let rowIndex = selectedItems.firstIndex; 586 if (!this._isRowVisible(rowIndex)) 587 this.revealRow(rowIndex); 588 } 589 590 if (this._delegate.tableSelectionDidChange) 591 this._delegate.tableSelectionDidChange(this); 592 } 593 594 selectionControllerNumberOfItems(controller) 595 { 596 return this.numberOfRows; 597 } 598 599 selectionControllerNextSelectableIndex(controller, index) 600 { 601 if (index >= this.numberOfRows - 1) 602 return NaN; 603 return index + 1; 604 } 605 606 selectionControllerPreviousSelectableIndex(controller, index) 607 { 608 if (index <= 0) 609 return NaN; 610 return index - 1; 668 611 } 669 612 … … 1306 1249 _handleKeyDown(event) 1307 1250 { 1308 if (!this.numberOfRows) 1309 return; 1310 1311 if (event.key === "a" && event.commandOrControlKey) { 1312 this.selectAll(); 1313 return; 1314 } 1315 1316 if (event.metaKey || event.ctrlKey) 1317 return; 1318 1319 if (event.keyIdentifier === "Up" || event.keyIdentifier === "Down") { 1320 this._selectRowsFromArrowKey(event.keyIdentifier === "Up", event.shiftKey); 1321 1322 this.revealRow(this._selectedRowIndex); 1323 1324 event.preventDefault(); 1325 event.stopPropagation(); 1326 } 1327 } 1328 1329 _selectRowsFromArrowKey(goingUp, shiftKey) 1330 { 1331 if (!this._selectedRows.size) { 1332 let rowIndex = goingUp ? this.numberOfRows - 1 : 0; 1333 this.selectRow(rowIndex); 1334 return; 1335 } 1336 1337 let rowIncrement = goingUp ? -1 : 1; 1338 let rowIndex = this._selectedRowIndex + rowIncrement; 1339 if (rowIndex < 0 || rowIndex >= this.numberOfRows) 1340 return; 1341 1342 let extendSelection = shiftKey && this._allowsMultipleSelection; 1343 1344 if (!extendSelection || !this.isRowSelected(rowIndex)) { 1345 this.selectRow(rowIndex, extendSelection); 1346 return; 1347 } 1348 1349 // Since the row in the direction of movement is selected, we are either 1350 // extending the selection into the row, or deselecting. Determine which 1351 // by checking whether the row opposite the anchor row is selected. 1352 let priorRowIndex = this._selectedRowIndex - rowIncrement; 1353 if (!this.isRowSelected(priorRowIndex)) { 1354 this.deselectRow(this._selectedRowIndex); 1355 return; 1356 } 1357 1358 // The selection is being extended into the row; make it the new 1359 // anchor row then continue searching in the direction of movement 1360 // for an unselected row to select. 1361 for (; rowIndex >= 0 && rowIndex < this.numberOfRows; rowIndex += rowIncrement) { 1362 if (!this.isRowSelected(rowIndex)) { 1363 this.selectRow(rowIndex, extendSelection); 1364 break; 1365 } 1366 1367 this._selectedRowIndex = rowIndex; 1368 } 1251 this._selectionController.handleKeyDown(event); 1369 1252 } 1370 1253 1371 1254 _handleMouseDown(event) 1372 1255 { 1373 if (event.button !== 0 || event.ctrlKey)1374 return;1375 1376 1256 let cell = event.target.enclosingNodeOrSelfWithClass("cell"); 1377 1257 if (!cell) … … 1383 1263 1384 1264 let rowIndex = row.__index; 1385 let isRowSelected = this.isRowSelected(rowIndex);1386 1265 1387 1266 // Before checking if multiple selection is allowed, check if clicking the 1388 1267 // row would cause it to be selected, and whether it is allowed by the delegate. 1389 if (! isRowSelected&& this._delegate.tableShouldSelectRow) {1268 if (!this.isRowSelected(rowIndex) && this._delegate.tableShouldSelectRow) { 1390 1269 let columnIndex = Array.from(row.children).indexOf(cell); 1391 1270 let column = this._visibleColumns[columnIndex]; … … 1394 1273 } 1395 1274 1396 // Command (meta) key takes precedence over shift whether or not multiple 1397 // selection is enabled, so handle it first. 1398 if (event.metaKey) { 1399 if (isRowSelected) 1400 this.deselectRow(rowIndex); 1401 else 1402 this.selectRow(rowIndex, this._allowsMultipleSelection); 1403 return; 1404 } 1405 1406 let shiftExtendSelection = this._allowsMultipleSelection && event.shiftKey; 1407 if (!shiftExtendSelection) { 1408 this.selectRow(rowIndex); 1409 return; 1410 } 1411 1412 let newSelectedRows = this._selectedRows.copy(); 1413 1414 // Shift-clicking when nothing is selected should cause the first row 1415 // through the clicked row to be selected. 1416 if (!newSelectedRows.size) { 1417 this._shiftAnchorIndex = 0; 1418 this._selectedRowIndex = rowIndex; 1419 newSelectedRows.addRange(0, rowIndex + 1); 1420 this._updateSelectedRows(newSelectedRows); 1421 return; 1422 } 1423 1424 if (isNaN(this._shiftAnchorIndex)) 1425 this._shiftAnchorIndex = this._selectedRowIndex; 1426 1427 // Shift-clicking will add to or delete from the current selection, or 1428 // pivot the selection around the anchor (a delete followed by an add). 1429 // We could check for all three cases, and add or delete only those rows 1430 // that are necessary, but it is simpler to throw out the previous shift- 1431 // selected range and add the new range between the anchor and clicked row. 1432 1433 function normalizeRange(startIndex, endIndex) { 1434 return startIndex > endIndex ? [endIndex, startIndex] : [startIndex, endIndex]; 1435 } 1436 1437 if (this._shiftAnchorIndex !== this._selectedRowIndex) { 1438 let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, this._selectedRowIndex); 1439 newSelectedRows.deleteRange(startIndex, endIndex - startIndex + 1); 1440 } 1441 1442 let [startIndex, endIndex] = normalizeRange(this._shiftAnchorIndex, rowIndex); 1443 newSelectedRows.addRange(startIndex, endIndex - startIndex + 1); 1444 1445 this._selectedRowIndex = rowIndex; 1446 1447 this._updateSelectedRows(newSelectedRows); 1275 this._selectionController.handleItemMouseDown(rowIndex, event); 1448 1276 } 1449 1277 … … 1524 1352 } 1525 1353 1526 _deselectAllAndSelect(rowIndex)1527 {1528 if (!this._selectedRows.size)1529 return;1530 1531 if (this._selectedRows.size === 1 && this._selectedRows.firstIndex === rowIndex)1532 return;1533 1534 this._toggleSelectedRowStyle(this._selectedRows, false);1535 1536 this._shiftAnchorIndex = NaN;1537 this._selectedRowIndex = rowIndex;1538 this._selectedRows.clear();1539 1540 if (!isNaN(rowIndex)) {1541 this._selectedRows.add(rowIndex);1542 this._toggleSelectedRowStyle(this._selectedRows, true);1543 }1544 1545 this._notifySelectionDidChange();1546 }1547 1548 1354 _removeRows(rowIndexes) 1549 1355 { … … 1551 1357 1552 1358 let adjustRowAtIndex = (index) => { 1553 let newIndex = index - removed;1554 1359 let row = this._cachedRows.get(index); 1555 1360 if (row) { 1556 this._cachedRows.delete(row.__index); 1557 row.__index = newIndex; 1558 this._cachedRows.set(newIndex, row); 1559 } 1560 1561 if (this.isRowSelected(index)) { 1562 this._selectedRows.delete(index); 1563 this._selectedRows.add(newIndex); 1564 if (this._selectedRowIndex === index) 1565 this._selectedRowIndex = newIndex; 1361 this._cachedRows.delete(index); 1362 row.__index -= removed; 1363 this._cachedRows.set(row.__index, row); 1566 1364 } 1567 1365 }; 1568 1569 if (rowIndexes.has(this._shiftAnchorIndex))1570 this._shiftAnchorIndex = NaN;1571 if (rowIndexes.has(this._selectedRowIndex))1572 this._selectedRowIndex = NaN;1573 1366 1574 1367 for (let index = rowIndexes.firstIndex; index <= rowIndexes.lastIndex; ++index) { … … 1602 1395 } 1603 1396 1604 _notifySelectionDidChange()1605 {1606 if (this._suppressNextSelectionDidChange) {1607 this._suppressNextSelectionDidChange = false;1608 return;1609 }1610 1611 if (this._delegate.tableSelectionDidChange)1612 this._delegate.tableSelectionDidChange(this);1613 }1614 1615 1397 _toggleSelectedRowStyle(rowIndexes, flag) 1616 1398 { … … 1620 1402 row.classList.toggle("selected", flag); 1621 1403 } 1622 }1623 1624 _updateSelectedRows(rowIndexes)1625 {1626 if (this._selectedRows.equals(rowIndexes))1627 return;1628 1629 let deselectedRows = this._selectedRows.difference(rowIndexes);1630 if (deselectedRows.size)1631 this._toggleSelectedRowStyle(deselectedRows, false);1632 1633 let selectedRows = rowIndexes.difference(this._selectedRows);1634 if (selectedRows.size)1635 this._toggleSelectedRowStyle(selectedRows, true);1636 1637 this._selectedRows = rowIndexes;1638 1639 this._notifySelectionDidChange();1640 1404 } 1641 1405 };
Note:
See TracChangeset
for help on using the changeset viewer.