/* * Copyright 2000-2015 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 com.intellij.ui; import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.INativeFileType; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.vfs.PersistentFSConstants; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.pom.Navigatable; import com.intellij.util.Alarm; import com.intellij.util.OpenSourceUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; public abstract class AutoScrollToSourceHandler { private Alarm myAutoScrollAlarm; protected AutoScrollToSourceHandler() { } public void install(final JTree tree) { myAutoScrollAlarm = new Alarm(); new ClickListener() { @Override public boolean onClick(@NotNull MouseEvent e, int clickCount) { if (clickCount > 1) return false; TreePath location = tree.getPathForLocation(e.getPoint().x, e.getPoint().y); if (location != null) { onMouseClicked(tree); return isAutoScrollMode(); } return false; } }.installOn(tree); tree.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(final MouseEvent e) { onSelectionChanged(tree); } }); tree.addTreeSelectionListener( new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { onSelectionChanged(tree); } } ); } public void install(final JTable table) { myAutoScrollAlarm = new Alarm(); new ClickListener() { @Override public boolean onClick(@NotNull MouseEvent e, int clickCount) { if (clickCount >= 2) return false; Component location = table.getComponentAt(e.getPoint()); if (location != null) { onMouseClicked(table); return isAutoScrollMode(); } return false; } }.installOn(table); table.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(final MouseEvent e) { onSelectionChanged(table); } }); table.getSelectionModel().addListSelectionListener( new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { onSelectionChanged(table); } } ); } public void install(final JList jList) { myAutoScrollAlarm = new Alarm(); new ClickListener() { @Override public boolean onClick(@NotNull MouseEvent e, int clickCount) { if (clickCount >= 2) return false; final Object source = e.getSource(); final int index = jList.locationToIndex(SwingUtilities.convertPoint(source instanceof Component ? (Component)source : null, e.getPoint(), jList)); if (index >= 0 && index < jList.getModel().getSize()) { onMouseClicked(jList); return true; } return false; } }.installOn(jList); jList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { onSelectionChanged(jList); } }); } public void cancelAllRequests(){ if (myAutoScrollAlarm != null) { myAutoScrollAlarm.cancelAllRequests(); } } public void onMouseClicked(final Component component) { cancelAllRequests(); if (isAutoScrollMode()){ ApplicationManager.getApplication().invokeLater(() -> scrollToSource(component)); } } private void onSelectionChanged(final Component component) { if (component != null && !component.isShowing()) return; if (!isAutoScrollMode()) { return; } if (needToCheckFocus() && !component.hasFocus()) { return; } myAutoScrollAlarm.cancelAllRequests(); myAutoScrollAlarm.addRequest( () -> { if (component.isShowing()) { //for tests scrollToSource(component); } }, 500 ); } protected boolean needToCheckFocus(){ return true; } protected abstract boolean isAutoScrollMode(); protected abstract void setAutoScrollMode(boolean state); protected void scrollToSource(final Component tree) { DataContext dataContext=DataManager.getInstance().getDataContext(tree); getReady(dataContext).doWhenDone(() -> { DataContext context = DataManager.getInstance().getDataContext(tree); final VirtualFile vFile = CommonDataKeys.VIRTUAL_FILE.getData(context); if (vFile != null) { // Attempt to navigate to the virtual file with unknown file type will show a modal dialog // asking to register some file type for this file. This behaviour is undesirable when autoscrolling. if (vFile.getFileType() == FileTypes.UNKNOWN || vFile.getFileType() instanceof INativeFileType) return; //IDEA-84881 Don't autoscroll to very large files if (vFile.getLength() > PersistentFSConstants.getMaxIntellisenseFileSize()) return; } Navigatable[] navigatables = CommonDataKeys.NAVIGATABLE_ARRAY.getData(context); if (navigatables != null) { if (navigatables.length > 1) { return; } for (Navigatable navigatable : navigatables) { // we are not going to open modal dialog during autoscrolling if (!navigatable.canNavigateToSource()) return; } } OpenSourceUtil.navigate(false, true, navigatables); }); } @NotNull public ToggleAction createToggleAction() { return new AutoscrollToSourceAction(); } private class AutoscrollToSourceAction extends ToggleAction implements DumbAware { public AutoscrollToSourceAction() { super(UIBundle.message("autoscroll.to.source.action.name"), UIBundle.message("autoscroll.to.source.action.description"), AllIcons.General.AutoscrollToSource); } public boolean isSelected(AnActionEvent event) { return isAutoScrollMode(); } public void setSelected(AnActionEvent event, boolean flag) { setAutoScrollMode(flag); } } private ActionCallback getReady(DataContext context) { ToolWindow toolWindow = PlatformDataKeys.TOOL_WINDOW.getData(context); return toolWindow != null ? toolWindow.getReady(this) : ActionCallback.DONE; } }