/* * RCompletionToolTip.java * * Copyright (C) 2009-15 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.studio.client.workbench.views.source.editors.text.r; import java.util.ArrayList; import org.rstudio.core.client.HandlerRegistrations; import org.rstudio.core.client.Rectangle; import org.rstudio.core.client.StringUtil; import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay; import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay.AnchoredSelection; import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position; import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range; import org.rstudio.studio.client.workbench.views.source.editors.text.cpp.CppCompletionToolTip; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.BlurEvent; import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.logical.shared.AttachEvent; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.Event.NativePreviewHandler; import com.google.gwt.user.client.Window; public class RCompletionToolTip extends CppCompletionToolTip { public RCompletionToolTip(DocDisplay docDisplay) { docDisplay_ = docDisplay; handlers_ = new HandlerRegistrations(); // set the max width setMaxWidth(Window.getClientWidth() - 200); getElement().getStyle().setZIndex(10000); addAttachHandler(new AttachEvent.Handler() { @Override public void onAttachOrDetach(AttachEvent event) { if (event.isAttached()) attachHandlers(); else detachHandlers(); } }); } private void attachHandlers() { handlers_.add(docDisplay_.addBlurHandler(new BlurHandler() { @Override public void onBlur(BlurEvent event) { hide(); } })); } private void detachHandlers() { handlers_.removeHandler(); } public boolean previewKeyDown(NativeEvent event) { if (!isShowing()) return false; if (event.getKeyCode() == KeyCodes.KEY_ESCAPE) { event.stopPropagation(); event.preventDefault(); hide(); return true; } return false; } public void resolvePositionAndShow(String signature, Rectangle rectangle) { resolvePositionAndShow( signature, rectangle.getLeft(), rectangle.getTop()); } public void resolvePositionAndShow(String signature) { setCursorAnchor(); resolvePositionAndShow(signature, docDisplay_.getCursorBounds()); } private String truncateSignature(String signature) { return truncateSignature(signature, 200); } private String truncateSignature(String signature, int length) { // Perform smart truncation -- look for a comma at or after the // length specified (so we don't cut argument names in half) if (signature.length() > length) { String truncated = signature; ArrayList<Integer> commaIndices = StringUtil.indicesOf(signature, ','); if (commaIndices.size() == 0) { truncated = signature.substring(0, length); } for (int i = 0; i < commaIndices.size(); i++) { int index = commaIndices.get(i); if (index >= length) { truncated = signature.substring(0, index + 1); break; } } return truncated + " <...truncated...> )"; } return signature; } public void resolvePositionAndShow(String signature, int left, int top) { signature = truncateSignature(signature); if (signature != null) setText(signature); resolveWidth(signature); resolvePositionRelativeTo(left, top); setVisible(true); show(); } private void resolveWidth(String signature) { if (signature.length() > 400) setWidth("800px"); else if (signature.length() > 300) setWidth("700px"); else if (signature.length() > 200) setWidth("600px"); else setWidth(getOffsetWidth() + "px"); } public void resolvePositionAndShow(String signature, Position displayPos) { resolvePositionAndShow(signature, docDisplay_.getPositionBounds(displayPos)); } public void resolvePositionAndShow(String signature, Range activeRange) { setAnchor(activeRange.getStart(), activeRange.getEnd()); resolvePositionAndShow( signature, docDisplay_.getPositionBounds(activeRange.getStart())); } private void resolvePositionRelativeTo(final int left, final int top) { // some constants final int H_PAD = 12; final int V_PAD = tooltipTopPadding(docDisplay_); final int H_BUFFER = 100; final int MIN_WIDTH = 300; // do we have enough room to the right? if not then int roomRight = Window.getClientWidth() - left; int maxWidth = Math.min(roomRight - H_BUFFER, 500); final boolean showLeft = maxWidth < MIN_WIDTH; if (showLeft) maxWidth = left - H_BUFFER; setMaxWidth(maxWidth); setPopupPositionAndShow(new PositionCallback(){ @Override public void setPosition(int offsetWidth, int offsetHeight) { // if we are showing left then adjust int adjustedLeft = left; if (showLeft) adjustedLeft = left - offsetWidth - H_PAD; setPopupPosition(adjustedLeft, top - getOffsetHeight() - V_PAD); } }); } private void setAnchor(Position start, Position end) { int startCol = start.getColumn(); if (startCol > 0) start.setColumn(start.getColumn() - 1); end.setColumn(end.getColumn() + 1); if (anchor_ != null) anchor_.detach(); anchor_ = docDisplay_.createAnchoredSelection(start, end); } private void setCursorAnchor() { Position start = docDisplay_.getSelectionStart(); start = Position.create(start.getRow(), start.getColumn() - 1); Position end = docDisplay_.getSelectionEnd(); end = Position.create(end.getRow(), end.getColumn() + 1); if (anchor_ != null) anchor_.detach(); anchor_ = docDisplay_.createAnchoredSelection(start, end); } @Override protected void onLoad() { super.onLoad(); if (nativePreviewReg_ != null) { nativePreviewReg_.removeHandler(); nativePreviewReg_ = null; } nativePreviewReg_ = Event.addNativePreviewHandler(new NativePreviewHandler() { public void onPreviewNativeEvent(NativePreviewEvent e) { int eventType = e.getTypeInt(); if (eventType == Event.ONKEYDOWN || eventType == Event.ONMOUSEDOWN) { // dismiss if we've left our anchor zone // (defer this so the current key has a chance to // enter the editor and affect the cursor) Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override public void execute() { Position cursorPos = docDisplay_.getCursorPosition(); if (anchor_ != null) { Range anchorRange = anchor_.getRange(); if (cursorPos.isBeforeOrEqualTo(anchorRange.getStart()) || cursorPos.isAfterOrEqualTo(anchorRange.getEnd())) { anchor_.detach(); anchor_ = null; hide(); } } } }); } } }); } @Override protected void onUnload() { super.onUnload(); if (nativePreviewReg_ != null) { nativePreviewReg_.removeHandler(); nativePreviewReg_ = null; } } public String getSignature() { return getLabel(); } private final DocDisplay docDisplay_; private final HandlerRegistrations handlers_; private HandlerRegistration nativePreviewReg_; private AnchoredSelection anchor_; }