/*
* Copyright 2003-2012 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.ide.ui.dialogs.properties.roots.editors;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.icons.AllIcons.Nodes;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.idea.ActionsBundle;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.fileChooser.FileSystemTree;
import com.intellij.openapi.fileChooser.actions.NewFolderAction;
import com.intellij.openapi.fileChooser.ex.FileSystemTreeImpl;
import com.intellij.openapi.fileChooser.impl.FileTreeBuilder;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.roots.ui.configuration.actions.IconWithTextAction;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.roots.ToolbarPanel;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ui.tree.TreeUtil;
import jetbrains.mps.extapi.persistence.FileBasedModelRoot;
import jetbrains.mps.extapi.persistence.SourceRootKind;
import jetbrains.mps.extapi.persistence.SourceRootKinds;
import jetbrains.mps.ide.vfs.VirtualFileUtils;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.ui.persistence.ModelRootEntryEditor;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import java.awt.BorderLayout;
import java.awt.LayoutManager;
import java.util.Collection;
import java.util.Comparator;
public class FileBasedModelRootEditor implements ModelRootEntryEditor {
protected Tree myTree;
private FileSystemTreeImpl myFileSystemTree;
private final JPanel myTreePanel;
private final DefaultMutableTreeNode EMPTY_TREE_ROOT = new DefaultMutableTreeNode("<empty>");
protected DefaultActionGroup myEditingActionsGroup;
private FileBasedModelRootEntry myFileBasedModelRootEntry;
private FileChooserDescriptor myDescriptor;
public FileBasedModelRootEditor() {
myTree = new Tree();
myTree.setRootVisible(true);
myTree.setShowsRootHandles(true);
myEditingActionsGroup = new DefaultActionGroup();
TreeUtil.installActions(myTree);
new TreeSpeedSearch(myTree);
myTreePanel = new MyPanel(new BorderLayout());
final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
myTreePanel.add(new ToolbarPanel(scrollPane, myEditingActionsGroup), BorderLayout.CENTER);
myTreePanel.setVisible(false);
myDescriptor = FileChooserDescriptorFactory.createMultipleFilesNoJarsDescriptor();
myDescriptor.setShowFileSystemRoots(false);
}
@NotNull
static String getKindText(@NotNull SourceRootKind kind) {
if (kind == SourceRootKinds.EXCLUDED) {
return "Excluded";
} else if (kind == SourceRootKinds.SOURCES) {
return "Sources";
}
return "Unknown";
}
protected void createEditingActions() {
myEditingActionsGroup.removeAll();
FileBasedModelRoot fileBasedModelRoot = myFileBasedModelRootEntry.getModelRoot();
Collection<SourceRootKind> kinds = fileBasedModelRoot.getSupportedFileKinds1();
for (final SourceRootKind kind : kinds) {
AnAction modelRootAnAction =
new ToggleFBModelRootKindAction(myTree, this, getKindText(kind), null, myFileBasedModelRootEntry.getKindIcon(kind)) {
@NotNull
@Override
protected SourceRootKind getKind() {
return kind;
}
};
// modelRootAnAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.ALT_MASK)), myTree);
myEditingActionsGroup.add(modelRootAnAction);
}
}
protected TreeCellRenderer getModelRootEntryCellRenderer() {
return new FileBasedModelRootEntryTreeCellRender(this);
}
public void setFileBasedModelRootEntry(final FileBasedModelRootEntry fileBasedModelRootEntry) {
if (myFileBasedModelRootEntry != null && myFileBasedModelRootEntry.equals(fileBasedModelRootEntry)) {
return;
}
if (myFileSystemTree != null) {
Disposer.dispose(myFileSystemTree);
myFileSystemTree = null;
}
if (myFileBasedModelRootEntry != null) {
myFileBasedModelRootEntry = null;
}
if (fileBasedModelRootEntry == null) {
((DefaultTreeModel) myTree.getModel()).setRoot(EMPTY_TREE_ROOT);
myTreePanel.setVisible(false);
if (myFileSystemTree != null) {
Disposer.dispose(myFileSystemTree);
}
return;
}
myTreePanel.setVisible(true);
myFileBasedModelRootEntry = fileBasedModelRootEntry;
IFile path = myFileBasedModelRootEntry.getModelRoot().getContentDirectory();
VirtualFile file = path == null ? null : VirtualFileUtils.getProjectVirtualFile(path);
setRoot(file);
final Runnable init = () -> {
myFileSystemTree.updateTree();
if (file != null) {
myFileSystemTree.select(file, null);
}
};
myFileSystemTree = new FileSystemTreeImpl(null, myDescriptor, myTree, getModelRootEntryCellRenderer(), init, null) {
@Override
protected AbstractTreeBuilder createTreeBuilder(JTree tree, DefaultTreeModel treeModel, AbstractTreeStructure treeStructure,
Comparator<NodeDescriptor> comparator, FileChooserDescriptor descriptor, final Runnable onInitialized) {
return new MyFileTreeBuilder(tree, treeModel, treeStructure, comparator, descriptor, onInitialized);
}
};
myFileSystemTree.showHiddens(true);
Disposer.register(myFileBasedModelRootEntry, myFileSystemTree);
final NewFolderAction newFolderAction = new MyNewFolderAction();
final DefaultActionGroup mousePopupGroup = new DefaultActionGroup();
mousePopupGroup.add(myEditingActionsGroup);
mousePopupGroup.addSeparator();
mousePopupGroup.add(newFolderAction);
mousePopupGroup.add(new ChooseModelRootContentFolder());
myFileSystemTree.registerMouseListener(mousePopupGroup);
createEditingActions();
}
private void setRoot(VirtualFile file) {
myDescriptor.setRoots(file);
if (file != null) {
myDescriptor.setTitle(FileUtil.toSystemDependentName(file.getPath()));
}
}
public void selectFile(@NotNull IFile file) {
VirtualFile file2Select = VirtualFileUtils.getProjectVirtualFile(file);
if (file2Select == null) {
return;
}
myTree.requestFocus();
if (myFileSystemTree != null) {
myFileSystemTree.select(file2Select, null);
}
}
public FileBasedModelRootEntry getFileBasedModelRootEntry() {
return myFileBasedModelRootEntry;
}
public FileChooserDescriptor getDescriptor() {
return myDescriptor;
}
public void setDescriptor(FileChooserDescriptor descriptor) {
myDescriptor = descriptor;
//TODO: find better way to update
final FileBasedModelRootEntry entry = myFileBasedModelRootEntry;
setFileBasedModelRootEntry(null);
setFileBasedModelRootEntry(entry);
}
@Override
public JComponent createComponent() {
return myTreePanel;
}
private class MyPanel extends JPanel implements DataProvider {
private MyPanel(final LayoutManager layout) {
super(layout);
}
@Override
@Nullable
public Object getData(@NonNls final String dataId) {
if (CommonDataKeys.VIRTUAL_FILE_ARRAY.is(dataId)) {
return myFileSystemTree.getSelectedFiles();
}
if (FileSystemTree.DATA_KEY.is(dataId)) {
return myFileSystemTree;
}
return null;
}
}
private static class MyFileTreeBuilder extends FileTreeBuilder {
public MyFileTreeBuilder(JTree tree,
DefaultTreeModel treeModel,
AbstractTreeStructure treeStructure,
Comparator<NodeDescriptor> comparator,
FileChooserDescriptor descriptor,
@Nullable Runnable onInitialized) {
super(tree, treeModel, treeStructure, comparator, descriptor, onInitialized);
}
@Override
protected boolean isAlwaysShowPlus(NodeDescriptor nodeDescriptor) {
return false; // need this in order to not show plus for empty directories
}
}
private static class MyNewFolderAction extends NewFolderAction implements CustomComponentAction {
private MyNewFolderAction() {
super(ActionsBundle.message("action.FileChooser.NewFolder.text"),
ActionsBundle.message("action.FileChooser.NewFolder.description"),
Actions.NewFolder);
}
@Override
public JComponent createCustomComponent(Presentation presentation) {
return IconWithTextAction.createCustomComponentImpl(this, presentation);
}
}
private class ChooseModelRootContentFolder extends AnAction implements DumbAware {
private final FileChooserDescriptor myDescriptor;
public ChooseModelRootContentFolder() {
super("Change Root Folder", "", Nodes.HomeFolder);
myDescriptor = new FileChooserDescriptor(false, true, true, false, true, false);
myDescriptor.setTitle("Select new content entry for model root");
}
@Override
public void actionPerformed(AnActionEvent e) {
VirtualFile[] files = FileChooser.chooseFiles(myDescriptor, null, null,
VirtualFileManager.getInstance().findFileByUrl(
VirtualFileManager.constructUrl("file", myFileBasedModelRootEntry.getModelRoot().getContentRoot())
)
);
if (files.length != 1) {
return;
}
FileBasedModelRootEntry fileBasedModelRootEntry = FileBasedModelRootEditor.this.myFileBasedModelRootEntry;
fileBasedModelRootEntry.getModelRoot().setContentRoot(files[0].getPath());
FileBasedModelRootEditor.this.myFileBasedModelRootEntry = null;
FileBasedModelRootEditor.this.setFileBasedModelRootEntry(fileBasedModelRootEntry);
FileBasedModelRootEditor.this.myFileBasedModelRootEntry.updateUI();
}
}
}