Changeset 238599 in webkit


Ignore:
Timestamp:
Nov 27, 2018 6:55:42 PM (5 years ago)
Author:
Matt Baker
Message:

Web Inspector: TreeOutline should re-use multiple-selection logic from Table
https://bugs.webkit.org/show_bug.cgi?id=191483
<rdar://problem/45953305>

Reviewed by Devin Rousso.

Update TreeOutline to use SelectionController. Adopting SelectionController
in TreeOutline is not as straightforward as it was in Table. Selected items
are tracked by index, and TreeElement lacks an explicit index. As a consequence
TreeElement indexes are calcualted as needed and cached. The cache is cleared
whenever an element is added or removed.

  • UserInterface/Controllers/SelectionController.js:

(WI.SelectionController.prototype.didInsertItem):
(WI.SelectionController.prototype.didRemoveItem):
(WI.SelectionController.prototype.handleKeyDown):
Drive-by syntax error fix.
(WI.SelectionController.prototype._adjustIndexesAfter):
(WI.SelectionController):

  • UserInterface/Views/DOMTreeElement.js:

(WI.DOMTreeElement.prototype.canSelectOnMouseDown):
(WI.DOMTreeElement.prototype.selectOnMouseDown): Deleted.

  • UserInterface/Views/DOMTreeOutline.js:

(WI.DOMTreeOutline.prototype._onmousedown):
Item selection is now handled by SelectionController.

  • UserInterface/Views/ShaderProgramTreeElement.js:

(WI.ShaderProgramTreeElement.prototype.canSelectOnMouseDown):
(WI.ShaderProgramTreeElement.prototype.selectOnMouseDown): Deleted.

  • UserInterface/Views/TreeElement.js:

(WI.TreeElement.prototype.canSelectOnMouseDown):
(WI.TreeElement.prototype._attach):
(WI.TreeElement.prototype.select):
(WI.TreeElement.prototype.deselect):
Route item selection through the parent TreeOutline, in order to go though
the TreeOutline's SelectionController.

(WI.TreeElement.treeElementMouseDown): Deleted.
Moved handler to TreeOutline, which owns the SelectionController that
needs to respond to mouse events.

  • UserInterface/Views/TreeOutline.js:

(WI.TreeOutline):
(WI.TreeOutline.prototype.get allowsMultipleSelection):
(WI.TreeOutline.prototype.set allowsMultipleSelection):
(WI.TreeOutline.prototype.get selectedTreeElement):
(WI.TreeOutline.prototype.set selectedTreeElement):
(WI.TreeOutline.prototype.insertChild):
(WI.TreeOutline.prototype.removeChildAtIndex):
(WI.TreeOutline.prototype._rememberTreeElement):
(WI.TreeOutline.prototype._forgetTreeElement):
(WI.TreeOutline.prototype._treeKeyDown):
(WI.TreeOutline.prototype.selectionControllerNumberOfItems):
(WI.TreeOutline.prototype.selectionControllerSelectionDidChange):
(WI.TreeOutline.prototype.selectionControllerNextSelectableIndex):
(WI.TreeOutline.prototype.selectionControllerPreviousSelectableIndex):
(WI.TreeOutline.prototype.selectTreeElementInternal):
(WI.TreeOutline._generateStyleRulesIfNeeded._indexOfTreeElement.previousElement):
(WI.TreeOutline._generateStyleRulesIfNeeded):

