/********************************************************************************
 * Copyright (C) 2018 TypeFox and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import { injectable , inject , postConstruct } from 'inversify' ; import { Tree , TreeNode } from './tree' ; import { Event , Emitter } from '../../common' ; import { TreeSelectionState , FocusableTreeSelection } from './tree-selection-state' ; import { TreeSelectionService , SelectableTreeNode , TreeSelection } from './tree-selection' ; @ injectable ( ) export class TreeSelectionServiceImpl implements TreeSelectionService { @ inject ( Tree ) protected readonly tree : Tree ; protected readonly onSelectionChangedEmitter = new Emitter < ReadonlyArray < Readonly < SelectableTreeNode > > > ( ) ; protected state : TreeSelectionState ; @ postConstruct ( ) protected init ( ) { this . state = new TreeSelectionState ( this . tree ) ; } dispose ( ) { this . onSelectionChangedEmitter . dispose ( ) ; } get selectedNodes ( ) : ReadonlyArray < Readonly < SelectableTreeNode > > { return this . state . selection ( ) ; } get onSelectionChanged ( ) : Event < ReadonlyArray < Readonly < SelectableTreeNode > > > { return this . onSelectionChangedEmitter . event ; } protected fireSelectionChanged ( ) { this . onSelectionChangedEmitter . fire ( this . state . selection ( ) ) ; } addSelection ( selectionOrTreeNode ) { const selection = ( ( arg ) : TreeSelection => { const type = TreeSelection . SelectionType . DEFAULT ; if ( TreeSelection . is ( arg ) ) { return { type , ... arg } ; } return { type , node : arg } ; } ) ( selectionOrTreeNode ) ; const node = this . validateNode ( selection . node ) ; if ( node === undefined ) { return ; } Object . assign ( selection , { node } ) ; const newState = this . state . nextState ( selection ) ; this . transiteTo ( newState ) ; } protected transiteTo ( newState ) { const oldNodes = this . state . selection ( ) ; const newNodes = newState . selection ( ) ; const toUnselect = this . difference ( oldNodes , newNodes ) ; const toSelect = this . difference ( newNodes , oldNodes ) ; this . unselect ( toUnselect ) ; this . select ( toSelect ) ; this . removeFocus ( oldNodes , newNodes ) ; this . addFocus ( newState . focus ) ; this . state = newState ; this . fireSelectionChanged ( ) ; } protected unselect ( nodes ) { nodes . forEach ( node => node . selected = false ) ; } protected select ( nodes ) { nodes . forEach ( node => node . selected = true ) ; } protected removeFocus ( ... nodes ) { nodes . forEach ( node => node . forEach ( n => n . focus = false ) ) ; } protected addFocus ( node ) { if ( node ) { node . focus = true ; } } /**
     * Returns an array of the difference of two arrays. The returned array contains all elements that are contained by
     * `left` and not contained by `right`. `right` may also contain elements not present in `left`: these are simply ignored.
     */ protected difference < T > ( left , right ) { return left . filter ( item => right . indexOf ( item ) === - 1 ) ; } /**
     * Returns a reference to the argument if the node exists in the tree. Otherwise, `undefined`.
     */ protected validateNode ( node ) { const result = this . tree . validateNode ( node ) ; return SelectableTreeNode . is ( result ) ? result : undefined ; } storeState ( ) { return { selectionStack : this . state . selectionStack . map ( s => ( { focus : s . focus && s . focus . id || undefined , node : s . node && s . node . id || undefined , type : s . type } ) ) } ; } restoreState ( state ) { const selectionStack = [ ] ; for ( const selection of state . selectionStack ) { const node = selection . node && this . tree . getNode ( selection . node ) || undefined ; if ( ! SelectableTreeNode . is ( node ) ) { break ; } const focus = selection . focus && this . tree . getNode ( selection . focus ) || undefined ; selectionStack . push ( { node , focus : SelectableTreeNode . is ( focus ) && focus || undefined , type : selection . type } ) ; } if ( selectionStack . length ) { this . transiteTo ( new TreeSelectionState ( this . tree , selectionStack ) ) ; } } } export namespace TreeSelectionServiceImpl { export interface State { selectionStack : ReadonlyArray < FocusableTreeSelectionState > ; } export interface FocusableTreeSelectionState { focus ? : string ; node ? : string ; type ? : TreeSelection . SelectionType ; } }