/* * 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.cells.ReadOnlyUtil; import jetbrains.mps.nodeEditor.cells.CellFinderUtil; import jetbrains.mps.nodeEditor.cells.EditorCell_Constant; import jetbrains.mps.nodeEditor.cells.EditorCell_Property; import jetbrains.mps.nodeEditor.cells.GeometryUtil; import jetbrains.mps.openapi.editor.EditorComponent; import jetbrains.mps.openapi.editor.cells.CellActionType; import jetbrains.mps.openapi.editor.cells.CellInfo; import jetbrains.mps.openapi.editor.cells.CellTraversalUtil; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.cells.EditorCell_Label; import jetbrains.mps.openapi.editor.selection.Selection; import jetbrains.mps.openapi.editor.selection.SelectionStoreException; import jetbrains.mps.smodel.ModelAccess; import jetbrains.mps.util.Computable; import org.jetbrains.annotations.NotNull; import java.util.Map; public class EditorCellLabelSelection extends EditorCellSelection { private static final String HAS_NON_TRIVIAL_SELECTION_PROPERTY_NAME = "hasNonTrivialSelection"; private static final String SELECTION_START_PROPERTY_NAME = "selectionStart"; private static final String SELECTION_END_PROPERTY_NAME = "selectionEnd"; private int mySelectionStart = -1; private int mySelectionEnd = -1; private boolean myNonTrivialSelection = false; public EditorCellLabelSelection(EditorComponent editorComponent, Map<String, String> properties, CellInfo cellInfo) throws SelectionStoreException, SelectionRestoreException { super(editorComponent, properties, cellInfo); if (!(getEditorCell() instanceof EditorCell_Label)) { throw new SelectionRestoreException(); } myNonTrivialSelection = SelectionInfoImpl.Util.getBooleanProperty(properties, HAS_NON_TRIVIAL_SELECTION_PROPERTY_NAME); if (getEditorCell().getCellInfo().equals(cellInfo)) { if (myNonTrivialSelection) { /* This is kind of hack for EditorManager.STHintCellInfo - if located cell is different from the original one then we do not restore selection. */ mySelectionStart = SelectionInfoImpl.Util.getIntProperty(properties, SELECTION_START_PROPERTY_NAME); mySelectionEnd = SelectionInfoImpl.Util.getIntProperty(properties, SELECTION_END_PROPERTY_NAME); } } else { myNonTrivialSelection = false; } } public EditorCellLabelSelection(EditorCell_Label editorCell) { super(editorCell); mySelectionStart = editorCell.getSelectionStart(); mySelectionEnd = editorCell.getSelectionEnd(); myNonTrivialSelection = mySelectionStart != mySelectionEnd; } @NotNull public EditorCell_Label getEditorCellLabel() { return (EditorCell_Label) getEditorCell(); } public int getSelectionStart() { return isActive() ? getEditorCellLabel().getSelectionStart() : mySelectionStart; } public int getSelectionEnd() { return isActive() ? getEditorCellLabel().getSelectionEnd() : mySelectionEnd; } public boolean hasNonTrivialSelection() { return isActive() ? getSelectionStart() != getSelectionEnd() : myNonTrivialSelection; } @Override public void activate() { super.activate(); if (myNonTrivialSelection) { getEditorCellLabel().setSelectionStart(mySelectionStart); getEditorCellLabel().setSelectionEnd(mySelectionEnd); } } @Override public void deactivate() { super.deactivate(); mySelectionStart = getEditorCellLabel().getSelectionStart(); mySelectionEnd = getEditorCellLabel().getSelectionEnd(); myNonTrivialSelection = mySelectionStart != mySelectionEnd; getEditorCellLabel().deselectAll(); } @Override public SelectionInfoImpl getSelectionInfo() throws SelectionStoreException { SelectionInfoImpl selectionInfo = super.getSelectionInfo(); selectionInfo.getPropertiesMap().put(HAS_NON_TRIVIAL_SELECTION_PROPERTY_NAME, Boolean.toString(hasNonTrivialSelection())); if (hasNonTrivialSelection()) { selectionInfo.getPropertiesMap().put(SELECTION_START_PROPERTY_NAME, Integer.toString(getSelectionStart())); selectionInfo.getPropertiesMap().put(SELECTION_END_PROPERTY_NAME, Integer.toString(getSelectionEnd())); } return selectionInfo; } @Override public boolean isSame(Selection another) { if (this == another) { return true; } if (another == null || getClass() != another.getClass()) { return false; } EditorCellLabelSelection that = (EditorCellLabelSelection) another; if (!getEditorCell().equals(that.getEditorCell())) { return false; } if (getCaretX() != that.getCaretX()) { return false; } if (hasNonTrivialSelection() != that.hasNonTrivialSelection()) { return false; } if (hasNonTrivialSelection()) { if (getSelectionEnd() != that.getSelectionEnd()) { return false; } if (getSelectionStart() != that.getSelectionStart()) { return false; } } return true; } @Override public boolean canExecuteAction(CellActionType type) { if (type == CellActionType.DELETE || type == CellActionType.BACKSPACE) { return true; } if (type == CellActionType.DELETE_TO_WORD_END) { type = CellActionType.DELETE; } return super.canExecuteAction(type); } @Override public void executeAction(CellActionType type) { ((jetbrains.mps.nodeEditor.EditorComponent) getEditorComponent()).assertModelNotDisposed(); if (type == CellActionType.DELETE || type == CellActionType.BACKSPACE) { performDeleteAction(type); return; } if (type == CellActionType.DELETE_TO_WORD_END) { super.executeAction(CellActionType.DELETE); return; } super.executeAction(type); } @Override protected boolean suppressDelete(CellActionType type) { if (!super.suppressDelete(type)) { return false; } EditorCell_Label label = getEditorCellLabel(); if (label.getText().length() == 0) { return false; } if (label instanceof EditorCell_Constant || label instanceof EditorCell_Property) { return label.isEditable() || CellFinderUtil.findLastSelectableLeaf(CellTraversalUtil.getContainingBigCell(label)) != label; } return true; } private void performDeleteAction(CellActionType type) { if (getEditorCellLabel().executeTextAction(type, false)) { return; } if (processSideDeletes(type)) { return; } if (getEditorCellLabel().executeTextAction(type, true)) { return; } super.executeAction(type); } private boolean processSideDeletes(CellActionType type) { // TODO: review this logic - it was originally copied from EditorComponentKeyboardHandler final EditorCell selectedCell = getEditorCell(); if (type == CellActionType.DELETE && !hasNonTrivialSelection() && GeometryUtil.isLastPositionInBigCell(selectedCell) && !GeometryUtil.isFirstPositionInBigCell(selectedCell)) { final EditorCell target; EditorCell bigCellNextSibling = CellTraversalUtil.getNextSibling(CellTraversalUtil.getContainingBigCell(selectedCell)); if (bigCellNextSibling != null) { target = bigCellNextSibling; } else { EditorCell nextSibling = CellTraversalUtil.getNextSibling(CellTraversalUtil.getContainingBigCell(selectedCell)); if (nextSibling != null) { target = nextSibling; } else { target = CellTraversalUtil.getNextLeaf(selectedCell, jetbrains.mps.openapi.editor.cells.CellConditions.SELECTABLE); } } if (target == null || ModelAccess.instance().runReadAction(new Computable<Boolean>() { @Override public Boolean compute() { return jetbrains.mps.util.SNodeOperations.isAncestor(target.getSNode(), selectedCell.getSNode()); } })) { return false; } return getEditorComponent().getActionHandler().executeAction(target, type); } if (type == CellActionType.BACKSPACE && !hasNonTrivialSelection() && GeometryUtil.isFirstPositionInBigCell(selectedCell) && !GeometryUtil.isLastPositionInBigCell(selectedCell)) { final EditorCell target; EditorCell bigCellPrevSibling = CellTraversalUtil.getPrevSibling(CellTraversalUtil.getContainingBigCell(selectedCell)); if (bigCellPrevSibling != null) { target = bigCellPrevSibling; } else { EditorCell prevSibling = CellTraversalUtil.getPrevSibling(selectedCell); if (prevSibling != null) { target = prevSibling; } else { target = CellTraversalUtil.getPrevLeaf(selectedCell, jetbrains.mps.openapi.editor.cells.CellConditions.SELECTABLE); } } if (target == null || ReadOnlyUtil.isCellReadOnly(target)) { return false; } /* Was commented out (again) to let some of our unit-tests be green. in particular - pressing BackSpace at this situation: <code> int a = 1; --|a; <code> where "|" is a position of cursor; if (ModelAccess.instance().runReadAction(new Computable<Boolean>() { public Boolean compute() { return jetbrains.mps.util.SNodeOperations.isAncestor(target.getSNode(), selectedCell.getSNode()); } })) return false; */ return getEditorComponent().getActionHandler().executeAction(target, type); } return false; } @Override public String toString() { return String.format("EditorCellLabelSelection{cell=%s, start=%d, end=%d}", getEditorCell(), mySelectionStart, mySelectionEnd); } }