/*
* 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);
}
}