/* * Copyright 2003-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.nodeEditor.selection; import jetbrains.mps.editor.runtime.style.StyleAttributesUtil; import jetbrains.mps.nodeEditor.EditorComponent; import jetbrains.mps.nodeEditor.FocusPolicyUtil; import jetbrains.mps.nodeEditor.cells.EditorCell_Error; import jetbrains.mps.openapi.editor.cells.CellTraversalUtil; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.cells.EditorCell_Collection; import jetbrains.mps.openapi.editor.cells.EditorCell_Label; import jetbrains.mps.openapi.editor.selection.Selection; import jetbrains.mps.openapi.editor.selection.SelectionInfo; import jetbrains.mps.openapi.editor.selection.SelectionListener; import jetbrains.mps.openapi.editor.selection.SelectionManager; import jetbrains.mps.openapi.editor.selection.SelectionStoreException; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SNode; import java.util.ArrayList; import java.util.Arrays; import java.util.Deque; import java.util.LinkedList; import java.util.List; public class SelectionManagerImpl implements SelectionManager { private static final Logger LOG = LogManager.getLogger(SelectionManagerImpl.class); @NotNull private EditorComponent myEditorComponent; private Deque<Selection> mySelectionStack = new LinkedList<Selection>(); private List<SelectionListener> mySelectionListeners = new LinkedList<SelectionListener>(); public SelectionManagerImpl(@NotNull EditorComponent editorComponent) { myEditorComponent = editorComponent; } public void clearSelection() { if (mySelectionStack.isEmpty()) { return; } Selection oldSelection = mySelectionStack.getLast(); mySelectionStack.clear(); doChangeSelection(oldSelection, null); } public Selection getSelection() { return mySelectionStack.isEmpty() ? null : mySelectionStack.getLast(); } public Selection getDeepestSelection() { return mySelectionStack.isEmpty() ? null : mySelectionStack.getFirst(); } @Override public Selection createSelection(EditorCell editorCell) { Selection selection; if (editorCell instanceof EditorCell_Label) { selection = new EditorCellLabelSelection((EditorCell_Label) editorCell); } else if (editorCell != null) { selection = new EditorCellSelection(editorCell); } else { selection = null; } return selection; } @Override public Selection createRangeSelection(SNode firstNode, SNode lastNode) { return new NodeRangeSelection(myEditorComponent, firstNode, lastNode); } @Override public void setSelection(EditorCell editorCell) { setSelection(createSelection(editorCell)); } @Override public void setSelection(EditorCell_Label label, int caretPosition) { label.setCaretPosition(caretPosition); setSelection(new EditorCellLabelSelection(label)); } @Override public void setSelection(EditorCell_Label label, int caretPosition, int selectionStart, int selectionEnd) { label.setCaretPosition(caretPosition); label.setSelectionStart(selectionStart); label.setSelectionEnd(selectionEnd); setSelection(new EditorCellLabelSelection(label)); } @Override public void setSelection(Selection selection) { Selection oldSelection = getSelection(); if (!mySelectionStack.isEmpty()) { mySelectionStack.clear(); } if (selection != null) { mySelectionStack.addLast(selection); } doChangeSelection(oldSelection, selection); } @Override public void pushSelection(@NotNull Selection selection) { Selection oldSelection = getSelection(); mySelectionStack.addLast(selection); doChangeSelection(oldSelection, selection); } @Override public Selection popSelection() { if (mySelectionStack.isEmpty()) { return null; } Selection oldSelection = mySelectionStack.removeLast(); doChangeSelection(oldSelection, getSelection()); return oldSelection; } @Override public Iterable<Selection> getSelectionStackIterable() { return new ArrayList<Selection>(mySelectionStack); } @Override public List<SelectionInfo> getSelectionInfoStack() { List<SelectionInfo> result = new ArrayList<SelectionInfo>(); try { for (Selection nextSelection : mySelectionStack) { result.add(nextSelection.getSelectionInfo()); } } catch (SelectionStoreException e) { LOG.error(null, e); // unable to store selection - cleaning selection stack result.clear(); } return result; } @Override public void setSelectionInfoStack(@NotNull List<SelectionInfo> selectionStack) { List<Selection> newSelectionStack = new ArrayList<Selection>(); for (SelectionInfo nextSelectionInfo : selectionStack) { Selection selection = nextSelectionInfo.createSelection(myEditorComponent); if (selection != null) { // restoring only those selection stack elements available in the current editor, skipping those are not available now newSelectionStack.add(selection); } } if (isSameSelectionStack(newSelectionStack)) { return; } Selection oldSelection = getSelection(); mySelectionStack.clear(); mySelectionStack.addAll(newSelectionStack); doChangeSelection(oldSelection, getSelection()); } private boolean isSameSelectionStack(List<Selection> newSelectionStack) { if (newSelectionStack.size() != mySelectionStack.size()) { return false; } int index = 0; for (Selection oldSelection : mySelectionStack) { Selection newSelection = newSelectionStack.get(index++); if (!oldSelection.isSame(newSelection)) { return false; } } return true; } @Override public int getSelectionStackSize() { return mySelectionStack.size(); } @Override public void addSelectionListener(@NotNull SelectionListener listener) { assert !mySelectionListeners.contains(listener); mySelectionListeners.add(listener); } @Override public void removeSelectionListener(@NotNull SelectionListener listener) { mySelectionListeners.remove(listener); } private void doChangeSelection(Selection oldSelection, Selection newSelection) { boolean isSameSelection = oldSelection == newSelection; if (oldSelection != null && !isSameSelection) { oldSelection.deactivate(); } if (newSelection != null && !isSameSelection) { newSelection.activate(); } for (SelectionListener nextListener : mySelectionListeners) { try { nextListener.selectionChanged(myEditorComponent, oldSelection, newSelection); } catch (Exception e) { LOG.error(null, e); } } } public void dispose() { if (!mySelectionStack.isEmpty()) { mySelectionStack.getLast().deactivate(); } mySelectionStack.clear(); } @Override public void setSelection(SNode node) { setSelection(myEditorComponent.findNodeCell(node)); } @Override public void setSelection(SNode node, @NotNull String cellId) { EditorCell cell = findCell(node, cellId); if (cell == null) { clearSelection(); } else { setSelection(cell); } } @Override public void setSelection(SNode node, @NotNull String cellId, int caretPosition) { EditorCell cell = findCell(node, cellId); if (cell instanceof EditorCell_Collection && (caretPosition == 0 || caretPosition == -1)) { cell = findChildCell(cell, caretPosition == 0 ? SelectionManager.FIRST_CELL : SelectionManager.LAST_CELL); } if (cell instanceof EditorCell_Label) { EditorCell_Label labelCell = (EditorCell_Label) cell; boolean isFirstPositionRequested = caretPosition == 0; boolean isLastPositionRequested = caretPosition == -1 || (caretPosition != 0 && caretPosition == labelCell.getText().length()); EditorCell_Label refinedCell = refineUsingCursorPositioningRules(labelCell, cellId, isFirstPositionRequested, isLastPositionRequested); if (refinedCell != null) { if (refinedCell != labelCell) { // first -> last & last -> first if prev. or next cell was selected if (isFirstPositionRequested) { isLastPositionRequested = true; } else if (isLastPositionRequested) { isLastPositionRequested = false; caretPosition = 0; } } if (isLastPositionRequested) { caretPosition = refinedCell.getText().length(); } setSelection(refinedCell, caretPosition); return; } } clearSelection(); } private EditorCell_Label refineUsingCursorPositioningRules(EditorCell_Label labelCell, String cellId, boolean isFirstPositionRequested, boolean isLastPositionRequested) { if (isFirstPositionRequested && !StyleAttributesUtil.isFirstPositionAllowed(labelCell.getStyle())) { return getNextApplicableCell(labelCell, false, SelectionManager.FIRST_EDITABLE_CELL.equals(cellId)); } if (isLastPositionRequested && !StyleAttributesUtil.isLastPositionAllowed(labelCell.getStyle())) { return getNextApplicableCell(labelCell, true, SelectionManager.LAST_EDITABLE_CELL.equals(cellId)); } return labelCell; } private EditorCell_Label getNextApplicableCell(EditorCell_Label startCell, boolean forwardDirection, boolean findEditableCell) { for (EditorCell nextCell : CellTraversalUtil.iterateTree(null, startCell, forwardDirection).skipStart()) { if (nextCell instanceof EditorCell_Label) { EditorCell_Label labelCell = (EditorCell_Label) nextCell; if (findEditableCell ? labelCell.isSelectable() && labelCell.isEditable() : labelCell.isSelectable()) { return labelCell; } } } return null; } @Override public void setSelection(SNode node, @NotNull String cellId, int selectionStart, int selectionEnd) { EditorCell cell = findCell(node, cellId); if (cell instanceof EditorCell_Label) { EditorCell_Label label = (EditorCell_Label) cell; if (selectionStart == -1) { selectionStart = label.getText().length(); } if (selectionEnd == -1) { selectionEnd = label.getText().length(); } setSelection(label, selectionEnd, selectionStart, selectionEnd); } else { clearSelection(); } } /** * looking for the cell with specified id for the specified node taking into account * cell selector constants: SelectionManager.FIRST_CELL/SelectionManager.LAST_CELL/.. */ private EditorCell findCell(SNode node, String cellId) { EditorCell nodeCell = myEditorComponent.findNodeCell(node); if (nodeCell == null) { return null; } if (FOCUS_POLICY_CELL.equals(cellId)) { return FocusPolicyUtil.findFocusedCell(nodeCell); } if (isSpecifiedById(nodeCell, cellId)) { return nodeCell; } return findChildCell(nodeCell, cellId); } private EditorCell findChildCell(EditorCell nodeCell, String cellId) { boolean useForwardIterator = shouldUseForwardIterator(cellId); boolean ignoreChildNodes = shouldIgnoreChildNodes(cellId); for (EditorCell cell : CellTraversalUtil.iterateTree(nodeCell, nodeCell, useForwardIterator).skipStart()) { if (ignoreChildNodes && cell.getSNode() != nodeCell.getSNode()) { continue; } if (isSpecifiedById(cell, cellId)) { return cell; } } return null; } private boolean shouldUseForwardIterator(String cellId) { String[] selectorsShouldUseBackwardIterator = new String[]{LAST_CELL, LAST_EDITABLE_CELL,LAST_ERROR_CELL}; return !Arrays.stream(selectorsShouldUseBackwardIterator).anyMatch(s -> s.equals(cellId)); } private boolean shouldIgnoreChildNodes(String cellId) { String[] selectorsShouldNotIgnoreChildNodes = new String[]{FIRST_CELL, LAST_CELL, FIRST_EDITABLE_CELL, LAST_EDITABLE_CELL, FIRST_ERROR_CELL, LAST_ERROR_CELL}; return !Arrays.stream(selectorsShouldNotIgnoreChildNodes).anyMatch(s -> s.equals(cellId)); } private boolean isSpecifiedById(EditorCell cell, String requestedCellId) { String thisCellId = cell.getCellId(); // supporting this hidden notation for selecting property cells. // Now property cellIDs are prefixed with editor component name. if (requestedCellId.startsWith("*")) { if (thisCellId != null && thisCellId.contains(requestedCellId.substring(1))) { return true; } } if (requestedCellId.equals(thisCellId)) { return true; } // processing cell selector constants boolean selectable = SelectionManager.FIRST_CELL.equals(requestedCellId) || SelectionManager.LAST_CELL.equals(requestedCellId); boolean editable = SelectionManager.FIRST_EDITABLE_CELL.equals(requestedCellId) || SelectionManager.LAST_EDITABLE_CELL.equals(requestedCellId); boolean error = SelectionManager.FIRST_ERROR_CELL.equals(requestedCellId) || SelectionManager.LAST_ERROR_CELL.equals(requestedCellId); if (cell instanceof EditorCell_Collection) { return false; } if (selectable) { return cell.isSelectable(); } else if (editable) { return cell.isSelectable() && cell instanceof EditorCell_Label && ((EditorCell_Label) cell).isEditable(); } else if (error) { return cell instanceof EditorCell_Error || cell.isErrorState(); } return false; } }