From 1edbecfcc2156700657cf25fe31bf4936dbd9dae Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Wed, 20 Jan 2010 12:23:49 +0300 Subject: [PATCH] IDEA-41229: attach source directly from edit window --- .../impl/AddSourcesNotificationProvider.java | 148 ++++++++++++++++++ .../roots/ui/configuration/PathUIUtils.java | 9 +- .../ide/highlighter/JavaClassFileType.java | 6 + .../impl/DefaultFileTypeFactory.java | 2 +- .../openapi/roots/libraries/LibraryTable.java | 3 +- .../intellij/ui/EditorNotificationPanel.java | 14 +- .../com/intellij/ui/EditorNotifications.java | 16 +- .../openapi/fileEditor/impl/EditorWindow.java | 7 +- .../ChangelistConflictNotificationPanel.java | 40 ++--- resources/src/META-INF/IdeaPlugin.xml | 2 + 10 files changed, 214 insertions(+), 33 deletions(-) create mode 100644 java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AddSourcesNotificationProvider.java diff --git a/java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AddSourcesNotificationProvider.java b/java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AddSourcesNotificationProvider.java new file mode 100644 index 000000000000..d9080da12406 --- /dev/null +++ b/java/idea-ui/src/com/intellij/codeInsight/daemon/impl/AddSourcesNotificationProvider.java @@ -0,0 +1,148 @@ +/* + * Copyright 2000-2010 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.codeInsight.daemon.impl; + +import com.intellij.ProjectTopics; +import com.intellij.ide.highlighter.JavaClassFileType; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx; +import com.intellij.openapi.fileEditor.impl.EditorWindow; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.ui.configuration.PathUIUtils; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.compiled.ClsClassImpl; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.ui.EditorNotificationPanel; +import com.intellij.ui.EditorNotifications; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author Dmitry Avdeev + */ +public class AddSourcesNotificationProvider implements EditorNotifications.Provider { + + private static final Key KEY = Key.create("add sources to class"); + + private final Project myProject; + + public AddSourcesNotificationProvider(Project project, final EditorNotifications notifications) { + myProject = project; + myProject.getMessageBus().connect(project).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + + public void beforeRootsChange(ModuleRootEvent event) { + } + + public void rootsChanged(ModuleRootEvent event) { + notifications.updateAllNotifications(); + } + }); + } + + public Key getKey() { + return KEY; + } + + public EditorNotificationPanel createNotificationPanel(final VirtualFile file) { + + if (file.getFileType() != JavaClassFileType.INSTANCE) return null; + final Library library = findLibrary(file); + if (library == null) return null; + + PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); + if (!(psiFile instanceof PsiJavaFile)) return null; + PsiClass[] classes = ((PsiJavaFile)psiFile).getClasses(); + if (classes.length == 0) return null; + final String fqn = classes[0].getQualifiedName(); + if (fqn == null) return null; + + final EditorNotificationPanel panel = new EditorNotificationPanel(); + panel.setText("Class sources not found"); + panel.createActionLabel("Attach sources", new Runnable() { + public void run() { + final Library library = findLibrary(file); + if (library == null) { + Messages.showErrorDialog(myProject, "Cannot find library for " + StringUtil.getShortName(fqn), "Error"); + return; + } + FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, true, false, true, true); + descriptor.setTitle(ProjectBundle.message("library.attach.sources.action")); + descriptor.setDescription(ProjectBundle.message("library.attach.sources.description")); + VirtualFile[] roots = library.getFiles(OrderRootType.CLASSES); + VirtualFile[] candidates = FileChooser.chooseFiles(myProject, descriptor, roots.length == 0 ? null : roots[0]); + final VirtualFile[] files = PathUIUtils.scandAndSelectDetectedJavaSourceRoots(myProject, candidates); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + Library.ModifiableModel model = library.getModifiableModel(); + for (VirtualFile virtualFile : files) { + model.addRoot(virtualFile, OrderRootType.SOURCES); + } + model.commit(); + } + }); + + PsiClass clsClass = JavaPsiFacade.getInstance(myProject).findClass(fqn, GlobalSearchScope.allScope(myProject)); + if (!(clsClass instanceof ClsClassImpl)) { + return; + } + PsiClass sourceClass = ((ClsClassImpl)clsClass).getSourceMirrorClass(); + if (sourceClass == null) { + return; + } + VirtualFile newFile = sourceClass.getContainingFile().getVirtualFile(); + assert newFile != null; + + FileEditorManagerEx manager = FileEditorManagerEx.getInstanceEx(myProject); + EditorWindow[] windows = manager.getWindows(); + for (EditorWindow window : windows) { + int index = window.findFileIndex(file); + if (index != -1) { + manager.closeFile(file, window); + try { + newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, index); + manager.openFile(newFile, true); + } + finally { + newFile.putUserData(EditorWindow.INITIAL_INDEX_KEY, null); + } + } + } + } + }); + return panel; + } + + @Nullable + private Library findLibrary(VirtualFile file) { + List entries = ProjectRootManager.getInstance(myProject).getFileIndex().getOrderEntriesForFile(file); + for (OrderEntry entry : entries) { + if (entry instanceof LibraryOrderEntry) { + return ((LibraryOrderEntry)entry).getLibrary(); + } + } + return null; + } +} diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/PathUIUtils.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/PathUIUtils.java index 6127aae93b61..a5f6d0ed9c04 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/PathUIUtils.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/PathUIUtils.java @@ -17,6 +17,7 @@ package com.intellij.openapi.roots.ui.configuration; import com.intellij.ide.util.JavaUtilForVfs; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ui.configuration.libraryEditor.DetectedSourceRootsDialog; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; @@ -38,11 +39,11 @@ public class PathUIUtils { * This method takes a candidates for the project root, then scans the candidates and * if multiple candidates or non root source directories are found whithin some * directories, it shows a dialog that allows selecting or deselecting them. - * @param component a parent component + * @param parent a parent parent or project * @param rootCandidates a candidates for roots * @return a array of source folders or empty array if non was selected or dialog was canceled. */ - public static VirtualFile[] scandAndSelectDetectedJavaSourceRoots(Component component, final VirtualFile[] rootCandidates) { + public static VirtualFile[] scandAndSelectDetectedJavaSourceRoots(Object parent, final VirtualFile[] rootCandidates) { final Set result = new HashSet(); final Map> detectedRootsMap = new LinkedHashMap>(); // scan for roots @@ -59,7 +60,9 @@ public class PathUIUtils { } }, "Scanning for source roots", true, null); if(!detectedRootsMap.isEmpty()) { - DetectedSourceRootsDialog dlg = new DetectedSourceRootsDialog(component, detectedRootsMap); + DetectedSourceRootsDialog dlg = parent instanceof Component ? + new DetectedSourceRootsDialog((Component)parent, detectedRootsMap) : + new DetectedSourceRootsDialog((Project)parent, detectedRootsMap); dlg.show(); if (dlg.isOK()) { result.addAll(dlg.getChosenRoots()); diff --git a/java/java-impl/src/com/intellij/ide/highlighter/JavaClassFileType.java b/java/java-impl/src/com/intellij/ide/highlighter/JavaClassFileType.java index ebc12352e3bd..055dda576dcd 100644 --- a/java/java-impl/src/com/intellij/ide/highlighter/JavaClassFileType.java +++ b/java/java-impl/src/com/intellij/ide/highlighter/JavaClassFileType.java @@ -24,8 +24,14 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; public class JavaClassFileType implements FileType { + + public static JavaClassFileType INSTANCE = new JavaClassFileType(); + private static final Icon ICON = IconLoader.getIcon("/fileTypes/javaClass.png"); + private JavaClassFileType() { + } + @NotNull public String getName() { return "CLASS"; diff --git a/java/java-impl/src/com/intellij/openapi/fileTypes/impl/DefaultFileTypeFactory.java b/java/java-impl/src/com/intellij/openapi/fileTypes/impl/DefaultFileTypeFactory.java index 5c577d3bfe9a..6b9743fae277 100644 --- a/java/java-impl/src/com/intellij/openapi/fileTypes/impl/DefaultFileTypeFactory.java +++ b/java/java-impl/src/com/intellij/openapi/fileTypes/impl/DefaultFileTypeFactory.java @@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull; */ public class DefaultFileTypeFactory extends FileTypeFactory { public void createFileTypes(@NotNull final FileTypeConsumer consumer) { - consumer.consume(new JavaClassFileType(), "class"); + consumer.consume(JavaClassFileType.INSTANCE, "class"); consumer.consume(JavaFileType.INSTANCE, "java"); diff --git a/platform/lang-api/src/com/intellij/openapi/roots/libraries/LibraryTable.java b/platform/lang-api/src/com/intellij/openapi/roots/libraries/LibraryTable.java index 5ba863bf91c4..6cc8fd65bb66 100644 --- a/platform/lang-api/src/com/intellij/openapi/roots/libraries/LibraryTable.java +++ b/platform/lang-api/src/com/intellij/openapi/roots/libraries/LibraryTable.java @@ -24,7 +24,8 @@ import java.util.EventListener; import java.util.Iterator; /** - * @author dsl + * @see com.intellij.openapi.roots.libraries.LibraryTablesRegistrar#getLibraryTable(com.intellij.openapi.project.Project) + * @author dsl */ public interface LibraryTable { @NotNull diff --git a/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java b/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java index e443aee5aac8..dff1f5cfd25a 100644 --- a/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java +++ b/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java @@ -37,8 +37,10 @@ public class EditorNotificationPanel extends JPanel { public EditorNotificationPanel() { super(new BorderLayout()); + setBackground(LightColors.YELLOW); - setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4)); + setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 4)); + setPreferredSize(new Dimension(-1, 24)); add(myLabel, BorderLayout.CENTER); myLinksPanel = new JPanel(new FlowLayout()); @@ -51,11 +53,19 @@ public class EditorNotificationPanel extends JPanel { } public HyperlinkLabel createActionLabel(final String text, @NonNls final String actionId) { + return createActionLabel(text, new Runnable() { + public void run() { + executeAction(actionId); + } + }); + } + + public HyperlinkLabel createActionLabel(final String text, final Runnable action) { HyperlinkLabel label = new HyperlinkLabel(text, Color.BLUE, LightColors.YELLOW, Color.BLUE); label.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(final HyperlinkEvent e) { if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { - executeAction(actionId); + action.run(); } } }); diff --git a/platform/platform-api/src/com/intellij/ui/EditorNotifications.java b/platform/platform-api/src/com/intellij/ui/EditorNotifications.java index 44fbfdcc0560..77f655f29579 100644 --- a/platform/platform-api/src/com/intellij/ui/EditorNotifications.java +++ b/platform/platform-api/src/com/intellij/ui/EditorNotifications.java @@ -41,6 +41,7 @@ public class EditorNotifications extends AbstractProjectComponent { public interface Provider { + Key getKey(); @Nullable @@ -88,16 +89,23 @@ public class EditorNotifications extends AbstractProjectComponent { }); } + public void updateAllNotifications() { + VirtualFile[] files = myFileEditorManager.getOpenFiles(); + for (VirtualFile file : files) { + updateNotifications(file); + } + } + private void updateNotification(FileEditor editor, Key key, @Nullable JComponent component) { + JComponent old = editor.getUserData(key); + if (old != null) { + myFileEditorManager.removeTopComponent(editor, old); + } if (component != null) { myFileEditorManager.addTopComponent(editor, component); editor.putUserData((Key)key, component); } else { - JComponent old = editor.getUserData(key); - if (old != null) { - myFileEditorManager.removeTopComponent(editor, old); - } editor.putUserData(key, null); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java index 4a2cb302ff32..06700ca4a0cc 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java @@ -27,6 +27,7 @@ import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.ui.LayeredIcon; @@ -57,6 +58,7 @@ public class EditorWindow { private boolean myIsDisposed = false; private static final Icon PIN_ICON = IconLoader.getIcon("/nodes/tabPin.png"); + public static final Key INITIAL_INDEX_KEY = Key.create("initial editor index"); protected EditorWindow(final EditorsSplitters owner) { myOwner = owner; @@ -404,7 +406,8 @@ public class EditorWindow { setSelectedEditor(editor, focusEditor); } else { - final int indexToInsert = myTabbedPane.getSelectedIndex() + 1; + Integer initialIndex = editor.getFile().getUserData(INITIAL_INDEX_KEY); + final int indexToInsert = initialIndex == null ? myTabbedPane.getSelectedIndex() + 1 : initialIndex; final VirtualFile file = editor.getFile(); final Icon template = IconLoader.getIcon("/fileTypes/text.png"); myTabbedPane.insertTab(file, new EmptyIcon(template.getIconWidth(), template.getIconHeight()), new TComp(editor), null, indexToInsert); @@ -661,7 +664,7 @@ public class EditorWindow { return -1; } - private int findFileIndex(final VirtualFile fileToFind) { + public int findFileIndex(final VirtualFile fileToFind) { for (int i = 0; i != getTabCount(); ++i) { final VirtualFile file = getFileAt(i); if (file.equals (fileToFind)) { diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/conflicts/ChangelistConflictNotificationPanel.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/conflicts/ChangelistConflictNotificationPanel.java index 95741cda6006..1455c9c713f9 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/conflicts/ChangelistConflictNotificationPanel.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/conflicts/ChangelistConflictNotificationPanel.java @@ -25,7 +25,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.EditorNotificationPanel; import com.intellij.ui.InplaceButton; -import javax.swing.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collections; @@ -50,13 +49,26 @@ public class ChangelistConflictNotificationPanel extends EditorNotificationPanel myChangeList = manager.getChangeList(myChange); assert myChangeList != null; myLabel.setText("File from non-active changelist is modified"); - createActionLabel("Move changes", "move"). - setToolTipText("Move changes to active changelist (" + manager.getDefaultChangeList().getName() + ")"); - createActionLabel("Switch changelist", "switch"). - setToolTipText("Set active changelist to '" + myChangeList.getName() + "'"); - createActionLabel("Ignore", "ignore"). - setToolTipText("Hide this notification"); - setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0)); + createActionLabel("Move changes", new Runnable() { + public void run() { + ChangelistConflictResolution.MOVE.resolveConflict(myTracker.getProject(), myChangeList.getChanges()); + } + }).setToolTipText("Move changes to active changelist (" + manager.getDefaultChangeList().getName() + ")"); + + createActionLabel("Switch changelist", new Runnable() { + public void run() { + List changes = Collections.singletonList(myTracker.getChangeListManager().getChange(myFile)); + ChangelistConflictResolution.SWITCH.resolveConflict(myTracker.getProject(), changes); + } + }).setToolTipText("Set active changelist to '" + myChangeList.getName() + "'"); + + createActionLabel("Ignore", new Runnable() { + public void run() { + myTracker.ignoreConflict(myFile, true); + } + }).setToolTipText("Hide this notification"); + +// setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0)); myLinksPanel.add(new InplaceButton("Show options dialog", IconLoader.getIcon("/general/ideOptions.png"), new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -66,16 +78,4 @@ public class ChangelistConflictNotificationPanel extends EditorNotificationPanel } })); } - - @Override - protected void executeAction(String actionId) { - if (actionId.equals("move")) { - ChangelistConflictResolution.MOVE.resolveConflict(myTracker.getProject(), myChangeList.getChanges()); - } else if (actionId.equals("switch")) { - List changes = Collections.singletonList(myTracker.getChangeListManager().getChange(myFile)); - ChangelistConflictResolution.SWITCH.resolveConflict(myTracker.getProject(), changes); - } else if (actionId.equals("ignore")) { - myTracker.ignoreConflict(myFile, true); - } - } } diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 9c8ed5cd1320..a774617b58db 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -668,6 +668,8 @@ + +