Location:
trunk/Source/WebInspectorUI
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebInspectorUI/ChangeLog

    r238594 r238599  
     12018-11-27  Matt Baker  <mattbaker@apple.com>
     2
     3        Web Inspector: TreeOutline should re-use multiple-selection logic from Table
     4        https://bugs.webkit.org/show_bug.cgi?id=191483
     5        <rdar://problem/45953305>
     6
     7        Reviewed by Devin Rousso.
     8
     9        Update TreeOutline to use SelectionController. Adopting SelectionController
     10        in TreeOutline is not as straightforward as it was in Table. Selected items
     11        are tracked by index, and TreeElement lacks an explicit index. As a consequence
     12        TreeElement indexes are calcualted as needed and cached. The cache is cleared
     13        whenever an element is added or removed.
     14
     15        * UserInterface/Controllers/SelectionController.js:
     16        (WI.SelectionController.prototype.didInsertItem):
     17        (WI.SelectionController.prototype.didRemoveItem):
     18        (WI.SelectionController.prototype.handleKeyDown):
     19        Drive-by syntax error fix.
     20        (WI.SelectionController.prototype._adjustIndexesAfter):
     21        (WI.SelectionController):
     22
     23        * UserInterface/Views/DOMTreeElement.js:
     24        (WI.DOMTreeElement.prototype.canSelectOnMouseDown):
     25        (WI.DOMTreeElement.prototype.selectOnMouseDown): Deleted.
     26
     27        * UserInterface/Views/DOMTreeOutline.js:
     28        (WI.DOMTreeOutline.prototype._onmousedown):
     29        Item selection is now handled by SelectionController.
     30
     31        * UserInterface/Views/ShaderProgramTreeElement.js:
     32        (WI.ShaderProgramTreeElement.prototype.canSelectOnMouseDown):
     33        (WI.ShaderProgramTreeElement.prototype.selectOnMouseDown): Deleted.
     34
     35        * UserInterface/Views/TreeElement.js:
     36        (WI.TreeElement.prototype.canSelectOnMouseDown):
     37        (WI.TreeElement.prototype._attach):
     38        (WI.TreeElement.prototype.select):
     39        (WI.TreeElement.prototype.deselect):
     40        Route item selection through the parent TreeOutline, in order to go though
     41        the TreeOutline's SelectionController.
     42
     43        (WI.TreeElement.treeElementMouseDown): Deleted.
     44        Moved handler to TreeOutline, which owns the SelectionController that
     45        needs to respond to mouse events.
     46
     47        * UserInterface/Views/TreeOutline.js:
     48        (WI.TreeOutline):
     49        (WI.TreeOutline.prototype.get allowsMultipleSelection):
     50        (WI.TreeOutline.prototype.set allowsMultipleSelection):
     51        (WI.TreeOutline.prototype.get selectedTreeElement):
     52        (WI.TreeOutline.prototype.set selectedTreeElement):
     53        (WI.TreeOutline.prototype.insertChild):
     54        (WI.TreeOutline.prototype.removeChildAtIndex):
     55        (WI.TreeOutline.prototype._rememberTreeElement):
     56        (WI.TreeOutline.prototype._forgetTreeElement):
     57        (WI.TreeOutline.prototype._treeKeyDown):
     58        (WI.TreeOutline.prototype.selectionControllerNumberOfItems):
     59        (WI.TreeOutline.prototype.selectionControllerSelectionDidChange):
     60        (WI.TreeOutline.prototype.selectionControllerNextSelectableIndex):
     61        (WI.TreeOutline.prototype.selectionControllerPreviousSelectableIndex):
     62        (WI.TreeOutline.prototype.selectTreeElementInternal):
     63        (WI.TreeOutline._generateStyleRulesIfNeeded._indexOfTreeElement.previousElement):
     64        (WI.TreeOutline._generateStyleRulesIfNeeded):
     65
    1662018-11-27  Nikita Vasilyev  <nvasilyev@apple.com>
    267
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/SelectionController.js

    r238563 r238599  
    193193    }
    194194
     195    didInsertItem(index)
     196    {
     197        this._adjustIndexesAfter(index - 1, 1);
     198    }
     199
    195200    didRemoveItem(index)
    196201    {
     
    198203            this.deselectItem(index);
    199204
    200         while (index = this._selectedIndexes.indexGreaterThan(index)) {
    201             this._selectedIndexes.delete(index);
    202             this._selectedIndexes.add(index - 1);
    203         }
     205        this._adjustIndexesAfter(index, -1);
    204206    }
    205207
     
    209211            return false;
    210212
    211         if (event.key === "a" && event.commandOrControlKey()) {
     213        if (event.key === "a" && event.commandOrControlKey) {
    212214            this.selectAll();
    213215            return true;
     
    373375        this._delegate.selectionControllerSelectionDidChange(this, deselectedItems, selectedItems);
    374376    }
     377
     378    _adjustIndexesAfter(index, delta)
     379    {
     380        while (index = this._selectedIndexes.indexGreaterThan(index)) {
     381            this._selectedIndexes.delete(index);
     382            this._selectedIndexes.add(index + delta);
     383        }
     384    }
    375385};
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js

    r237613 r238599  
    670670    }
    671671
    672     selectOnMouseDown(event)
    673     {
    674         super.selectOnMouseDown(event);
    675 
     672    canSelectOnMouseDown(event)
     673    {
    676674        if (this._editing)
    677             return;
     675            return false;
    678676
    679677        // Prevent selecting the nearest word on double click.
    680678        if (event.detail >= 2)
    681             event.preventDefault();
     679            return false;
     680
     681        return true;
    682682    }
    683683
  • trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js

    r237661 r238599  
    299299            return;
    300300        }
    301 
    302         element.select();
    303301    }
    304302
  • trunk/Source/WebInspectorUI/UserInterface/Views/ShaderProgramTreeElement.js

    r230127 r238599  
    4949    }
    5050
    51     selectOnMouseDown(event)
     51    canSelectOnMouseDown(event)
    5252    {
    53         if (this._statusElement.contains(event.target))
    54             return;
    55 
    56         super.selectOnMouseDown(event);
     53        return !this._statusElement.contains(event.target);
    5754    }
    5855
  • trunk/Source/WebInspectorUI/UserInterface/Views/TreeElement.js

    r238483 r238599  
    188188    }
    189189
     190    canSelectOnMouseDown(event)
     191    {
     192        // Overridden by subclasses if needed.
     193        return true;
     194    }
     195
    190196    _fireDidChange()
    191197    {
     
    240246                this._listItemNode.classList.add("selected");
    241247
    242             this._listItemNode.addEventListener("mousedown", WI.TreeElement.treeElementMouseDown);
    243248            this._listItemNode.addEventListener("click", WI.TreeElement.treeElementToggled);
    244249            this._listItemNode.addEventListener("dblclick", WI.TreeElement.treeElementDoubleClicked);
     
    278283        if (this.treeOutline)
    279284            this.treeOutline.soon.updateVirtualizedElements();
    280     }
    281 
    282     static treeElementMouseDown(event)
    283     {
    284         var element = event.currentTarget;
    285         if (!element || !element.treeElement || !element.treeElement.selectable)
    286             return;
    287 
    288         if (element.treeElement.isEventWithinDisclosureTriangle(event)) {
    289             event.preventDefault();
    290             return;
    291         }
    292 
    293         element.treeElement.selectOnMouseDown(event);
    294285    }
    295286
     
    527518        treeOutline.processingSelectionChange = true;
    528519
    529         // Prevent dispatching a SelectionDidChange event for the deselected element if
    530         // it will be dispatched for the selected element.
    531         if (!suppressOnSelect)
    532             suppressOnDeselect = true;
    533 
    534         let deselectedElement = treeOutline.selectedTreeElement;
    535         if (!this.selected) {
    536             if (treeOutline.selectedTreeElement)
    537                 treeOutline.selectedTreeElement.deselect(suppressOnDeselect);
    538 
    539             this.selected = true;
    540             treeOutline.selectedTreeElement = this;
    541 
    542             if (this._listItemNode)
    543                 this._listItemNode.classList.add("selected");
    544         }
    545 
    546         if (!suppressOnSelect) {
    547             if (this.onselect)
    548                 this.onselect(this, selectedByUser);
    549 
    550             treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange, {selectedByUser});
    551         }
     520        this.selected = true;
     521        treeOutline.selectTreeElementInternal(this, suppressOnSelect, selectedByUser);
     522
     523        if (!suppressOnSelect && this.onselect)
     524            this.onselect(this, selectedByUser);
    552525
    553526        treeOutline.processingSelectionChange = false;
     
    572545
    573546        this.selected = false;
    574         this.treeOutline.selectedTreeElement = null;
    575 
    576         if (this._listItemNode)
    577             this._listItemNode.classList.remove("selected");
    578 
    579         if (!suppressOnDeselect) {
    580             if (this.ondeselect)
    581                 this.ondeselect(this);
    582 
    583             this.treeOutline.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange);
    584         }
     547        this.treeOutline.selectTreeElementInternal(null, suppressOnDeselect);
     548
     549        if (!suppressOnDeselect && this.ondeselect)
     550            this.ondeselect(this);
    585551
    586552        return true;
  • trunk/Source/WebInspectorUI/UserInterface/Views/TreeOutline.js

    r238305 r238599  
    11/*
    2  * Copyright (C) 2007, 2013, 2015 Apple Inc.  All rights reserved.
     2 * Copyright (C) 2007-2018 Apple Inc.  All rights reserved.
    33 *
    44 * Redistribution and use in source and binary forms, with or without
     
    3838
    3939        this.children = [];
    40         this.selectedTreeElement = null;
    4140        this._childrenListNode = this.element;
    4241        this._childrenListNode.removeChildren();
     
    5655        this._selectable = selectable;
    5756
     57        this._cachedNumberOfDescendents = 0;
     58        this._selectionController = new WI.SelectionController(this);
     59        this._treeElementIndexCache = new Map;
     60
     61        this._itemWasSelectedByUser = false;
     62        this._processingSelectionControllerSelectionDidChange = false;
     63        this._suppressNextSelectionDidChangeEvent = false;
     64
    5865        this._virtualizedVisibleTreeElements = null;
    5966        this._virtualizedAttachedTreeElements = null;
     
    6572        this._childrenListNode.tabIndex = 0;
    6673        this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true);
     74        this._childrenListNode.addEventListener("mousedown", this._handleMouseDown.bind(this));
    6775
    6876        WI.TreeOutline._generateStyleRulesIfNeeded();
     
    7381
    7482    // Public
     83
     84    get allowsMultipleSelection()
     85    {
     86        return this._selectionController.allowsMultipleSelection;
     87    }
     88
     89    set allowsMultipleSelection(flag)
     90    {
     91        this._selectionController.allowsMultipleSelection = flag;
     92    }
     93
     94    get selectedTreeElement()
     95    {
     96        let selectedIndex = this._selectionController.lastSelectedItem;
     97        return this._treeElementAtIndex(selectedIndex) || null;
     98    }
     99
     100    set selectedTreeElement(treeElement)
     101    {
     102        let index = this._indexOfTreeElement(treeElement);
     103        this._selectionController.selectItem(index);
     104    }
    75105
    76106    get hidden()
     
    243273        if (isFirstChild && this.expanded)
    244274            this.expand();
     275
     276        let insertionIndex = this.treeOutline._indexOfTreeElement(child.previousSibling) || 0;
     277        this.treeOutline._selectionController.didInsertItem(insertionIndex);
    245278    }
    246279
     
    273306            treeOutline._forgetTreeElement(child);
    274307            treeOutline._forgetChildrenRecursive(child);
     308            treeOutline._selectionController.didRemoveItem(childIndex);
    275309        }
    276310
     
    379413    _rememberTreeElement(element)
    380414    {
     415        this._treeElementIndexCache.clear();
     416        this._cachedNumberOfDescendents++;
     417
    381418        if (!this._knownTreeElements[element.identifier])
    382419            this._knownTreeElements[element.identifier] = [];
     
    393430    _forgetTreeElement(element)
    394431    {
     432        this._treeElementIndexCache.clear();
     433        this._cachedNumberOfDescendents--;
     434
    395435        if (this.selectedTreeElement === element) {
    396436            element.deselect(true);
     
    524564            return;
    525565
    526         if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey)
     566        if (!this.selectedTreeElement || event.commandOrControlKey)
    527567            return;
    528568
     
    531571        var handled = false;
    532572        var nextSelectedElement;
    533         if (event.keyIdentifier === "Up" && !event.altKey) {
    534             nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true);
    535             while (nextSelectedElement && !nextSelectedElement.selectable)
    536                 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(true);
    537             handled = nextSelectedElement ? true : false;
    538         } else if (event.keyIdentifier === "Down" && !event.altKey) {
    539             nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true);
    540             while (nextSelectedElement && !nextSelectedElement.selectable)
    541                 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(true);
    542             handled = nextSelectedElement ? true : false;
    543         } else if ((!isRTL && event.keyIdentifier === "Left") || (isRTL && event.keyIdentifier === "Right")) {
     573
     574        if ((!isRTL && event.keyIdentifier === "Left") || (isRTL && event.keyIdentifier === "Right")) {
    544575            if (this.selectedTreeElement.expanded) {
    545576                if (event.altKey)
     
    564595            } else if (this.selectedTreeElement.hasChildren) {
    565596                handled = true;
    566                 if (!this.selectedTreeElement.expanded) {
     597                if (this.selectedTreeElement.expanded) {
     598                    nextSelectedElement = this.selectedTreeElement.children[0];
     599                    while (nextSelectedElement && !nextSelectedElement.selectable)
     600                        nextSelectedElement = nextSelectedElement.nextSibling;
     601                    handled = nextSelectedElement ? true : false;
     602                } else {
    567603                    if (event.altKey)
    568604                        this.selectedTreeElement.expandRecursively();
     
    588624        }
    589625
     626        if (!handled)
     627            handled = this._selectionController.handleKeyDown(event);
     628
    590629        if (nextSelectedElement) {
    591630            nextSelectedElement.reveal();
     
    758797    }
    759798
     799    // SelectionController delegate
     800
     801    selectionControllerNumberOfItems(controller)
     802    {
     803        return this._cachedNumberOfDescendents;
     804    }
     805
     806    selectionControllerSelectionDidChange(controller, deselectedItems, selectedItems)
     807    {
     808        this._processingSelectionControllerSelectionDidChange = true;
     809
     810        for (let index of deselectedItems) {
     811            let treeElement = this._treeElementAtIndex(index);
     812            console.assert(treeElement, "Missing TreeElement for deselected index " + index);
     813            if (treeElement) {
     814                treeElement.listItemElement.classList.remove("selected");
     815                if (!this._suppressNextSelectionDidChangeEvent)
     816                    treeElement.deselect();
     817            }
     818        }
     819
     820        for (let index of selectedItems) {
     821            let treeElement = this._treeElementAtIndex(index);
     822            console.assert(treeElement, "Missing TreeElement for selected index " + index);
     823            if (treeElement) {
     824                treeElement.listItemElement.classList.add("selected");
     825                if (!this._suppressNextSelectionDidChangeEvent)
     826                    treeElement.select();
     827            }
     828        }
     829
     830        this._processingSelectionControllerSelectionDidChange = false;
     831
     832        this._dispatchSelectionDidChangeEvent();
     833    }
     834
     835    selectionControllerNextSelectableIndex(controller, index)
     836    {
     837        let treeElement = this._treeElementAtIndex(index);
     838        if (!treeElement)
     839            return NaN;
     840
     841        const skipUnrevealed = true;
     842        const stayWithin = null;
     843        const dontPopulate = true;
     844
     845        while (treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
     846            if (treeElement.selectable)
     847                return this._indexOfTreeElement(treeElement);
     848        }
     849
     850        return NaN;
     851    }
     852
     853    selectionControllerPreviousSelectableIndex(controller, index)
     854    {
     855        let treeElement = this._treeElementAtIndex(index);
     856        if (!treeElement)
     857            return NaN;
     858
     859        const skipUnrevealed = true;
     860        const stayWithin = null;
     861        const dontPopulate = true;
     862
     863        while (treeElement = treeElement.traversePreviousTreeElement(skipUnrevealed, stayWithin, dontPopulate)) {
     864            if (treeElement.selectable)
     865                return this._indexOfTreeElement(treeElement);
     866        }
     867
     868        return NaN;
     869    }
     870
    760871    // Protected
     872
     873    selectTreeElementInternal(treeElement, suppressNotification = false, selectedByUser = false)
     874    {
     875        if (this._processingSelectionControllerSelectionDidChange)
     876            return;
     877
     878        this._itemWasSelectedByUser = selectedByUser;
     879        this._suppressNextSelectionDidChangeEvent = suppressNotification;
     880
     881        if (this.allowsRepeatSelection && this.selectedTreeElement === treeElement) {
     882            this._dispatchSelectionDidChangeEvent();
     883            return;
     884        }
     885
     886        this.selectedTreeElement = treeElement;
     887    }
    761888
    762889    treeElementFromEvent(event)
     
    856983        this.populateContextMenu(contextMenu, event, treeElement);
    857984    }
     985
     986    _handleMouseDown(event)
     987    {
     988        let treeElement = this.treeElementFromEvent(event);
     989        if (!treeElement || !treeElement.selectable)
     990            return;
     991
     992        if (treeElement.isEventWithinDisclosureTriangle(event)) {
     993            event.preventDefault();
     994            return;
     995        }
     996
     997        if (!treeElement.canSelectOnMouseDown(event)) {
     998            event.preventDefault();
     999            return;
     1000        }
     1001
     1002        let index = this._indexOfTreeElement(treeElement);
     1003        if (isNaN(index))
     1004            return;
     1005
     1006        this._selectionController.handleItemMouseDown(index, event);
     1007    }
     1008
     1009    _indexOfTreeElement(treeElement)
     1010    {
     1011        function previousElement(element) {
     1012            if (element.previousSibling) {
     1013                element = element.previousSibling;
     1014                if (element.children.length)
     1015                    element = element.children.lastValue;
     1016            } else
     1017                element = element.parent && element.parent.root ? null : element.parent;
     1018            return element;
     1019        }
     1020
     1021        let index = 0;
     1022        let current = treeElement;
     1023        while (current) {
     1024            let closestIndex = this._treeElementIndexCache.get(current);
     1025            if (!isNaN(closestIndex)) {
     1026                index += closestIndex;
     1027                break;
     1028            }
     1029
     1030            current = previousElement(current);
     1031            if (current)
     1032                index++;
     1033        }
     1034
     1035        if (!this._treeElementIndexCache.has(treeElement))
     1036            this._treeElementIndexCache.set(treeElement, index);
     1037
     1038        return index;
     1039    }
     1040
     1041    _treeElementAtIndex(index)
     1042    {
     1043        const skipUnrevealed = false;
     1044        const stayWithin = null;
     1045        const dontPopulate = true;
     1046
     1047        let current = 0;
     1048        let treeElement = this.children[0];
     1049        while (treeElement) {
     1050            if (current === index)
     1051                return treeElement;
     1052
     1053            treeElement = treeElement.traverseNextTreeElement(skipUnrevealed, stayWithin, dontPopulate);
     1054            ++current;
     1055        }
     1056
     1057        return null;
     1058    }
     1059
     1060    _dispatchSelectionDidChangeEvent()
     1061    {
     1062        let selectedByUser = this._itemWasSelectedByUser;
     1063        this._itemWasSelectedByUser = false;
     1064
     1065        if (this._suppressNextSelectionDidChangeEvent) {
     1066            this._suppressNextSelectionDidChangeEvent = false;
     1067            return;
     1068        }
     1069
     1070        this.dispatchEventToListeners(WI.TreeOutline.Event.SelectionDidChange, {selectedByUser});
     1071    }
    8581072};
    8591073
Note: See TracChangeset for help on using the changeset viewer.