Java tutorial
/* * Copyright 2016 Yuyou Chow * * 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 com.intellij.translation; import com.intellij.codeInsight.CodeInsightBundle; import com.intellij.codeInsight.documentation.DockablePopupManager; import com.intellij.codeInsight.hint.HintManagerImpl; import com.intellij.ide.DataManager; import com.intellij.ide.actions.BaseNavigateToSourceAction; import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.gotoByName.ChooseByNameBase; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.AnActionListener; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Pair; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.presentation.java.SymbolPresentationUtil; import com.intellij.translation.translator.Translator; import com.intellij.ui.ScrollingUtil; import com.intellij.ui.content.Content; import com.intellij.ui.popup.AbstractPopup; import com.intellij.ui.popup.PopupPositionManager; import com.intellij.util.Alarm; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.accessibility.AccessibleContextUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ActionListener; import java.lang.ref.WeakReference; import java.util.List; /** * Created by zyuyou on 16/6/20. * * https://github.com/JetBrains/intellij-community/blob/master/platform/lang-impl/src/com/intellij/codeInsight/documentation/DocumentationManager.java * @author JetBrains s.r.o. */ public class TranslationManager extends DockablePopupManager<TranslationComponent> { public static final ExtensionPointName<Translator> TRANSLATOR_EP = ExtensionPointName .create("com.intellij.translation.translator"); @NonNls public static final String TRANSLATION_LOCATION_AND_SIZE = "com.intellij.translation.popup"; public static final DataKey<String> SELECTED_QUICK_TRANSLATION_TEXT = DataKey .create("QUICK_TRANSLATION.SELECTED_TEXT"); private static final Logger LOG = Logger.getInstance("#" + TranslationManager.class.getName()); private static final String SHOW_TRANSLATION_IN_TOOL_WINDOW = "ShowTranslationInToolWindow"; private static final String TRANSLATION_AUTO_UPDATE_ENABLED = "TranslationAutoUpdateEnabled"; private Editor myEditor; private final Alarm myUpdateTranslationAlarm; private WeakReference<JBPopup> myTranslationHintRef; private Component myPreviouslyFocused; private final ActionManager myActionManager; private boolean myCloseOnSneeze; private ActionCallback myLastAction; private TranslationComponent myTestTranslationComponent; private AnAction myRestorePopupAction; public TranslationManager(final Project project, ActionManager manager) { super(project); myActionManager = manager; final AnActionListener actionListener = new AnActionListener() { @Override public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { final JBPopup hint = getTranslationHint(); if (hint != null) { if (action instanceof HintManagerImpl.ActionToIgnore) { ((AbstractPopup) hint).focusPreferredComponent(); return; } if (action instanceof ScrollingUtil.ListScrollAction) return; if (action == myActionManager.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN)) return; if (action == myActionManager.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_UP)) return; if (action == myActionManager.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_PAGE_DOWN)) return; if (action == myActionManager.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_PAGE_UP)) return; if (TranslationConstants.TRANSLATION_INPLACE_SETTINGS.equals(event.getPlace())) return; if (action instanceof BaseNavigateToSourceAction) return; closeTranslationHint(); } } @Override public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { } @Override public void beforeEditorTyping(char c, DataContext dataContext) { final JBPopup hint = getTranslationHint(); // todo when typing if (hint != null) { hint.cancel(); } } }; myActionManager.addAnActionListener(actionListener, project); myUpdateTranslationAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, myProject); } public static TranslationManager getInstance(Project project) { return ServiceManager.getService(project, TranslationManager.class); } @Override protected String getShowInToolWindowProperty() { return SHOW_TRANSLATION_IN_TOOL_WINDOW; } @Override protected String getAutoUpdateEnabledProperty() { return TRANSLATION_AUTO_UPDATE_ENABLED; } @Override protected String getAutoUpdateTitle() { return "Auto-update"; } @Override protected String getRestorePopupDescription() { return "Restore popup view mode"; } @Override protected String getAutoUpdateDescription() { return "Refresh com.intellij.com.intellij.translation on selection change automatically"; } @NotNull @Override protected AnAction createRestorePopupAction() { myRestorePopupAction = super.createRestorePopupAction(); return myRestorePopupAction; } @Override protected void restorePopupBehavior() { if (myPreviouslyFocused != null) { IdeFocusManager.getInstance(myProject).requestFocus(myPreviouslyFocused, true); } super.restorePopupBehavior(); updateComponent(); } @Override public void createToolWindow(PsiElement element, PsiElement originalElement) { super.createToolWindow(element, originalElement); // switch between toolWindow and popup if (myToolWindow != null) { myToolWindow.getComponent().putClientProperty(ChooseByNameBase.TEMPORARILY_FOCUSABLE_COMPONENT_KEY, Boolean.TRUE); if (myRestorePopupAction != null) { ShortcutSet quickTranslateShortCut = ActionManager.getInstance() .getAction(TranslationConstants.ACTION_QUICK_TRANSLATE).getShortcutSet(); myRestorePopupAction.registerCustomShortcutSet(quickTranslateShortCut, myToolWindow.getComponent()); myRestorePopupAction = null; } } } @Override protected TranslationComponent createComponent() { return new TranslationComponent(this, createActions()); } @Override protected void doUpdateComponent(PsiElement element, PsiElement originalElement, TranslationComponent component) { fetchTranslation(getDefaultCollector(myEditor.getSelectionModel().getSelectedText()), component); } @Override protected void doUpdateComponent(Editor editor, PsiFile psiFile) { showTranslation(editor, false, null); } @Override protected void doUpdateComponent(@NotNull PsiElement element) { // System.out.println("doUpdateComponent(@NotNull PsiElement element)"); } @Override protected String getTitle(PsiElement element) { return getTitle(element, true); } static String getTitle(@Nullable final PsiElement element, final boolean _short) { return _short ? "" : TranslationConstants.TOOL_WINDOW_ID; } @Override protected String getToolwindowId() { return TranslationConstants.TOOL_WINDOW_ID; } /** * @return <code>true</code> if quick doc control is configured to not prevent user-IDE interaction (e.g. should be closed if * the user presses a key); * <code>false</code> otherwise */ public boolean isMyCloseOnSneeze() { return myCloseOnSneeze; } @Nullable public JBPopup getTranslationHint() { if (myTranslationHintRef == null) return null; JBPopup hint = myTranslationHintRef.get(); if (hint == null || !hint.isVisible() && !ApplicationManager.getApplication().isUnitTestMode()) { myTranslationHintRef = null; return null; } return hint; } private void closeTranslationHint() { JBPopup hint = getTranslationHint(); if (hint == null) { return; } myCloseOnSneeze = false; hint.cancel(); Component toFocus = myPreviouslyFocused; hint.cancel(); if (toFocus != null) { IdeFocusManager.getInstance(myProject).requestFocus(toFocus, true); } } public void showTranslation(@NotNull Editor editor) { showTranslation(editor, true, null); } public void showTranslation(@NotNull Editor editor, boolean requestFocus, @Nullable final Runnable closeCallback) { myEditor = editor; SelectionModel selectionModel = editor.getSelectionModel(); if (selectionModel.getSelectedText() != null) { doShowTranslation(selectionModel.getSelectedText(), requestFocus, closeCallback); } } private void doShowTranslation(@NotNull String queryText, boolean requestFocus, @Nullable final Runnable closeCallback) { final Project project = myProject; if (!project.isOpen()) return; myPreviouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(project); JBPopup _oldHint = getTranslationHint(); if (myToolWindow == null && PropertiesComponent.getInstance().isTrueValue(SHOW_TRANSLATION_IN_TOOL_WINDOW)) { createToolWindow(null, null); } else if (myToolWindow != null) { Content content = myToolWindow.getContentManager().getSelectedContent(); if (content != null) { TranslationComponent component = (TranslationComponent) content.getComponent(); boolean samQuery = queryText.equals(component.getQuery()); if (samQuery) { JComponent preferredFocusableComponent = content.getPreferredFocusableComponent(); // focus toolwindow on the second actionPerformed boolean focus = requestFocus || CommandProcessor.getInstance().getCurrentCommand() != null; if (preferredFocusableComponent != null && focus) { IdeFocusManager.getInstance(myProject).requestFocus(preferredFocusableComponent, true); } } else { content.setDisplayName(getTitle(null, true)); fetchTranslation(getDefaultCollector(queryText), component, true); } } if (!myToolWindow.isVisible()) { myToolWindow.show(null); } } else if (_oldHint != null && _oldHint.isVisible() && _oldHint instanceof AbstractPopup) { TranslationComponent oldComponent = (TranslationComponent) ((AbstractPopup) _oldHint).getComponent(); fetchTranslation(getDefaultCollector(queryText), oldComponent); } else { showInPopup(queryText, requestFocus, closeCallback); } } private void showInPopup(@NotNull String queryText, boolean requestFocus, @Nullable final Runnable closeCallback) { final TranslationComponent component = myTestTranslationComponent == null ? new TranslationComponent(this) : myTestTranslationComponent; // todo NavigateCallback Processor<JBPopup> pinCallback = popup -> { createToolWindow(null, null); myToolWindow.setAutoHide(false); popup.cancel(); return false; }; ActionListener actionListener = e -> { createToolWindow(null, null); final JBPopup hint = getTranslationHint(); if (hint != null && hint.isVisible()) hint.cancel(); }; List<Pair<ActionListener, KeyStroke>> actions = ContainerUtil.newSmartList(); AnAction quickDocAction = ActionManager.getInstance() .getAction(TranslationConstants.ACTION_QUICK_TRANSLATE); for (Shortcut shortcut : quickDocAction.getShortcutSet().getShortcuts()) { if (!(shortcut instanceof KeyboardShortcut)) continue; actions.add(Pair.create(actionListener, ((KeyboardShortcut) shortcut).getFirstKeyStroke())); } final JBPopup hint = JBPopupFactory.getInstance().createComponentPopupBuilder(component, component) .setProject(myProject).setKeyboardActions(actions) .setDimensionServiceKey(myProject, TRANSLATION_LOCATION_AND_SIZE, false).setResizable(true) .setMovable(true).setRequestFocus(requestFocus).setCancelOnClickOutside(true) .setTitle(getTitle(null, false)).setCouldPin(pinCallback).setModalContext(false) .setCancelCallback(() -> { myCloseOnSneeze = false; if (closeCallback != null) { closeCallback.run(); } Disposer.dispose(component); myEditor = null; myPreviouslyFocused = null; return Boolean.TRUE; }).setKeyEventHandler(e -> { if (myCloseOnSneeze) { closeTranslationHint(); } if (AbstractPopup.isCloseRequest(e) && getTranslationHint() != null) { closeTranslationHint(); return true; } return false; }).createPopup(); component.setHint(hint); fetchTranslation(getDefaultCollector(queryText), component); myTranslationHintRef = new WeakReference<>(hint); } public void fetchTranslation(final TranslationCollector provider, final TranslationComponent component) { doFetchTranslation(component, provider, true, false); } public void fetchTranslation(final TranslationCollector provider, final TranslationComponent component, final boolean clearHistory) { doFetchTranslation(component, provider, true, clearHistory); } private ActionCallback doFetchTranslation(final TranslationComponent component, final TranslationCollector provider, final boolean cancelRequests, final boolean clearHistory) { final ActionCallback callback = new ActionCallback(); myLastAction = callback; boolean wasEmpty = component.isEmpty(); component.startWait(); if (cancelRequests) { myUpdateTranslationAlarm.cancelAllRequests(); } if (wasEmpty) { component.setText(TranslationBundle.message("translation.fetching.progress"), null, null); final AbstractPopup jbPopup = (AbstractPopup) getTranslationHint(); if (jbPopup != null) { jbPopup.setDimensionServiceKey(null); } } myUpdateTranslationAlarm.addRequest(() -> { if (myProject.isDisposed()) return; LOG.debug("Started fetching com.intellij.translation..."); final Throwable[] ex = new Throwable[1]; String text = null; try { text = provider.getTranslation(); } catch (Throwable e) { LOG.info(e); ex[0] = e; } if (ex[0] != null) { //noinspection SSBasedInspection SwingUtilities.invokeLater(() -> { String message = ex[0] instanceof IndexNotReadyException ? "Translation is not available until indices are built." : TranslationBundle.message("translation.external.fetch.error.message"); component.setText(message, null, null); callback.setDone(); }); return; } LOG.debug("Translation fetched successfully:\n", text); final String translationText = text; //noinspection SSBasedInspection SwingUtilities.invokeLater(() -> { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); if (translationText == null) { component.setText(TranslationBundle.message("translation.no.info.found"), provider.getQuery(), null); } else if (translationText.isEmpty()) { component.setText(component.getText(), provider.getQuery(), null); } else { component.setData(provider.getQuery(), translationText, provider.getTranslator()); } final AbstractPopup jbPopup = (AbstractPopup) getTranslationHint(); if (jbPopup == null) { callback.setDone(); return; } jbPopup.setDimensionServiceKey(TRANSLATION_LOCATION_AND_SIZE); jbPopup.setCaption(getTitle(null, false)); callback.setDone(); }); }, 10); return callback; } private TranslationCollector getDefaultCollector(final String queryText) { return new DefaultTranslationCollector(queryText); } private interface TranslationCollector { @Nullable String getTranslation() throws Exception; @Nullable String getQuery(); @Nullable String getExternalUrl(); @Nullable Translator getTranslator(); } private class DefaultTranslationCollector implements TranslationCollector { private final String myQuery; private String myExternalUrl; private Translator myTranslator; public DefaultTranslationCollector(String query) { this.myQuery = query; } @Nullable @Override public String getQuery() { return myQuery; } @Nullable @Override public String getExternalUrl() { return myExternalUrl; } @Nullable @Override public Translator getTranslator() { return myTranslator; } @Nullable @Override public String getTranslation() throws Exception { if (myQuery != null) { for (Translator provider : TranslationManager.TRANSLATOR_EP.getExtensions()) { final String translation = provider.fetchInfo(myQuery); if (translation != null) { LOG.debug("Fetched translation from ", provider.getTitle()); myExternalUrl = provider.getExternalUrl(myQuery); myTranslator = provider; return translation; } } } return null; } } void showHint(final JBPopup hint) { final Component focusOwner = IdeFocusManager.getInstance(myProject).getFocusOwner(); DataContext dataContext = DataManager.getInstance().getDataContext(focusOwner); PopupPositionManager.positionPopupInBestPosition(hint, myEditor, dataContext); } public Project getProject() { return myProject; } }