/* * 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.cellMenu; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.components.JBList; import com.intellij.util.ui.AbstractLayoutManager; import com.intellij.util.ui.UIUtil; import jetbrains.mps.RuntimeFlags; import jetbrains.mps.editor.runtime.commands.EditorCommand; import jetbrains.mps.nodeEditor.EditorComponent; import jetbrains.mps.nodeEditor.EditorContext; import jetbrains.mps.nodeEditor.EditorSettings; import jetbrains.mps.nodeEditor.IntelligentInputUtil; import jetbrains.mps.nodeEditor.KeyboardHandler; import jetbrains.mps.nodeEditor.SubstituteActionComparator; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.cells.SubstituteAction; import jetbrains.mps.openapi.editor.cells.SubstituteInfo; import jetbrains.mps.smodel.ModelAccessHelper; import jetbrains.mps.smodel.action.AbstractNodeSubstituteAction; import jetbrains.mps.typesystem.inference.ITypeContextOwner; import jetbrains.mps.typesystem.inference.NonReusableTypecheckingContextOwner; import jetbrains.mps.typesystem.inference.TypeContextManager; import jetbrains.mps.util.WindowsUtil; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.module.ModelAccess; import javax.swing.DefaultListModel; import javax.swing.JList; import javax.swing.JScrollPane; import javax.swing.JWindow; import javax.swing.ListModel; import javax.swing.ListSelectionModel; import javax.swing.event.ListDataListener; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.InputMethodEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Author: Sergey Dmitriev. * Created Sep 16, 2003 */ public class NodeSubstituteChooser implements KeyboardHandler { private static final Logger LOG = LogManager.getLogger(NodeSubstituteChooser.class); static final int MAX_LOOKUP_LIST_HEIGHT = 11; private PopupWindow myPopupWindow = null; private boolean myChooserActivated = false; private boolean myPopupActivated; private EditorCell myContextCell; private boolean myIsSmart = false; private EditorComponent myEditorComponent; private NodeSubstitutePatternEditor myPatternEditor; private SubstituteInfo myNodeSubstituteInfo; private List<SubstituteAction> mySubstituteActions = new ArrayList<>(); private boolean myMenuEmpty; private NodeItemCellRenderer myCellRenderer; private boolean myUserChoseItem; public NodeSubstituteChooser(EditorComponent editorComponent) { myEditorComponent = editorComponent; } public Window getWindow() { return myPopupWindow; } PopupWindow getPopupWindow() { if (myPopupWindow == null) { myPopupWindow = new PopupWindow(getEditorWindow()); } return myPopupWindow; } private Window getEditorWindow() { Component component = myEditorComponent; while (!(component instanceof Window) && component != null) { component = component.getParent(); } return (Window) component; } /** * Changes the location of the chooser accordingly to the location of the context cell * If containing component is not showings does nothing. * * @throws java.lang.IllegalStateException if the chooser is not visible */ public void moveToContextCell() { if (!isVisible()) { throw (new IllegalStateException("NodeSubstituteChooser must be visible to change its location")); } Point location = calcPatternEditorLocation(); if (location == null) { return; } getPatternEditor().setLocation(location); getPopupWindow().moveToPatternEditor(); } private Dimension calcPatternEditorDimension() { return new Dimension( myContextCell.getWidth() - myContextCell.getLeftInset() - myContextCell.getRightInset() + 1, myContextCell.getHeight() - myContextCell.getTopInset() - myContextCell.getBottomInset() + 1); } @Nullable public Point calcPatternEditorLocation() { if (!myEditorComponent.isShowing()) { return null; } Point anchor = myEditorComponent.getLocationOnScreen(); return new Point(anchor.x + myContextCell.getX() + myContextCell.getLeftInset(), anchor.y + myContextCell.getY() + myContextCell.getTopInset()); } @Deprecated public void setLocationRelative(@NotNull EditorCell cell) { myContextCell = cell; } public void setNodeSubstituteInfo(@NotNull SubstituteInfo nodeSubstituteInfo) { assert !myChooserActivated; myNodeSubstituteInfo = nodeSubstituteInfo; myCellRenderer = null; myPopupWindow = null; } public void setPatternEditor(NodeSubstitutePatternEditor patternEditor) { myPatternEditor = patternEditor; } public void setContextCell(@NotNull EditorCell contextCell) { myContextCell = contextCell; } public void setIsSmart(boolean isSmart) { myIsSmart = isSmart; } public NodeSubstitutePatternEditor getPatternEditor() { if (myPatternEditor == null) { myPatternEditor = new NodeSubstitutePatternEditor(); } return myPatternEditor; } NodeItemCellRenderer getCellRenderer() { if (myCellRenderer == null) { myCellRenderer = new NodeItemCellRenderer(this); } return myCellRenderer; } public boolean isVisible() { if (myChooserActivated) { NodeSubstitutePatternEditor patternEditor = getPatternEditor(); assert patternEditor.isActivated(); assert myContextCell != null; assert myNodeSubstituteInfo != null; } return myChooserActivated; } public boolean isMenuEmpty() { return myMenuEmpty; } /** * This method should be used for test purposes only * <p> * Number of substitute actions suggested by substitute chooser. * Check isVisible() before using this method * * @return number of substitute actions * @throws java.lang.IllegalStateException if the chooser is not visible */ public int getNumberOfActions() { if (!isVisible()) { throw new IllegalStateException("NodeSubstituteChooser is not visible"); } if (isMenuEmpty()) { return 0; } return mySubstituteActions.size(); } /** * Makes the chooser visible or invisible. * * @param visible true to make the chooser visible; false to * make it invisible. * @throws java.lang.IllegalStateException if making visible and context cell is null or substitute info is null */ public void setVisible(boolean visible) { if (myChooserActivated != visible) { boolean canShowPopup = getEditorWindow() != null && getEditorWindow().isShowing() && !(RuntimeFlags.isTestMode()); if (visible) { if (myContextCell == null || myNodeSubstituteInfo == null) { throw new IllegalStateException("Context cell and substitute info must not be null to show the NodeSubstituteChooser"); } myEditorComponent.pushKeyboardHandler(this); rebuildMenuEntries(); Point location = calcPatternEditorLocation(); if (location == null) { location = new Point(10, 10); } getPatternEditor().activate(getEditorWindow(), location, calcPatternEditorDimension(), canShowPopup); getPopupWindow().setSelectionIndex(0); if (canShowPopup) { getPopupWindow().setVisible(true); getPopupWindow().scrollToSelection(); } myPopupActivated = true; } else { dispose(); myNodeSubstituteInfo.invalidateActions(); myCellRenderer = null; myPopupWindow = null; myPopupActivated = false; myEditorComponent.popKeyboardHandler(); myContextCell = null; myNodeSubstituteInfo = null; } setUserChoseItem(false); myChooserActivated = visible; } } private List<SubstituteAction> getMatchingActions(final String pattern, final boolean strictMatching) { final ITypeContextOwner contextOwner = myIsSmart ? new NonReusableTypecheckingContextOwner() : myEditorComponent.getTypecheckingContextOwner(); return TypeContextManager.getInstance().runTypeCheckingComputation(contextOwner, myEditorComponent.getEditedNode(), context -> { if (myIsSmart) { return myNodeSubstituteInfo.getSmartMatchingActions(pattern, strictMatching, myContextCell); } else { return myNodeSubstituteInfo.getMatchingActions(pattern, strictMatching); } }); } private void rebuildMenuEntries() { if (myIsSmart) { // Command is required here because in "smart" mode: // - new temp model will be created & registered in the repository inside temp module // - this model will be modified by "smart" complete acton type calculation process // this command should not be associated with the current document to not show up in the undo stack getModelAccess().executeCommand(this::doRebuildMenuEntries); } else { getModelAccess().runReadAction(this::doRebuildMenuEntries); } } private void doRebuildMenuEntries() { myMenuEmpty = false; final String pattern = getPatternEditor().getPattern(); List<SubstituteAction> matchingActions = getMatchingActions(pattern, false); boolean needToTrim; String trimPattern = IntelligentInputUtil.trimLeft(pattern); if (pattern.equals(trimPattern)) { needToTrim = false; } else { needToTrim = true; if (!matchingActions.isEmpty()) { for (SubstituteAction action : matchingActions) { if (action.canSubstitute(pattern)) { needToTrim = false; break; } } } } if (needToTrim) { matchingActions = getMatchingActions(trimPattern, false); } try { Collections.sort(matchingActions, new SubstituteActionComparator(needToTrim ? trimPattern : pattern) { private Map<SubstituteAction, Integer> myLocalSortPrioritiesMap = new HashMap<>(); private Map<SubstituteAction, Integer> myRatesMap = new HashMap<>(); private Map<SubstituteAction, String> myVisibleMatchingTextsMap = new HashMap<>(); private Map<SubstituteAction, Boolean> myCanSubstituteStrictlyMap = new HashMap<>(); private Map<SubstituteAction, Boolean> myStartsWithMap = new HashMap<>(); private Map<SubstituteAction, Boolean> myStartsWithLowerCaseMap = new HashMap<>(); @Override protected int getLocalSortPriority(SubstituteAction action) { Integer priority = myLocalSortPrioritiesMap.get(action); if (priority == null) { priority = super.getLocalSortPriority(action); myLocalSortPrioritiesMap.put(action, priority); } return priority; } @Override protected String getVisibleMatchingText(SubstituteAction action) { String visibleText = myVisibleMatchingTextsMap.get(action); if (visibleText == null) { visibleText = super.getVisibleMatchingText(action); myVisibleMatchingTextsMap.put(action, visibleText); } return visibleText; } @Override protected boolean canSubstituteStrictly(SubstituteAction action) { Boolean canSubstituteStrictly = myCanSubstituteStrictlyMap.get(action); if (canSubstituteStrictly == null) { canSubstituteStrictly = super.canSubstituteStrictly(action); myCanSubstituteStrictlyMap.put(action, canSubstituteStrictly); } return canSubstituteStrictly; } @Override protected int getRate(SubstituteAction action) { Integer rate = myRatesMap.get(action); if (rate == null) { rate = super.getRate(action); myRatesMap.put(action, rate); } return rate; } @Override protected boolean startsWith(SubstituteAction action) { Boolean startsWith = myStartsWithMap.get(action); if (startsWith == null) { startsWith = super.startsWith(action); myStartsWithMap.put(action, startsWith); } return startsWith; } @Override protected boolean startsWithLowerCase(SubstituteAction action) { Boolean startsWithLowerCase = myStartsWithLowerCaseMap.get(action); if (startsWithLowerCase == null) { startsWithLowerCase = super.startsWithLowerCase(action); myStartsWithLowerCaseMap.put(action, startsWithLowerCase); } return startsWithLowerCase; } }); } catch (Exception e) { LOG.error(e, e); } mySubstituteActions = matchingActions; if (mySubstituteActions.size() == 0) { myMenuEmpty = true; mySubstituteActions.add(new AbstractNodeSubstituteAction() { @Override public String getMatchingText(String pattern) { return "No suggestions for \"" + getPatternEditor().getPattern() + "\""; } @Override public String getVisibleMatchingText(String pattern) { return getMatchingText(pattern); } @Override public SNode doSubstitute(@Nullable final jetbrains.mps.openapi.editor.EditorContext editorContext, String pattern) { return null; } }); } getPopupWindow().updateListDimension(getCellRenderer().getMaxDimension(mySubstituteActions)); getPopupWindow().initListModel(); } private void setUserChoseItem(boolean chose) { myUserChoseItem = chose; } private void processKeyEventInternal() { if (myPopupActivated) { SubstituteAction actionToSelect = getPopupWindow().getCurrentSelectedSubstituteAction(); rebuildMenuEntries(); selectPreviouslySelectedAction(actionToSelect); getPopupWindow().scrollToSelection(); if (getEditorWindow() != null && !RuntimeFlags.isTestMode()) { getPopupWindow().pack(); getPopupWindow().repaint(); } } } private void selectPreviouslySelectedAction(SubstituteAction actionToSelect) { if (myUserChoseItem && actionToSelect != null) { int indexOfPreviouslySelectedAction = mySubstituteActions.indexOf(actionToSelect); if (indexOfPreviouslySelectedAction == -1) { setUserChoseItem(false); getPopupWindow().setSelectionIndex(0); } else { getPopupWindow().setSelectionIndex(indexOfPreviouslySelectedAction); } } else { getPopupWindow().setSelectionIndex(0); } } @Override public boolean processKeyPressed(EditorContext editorContext, KeyEvent keyEvent) { String oldPattern = getPatternEditor().getPattern(); if (getPatternEditor().processKeyPressed(keyEvent)) { if (oldPattern.length() > getPatternEditor().getPattern().length()) { setUserChoseItem(false); } processKeyEventInternal(); return true; } if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) { setVisible(false); return true; } if (myPopupActivated) { return menu_processKeyPressed(keyEvent); } if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER || (keyEvent.getKeyCode() == KeyEvent.VK_SPACE && keyEvent.isControlDown())) { return doSubstitute(); } return false; } @Override public boolean processKeyTyped(EditorContext editorContext, KeyEvent keyEvent) { if (getPatternEditor().processKeyTyped(keyEvent)) { processKeyEventInternal(); return true; } return false; } @Override public boolean processKeyReleased(EditorContext editorContext, KeyEvent keyEvent) { return false; } @Override public boolean processTextChanged(EditorContext editorContext, InputMethodEvent inputEvent) { if (getPatternEditor().processTextChanged(inputEvent)) { processKeyEventInternal(); return true; } return false; } private boolean doSubstitute() { final String pattern = getPatternEditor().getPattern(); if (mySubstituteActions.size() == 1) { final SubstituteAction action = mySubstituteActions.get(0); if (new ModelAccessHelper(getModelAccess()).runReadAction(() -> action.canSubstitute(pattern))) { setVisible(false); getModelAccess().executeCommand(new EditorCommand(myEditorComponent) { @Override protected void doExecute() { action.substitute(myEditorComponent.getEditorContext(), pattern); } }); } } return true; } private ModelAccess getModelAccess() { return myEditorComponent.getEditorContext().getRepository().getModelAccess(); } private boolean menu_processKeyPressed(KeyEvent keyEvent) { if (keyEvent.getKeyCode() == KeyEvent.VK_UP) { getPopupWindow().setSelectionIndex(getPopupWindow().getSelectionIndex() - 1); setUserChoseItem(true); repaintPopupMenu(); return true; } if (keyEvent.getKeyCode() == KeyEvent.VK_DOWN) { getPopupWindow().setSelectionIndex(getPopupWindow().getSelectionIndex() + 1); setUserChoseItem(true); repaintPopupMenu(); return true; } if (keyEvent.getKeyCode() == KeyEvent.VK_PAGE_UP) { getPopupWindow().setSelectionIndex(getPopupWindow().getSelectionIndex() - getPageSize()); setUserChoseItem(true); repaintPopupMenu(); return true; } if (keyEvent.getKeyCode() == KeyEvent.VK_PAGE_DOWN) { getPopupWindow().setSelectionIndex(getPopupWindow().getSelectionIndex() + getPageSize()); setUserChoseItem(true); repaintPopupMenu(); return true; } if (keyEvent.getKeyCode() == KeyEvent.VK_HOME) { getPopupWindow().setSelectionIndex(0); setUserChoseItem(true); repaintPopupMenu(); return true; } if (keyEvent.getKeyCode() == KeyEvent.VK_END) { getPopupWindow().setSelectionIndex(mySubstituteActions.size() - 1); setUserChoseItem(true); repaintPopupMenu(); return true; } if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER || keyEvent.getKeyCode() == KeyEvent.VK_TAB) { if (!myMenuEmpty) { doSubstituteSelection(); } return true; } return true; } private int getPageSize() { return getPopupWindow().myList.getLastVisibleIndex() - getPopupWindow().myList.getFirstVisibleIndex(); } private void doSubstituteSelection() { final String pattern = getPatternEditor().getPattern(); final SubstituteAction action = mySubstituteActions.get(getPopupWindow().getSelectionIndex()); setVisible(false); myEditorComponent.getEditorContext().getRepository().getModelAccess().executeCommand(new EditorCommand(myEditorComponent) { @Override public void doExecute() { action.substitute(myEditorComponent.getEditorContext(), pattern); } }); } private void repaintPopupMenu() { if (myPopupActivated) { getPopupWindow().scrollToSelection(); getPopupWindow().repaint(); } } public void dispose() { if (myPopupWindow != null) { myPopupWindow.dispose(); } if (myPatternEditor != null) { myPatternEditor.done(); } } public void clearContent() { setVisible(false); mySubstituteActions.clear(); } private enum PopupWindowPosition { TOP, BOTTOM } jetbrains.mps.openapi.editor.EditorComponent getEditorComponent() { return myEditorComponent; } class PopupWindow extends JWindow { //COLORS: change after IDEA com.intellij.codeInsight.lookup.impl.LookupCellRenderer will be refactored to use Editor's Fonts & Colors settings private final Color BACKGROUND_COLOR = UIUtil.isUnderDarcula() ? new Color(0x141D29) : new Color(235, 244, 254); private final Color FOREGROUND_COLOR = EditorColorsManager.getInstance().getGlobalScheme().getDefaultForeground(); private final Color SELECTED_BACKGROUND_COLOR = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR); private final Color SELECTED_FOREGROUND_COLOR = EditorColorsManager.getInstance().getGlobalScheme().getColor(EditorColors.SELECTION_FOREGROUND_COLOR); private JList myList = new JBList(new DefaultListModel()); private PopupWindowPosition myPosition = PopupWindowPosition.BOTTOM; private JScrollPane myScroller = ScrollPaneFactory.createScrollPane(myList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); ComponentAdapter myComponentListener = new ComponentAdapter() { @Override public void componentMoved(ComponentEvent e) { if (NodeSubstituteChooser.this.isVisible()) { NodeSubstituteChooser.this.moveToContextCell(); } } }; public PopupWindow(final Window owner) { super(owner); getOwner().addComponentListener(myComponentListener); myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //TODO: change to EditorColorManager default font myList.setFont(EditorSettings.getInstance().getDefaultEditorFont()); myList.setBackground(BACKGROUND_COLOR); myList.setForeground(FOREGROUND_COLOR); myList.setSelectionBackground(SELECTED_BACKGROUND_COLOR); myList.setSelectionForeground(SELECTED_FOREGROUND_COLOR); myList.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { setUserChoseItem(true); repaintPopupMenu(); } @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { doSubstituteSelection(); } } }); myList.setCellRenderer(getCellRenderer()); add(myScroller); myScroller.getHorizontalScrollBar().setFocusable(false); myScroller.getVerticalScrollBar().setFocusable(false); myList.setFocusable(false); setPosition(PopupWindowPosition.BOTTOM); setLayout(new AbstractLayoutManager() { @Override public Dimension preferredLayoutSize(Container parent) { int height = myScroller.getPreferredSize().height; int width = myScroller.getPreferredSize().width; if (myList.getModel().getSize() > myList.getVisibleRowCount() && myList.getVisibleRowCount() > 1) { height -= myList.getFixedCellHeight() / 2; } return new Dimension(width, height); } @Override public void layoutContainer(Container parent) { relayout(); } }); } @Override public void dispose() { getOwner().removeComponentListener(myComponentListener); super.dispose(); } public SubstituteAction getCurrentSelectedSubstituteAction() { int selectionIndex = getSelectionIndex(); if (selectionIndex != -1) { return ((SubstituteAction) myList.getModel().getElementAt(selectionIndex)); } else { return null; } } public int getSelectionIndex() { return myList.getSelectedIndex(); } public void setSelectionIndex(int index) { if (index < 0) { index = myList.getModel().getSize() - 1; } else if (index >= myList.getModel().getSize()) { index = 0; } myList.setSelectedIndex(index); } public void moveToPatternEditor() { Point location = getPatternEditor().getLeftBottomPosition(); Point newLocation = getLocationWithRespectToScreenBounds(location, WindowsUtil.findDeviceBoundsAt(location)); setLocation(newLocation); } private void relayout() { if (!getPatternEditor().isActivated()) { return; } Point location = getPatternEditor().getLeftBottomPosition(); Rectangle deviceBounds = WindowsUtil.findDeviceBoundsAt(location); Dimension preferredSize = getPreferredSize(); if (getPosition() == PopupWindowPosition.BOTTOM && location.getY() + preferredSize.getHeight() > deviceBounds.height + deviceBounds.y - 150 && location.getY() - getPatternEditor().getHeight() / 2 > deviceBounds.y + deviceBounds.height / 2) { setPosition(PopupWindowPosition.TOP); } if (getPosition() == PopupWindowPosition.TOP && myList.getFixedCellHeight() != 0) { double maxHeight = location.getY() - getPatternEditor().getHeight() - deviceBounds.y; double visibleRowCount = maxHeight / myList.getFixedCellHeight(); if (visibleRowCount < myList.getVisibleRowCount()) { if (visibleRowCount <= 1) { myList.setVisibleRowCount(1); } else { myList.setVisibleRowCount((int) visibleRowCount); } preferredSize = getPreferredSize(); } } if (preferredSize.getWidth() >= deviceBounds.width) { setSize(deviceBounds.width, preferredSize.height); } else { setSize(preferredSize); } Point newLocation = getLocationWithRespectToScreenBounds(location, deviceBounds); setLocation(newLocation); myScroller.setSize(getSize()); myScroller.validate(); } private Point getLocationWithRespectToScreenBounds(Point location, Rectangle deviceBounds) { if (getPosition() == PopupWindowPosition.TOP) { location = new Point(location.x, location.y - getHeight() - getPatternEditor().getHeight()); } if (location.x < deviceBounds.x) { location.x = deviceBounds.x; } if (getWidth() + location.x > deviceBounds.width + deviceBounds.x) { location = new Point(deviceBounds.width + deviceBounds.x - getWidth(), location.y); } return location; } public void updateListDimension(Dimension dimension) { myList.setVisibleRowCount(Math.min(mySubstituteActions.size(), MAX_LOOKUP_LIST_HEIGHT)); myList.setFixedCellHeight(dimension.height); myList.setFixedCellWidth(dimension.width); } private void initListModel() { myCellRenderer.setLightweightMode(true); try { myList.setModel(new ListModel() { @Override public int getSize() { return mySubstituteActions.size(); } @Override public Object getElementAt(int index) { return mySubstituteActions.get(index); } @Override public void addListDataListener(ListDataListener l) { } @Override public void removeListDataListener(ListDataListener l) { } }); } finally { myCellRenderer.setLightweightMode(false); } } public void scrollToSelection() { myList.ensureIndexIsVisible(getSelectionIndex()); } private PopupWindowPosition getPosition() { return myPosition; } private void setPosition(PopupWindowPosition position) { myPosition = position; } } }