blob: 6ecb61920100da1fdcc369c69fba486a79e99aba [file] [log] [blame]
/*
* Copyright 2000-2013 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.uiDesigner.snapShooter;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.RunManager;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.application.ApplicationConfiguration;
import com.intellij.execution.application.ApplicationConfigurationType;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.execution.util.JreVersionDetector;
import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeView;
import com.intellij.ide.highlighter.JavaHighlightingColors;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.uiDesigner.GuiFormFileType;
import com.intellij.uiDesigner.UIDesignerBundle;
import com.intellij.uiDesigner.designSurface.InsertComponentProcessor;
import com.intellij.uiDesigner.palette.ComponentItem;
import com.intellij.uiDesigner.palette.Palette;
import com.intellij.uiDesigner.radComponents.LayoutManagerRegistry;
import com.intellij.uiDesigner.radComponents.RadComponentFactory;
import com.intellij.uiDesigner.radComponents.RadContainer;
import com.intellij.util.IncorrectOperationException;
import icons.UIDesignerIcons;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* @author yole
*/
public class CreateSnapShotAction extends AnAction {
private static final Logger LOG = Logger.getInstance("com.intellij.uiDesigner.snapShooter.CreateSnapShotAction");
@Override
public void update(AnActionEvent e) {
final Project project = e.getData(CommonDataKeys.PROJECT);
final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
e.getPresentation().setVisible(project != null && view != null && hasDirectoryInPackage(project, view));
}
private static boolean hasDirectoryInPackage(final Project project, final IdeView view) {
ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(project).getFileIndex();
PsiDirectory[] dirs = view.getDirectories();
for (PsiDirectory dir : dirs) {
if (projectFileIndex.isInSourceContent(dir.getVirtualFile()) && JavaDirectoryService.getInstance().getPackage(dir) != null) {
return true;
}
}
return false;
}
public void actionPerformed(AnActionEvent e) {
final Project project = e.getData(CommonDataKeys.PROJECT);
final IdeView view = e.getData(LangDataKeys.IDE_VIEW);
if (project == null || view == null) {
return;
}
final PsiDirectory dir = view.getOrChooseDirectory();
if (dir == null) return;
final SnapShotClient client = new SnapShotClient();
List<RunnerAndConfigurationSettings> appConfigurations = new ArrayList<RunnerAndConfigurationSettings>();
RunnerAndConfigurationSettings snapshotConfiguration = null;
boolean connected = false;
ApplicationConfigurationType cfgType = ApplicationConfigurationType.getInstance();
List<RunnerAndConfigurationSettings> racsi = RunManager.getInstance(project).getConfigurationSettingsList(cfgType);
for(RunnerAndConfigurationSettings config: racsi) {
if (config.getConfiguration() instanceof ApplicationConfiguration) {
ApplicationConfiguration appConfig = (ApplicationConfiguration) config.getConfiguration();
appConfigurations.add(config);
if (appConfig.ENABLE_SWING_INSPECTOR) {
SnapShooterConfigurationSettings settings = SnapShooterConfigurationSettings.get(appConfig);
snapshotConfiguration = config;
if (settings.getLastPort() > 0) {
try {
client.connect(settings.getLastPort());
connected = true;
}
catch(IOException ex) {
connected = false;
}
}
}
if (connected) break;
}
}
if (snapshotConfiguration == null) {
snapshotConfiguration = promptForSnapshotConfiguration(project, appConfigurations);
if (snapshotConfiguration == null) return;
}
if (!connected) {
int rc = Messages.showYesNoDialog(project, UIDesignerBundle.message("snapshot.run.prompt"),
UIDesignerBundle.message("snapshot.title"), Messages.getQuestionIcon());
if (rc == Messages.NO) return;
final ApplicationConfiguration appConfig = (ApplicationConfiguration) snapshotConfiguration.getConfiguration();
final SnapShooterConfigurationSettings settings = SnapShooterConfigurationSettings.get(appConfig);
settings.setNotifyRunnable(new Runnable() {
public void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.prepare.notice"),
UIDesignerBundle.message("snapshot.title"), Messages.getInformationIcon());
try {
client.connect(settings.getLastPort());
}
catch(IOException ex) {
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.connection.error"),
UIDesignerBundle.message("snapshot.title"), Messages.getErrorIcon());
return;
}
runSnapShooterSession(client, project, dir, view);
}
});
}
});
try {
ExecutionEnvironmentBuilder.create(DefaultRunExecutor.getRunExecutorInstance(), snapshotConfiguration).buildAndExecute();
}
catch (ExecutionException ex) {
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.run.error", ex.getMessage()),
UIDesignerBundle.message("snapshot.title"), Messages.getErrorIcon());
}
}
else {
runSnapShooterSession(client, project, dir, view);
}
}
private static void runSnapShooterSession(final SnapShotClient client, final Project project, final PsiDirectory dir, final IdeView view) {
try {
client.suspendSwing();
}
catch (IOException e1) {
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.connection.error"),
UIDesignerBundle.message("snapshot.title"), Messages.getInformationIcon());
return;
}
final MyDialog dlg = new MyDialog(project, client, dir);
dlg.show();
if (dlg.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
final int id = dlg.getSelectedComponentId();
final Ref<Object> result = new Ref<Object>();
ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() {
public void run() {
try {
result.set(client.createSnapshot(id));
}
catch (Exception ex) {
result.set(ex);
}
}
}, UIDesignerBundle.message("progress.creating.snapshot"), false, project);
String snapshot = null;
if (result.get() instanceof String) {
snapshot = (String) result.get();
}
else {
Exception ex = (Exception) result.get();
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.create.error", ex.getMessage()),
UIDesignerBundle.message("snapshot.title"), Messages.getErrorIcon());
}
if (snapshot != null) {
final String snapshot1 = snapshot;
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
public void run() {
try {
PsiFile formFile = PsiFileFactory.getInstance(dir.getProject())
.createFileFromText(dlg.getFormName() + GuiFormFileType.DOT_DEFAULT_EXTENSION, snapshot1);
formFile = (PsiFile)dir.add(formFile);
formFile.getVirtualFile().setCharset(CharsetToolkit.UTF8_CHARSET);
formFile.getViewProvider().getDocument().setText(snapshot1);
view.selectElement(formFile);
}
catch (IncorrectOperationException ex) {
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.save.error", ex.getMessage()),
UIDesignerBundle.message("snapshot.title"), Messages.getErrorIcon());
}
}
}, "", null);
}
});
}
}
try {
client.resumeSwing();
}
catch (IOException ex) {
Messages.showErrorDialog(project, UIDesignerBundle.message("snapshot.connection.broken"),
UIDesignerBundle.message("snapshot.title"));
}
client.dispose();
}
@Nullable
private static RunnerAndConfigurationSettings promptForSnapshotConfiguration(final Project project,
final List<RunnerAndConfigurationSettings> configurations) {
if (configurations.isEmpty()) {
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.no.configuration.error"),
UIDesignerBundle.message("snapshot.title"), Messages.getInformationIcon());
return null;
}
for(int i=configurations.size()-1; i >= 0; i--) {
final JreVersionDetector detector = new JreVersionDetector();
final ApplicationConfiguration configuration = (ApplicationConfiguration)configurations.get(i).getConfiguration();
if (!detector.isJre50Configured(configuration) && !detector.isModuleJre50Configured(configuration)) {
configurations.remove(i);
}
}
if (configurations.isEmpty()) {
Messages.showMessageDialog(project, UIDesignerBundle.message("snapshot.no.compatible.configuration.error"),
UIDesignerBundle.message("snapshot.title"), Messages.getInformationIcon());
return null;
}
final RunnerAndConfigurationSettings snapshotConfiguration;
if (configurations.size() == 1) {
final int rc = Messages.showYesNoDialog(
project,
UIDesignerBundle.message("snapshot.confirm.configuration.prompt", configurations.get(0).getConfiguration().getName()),
UIDesignerBundle.message("snapshot.title"),
Messages.getQuestionIcon());
if (rc == Messages.NO) {
return null;
}
snapshotConfiguration = configurations.get(0);
}
else {
String[] names = new String[configurations.size()];
for(int i=0; i<configurations.size(); i++) {
names [i] = configurations.get(i).getConfiguration().getName();
}
int rc = Messages.showChooseDialog(
project,
UIDesignerBundle.message("snapshot.choose.configuration.prompt"),
UIDesignerBundle.message("snapshot.title"),
Messages.getQuestionIcon(),
names,
names [0]
);
if (rc < 0) return null;
snapshotConfiguration = configurations.get(rc);
}
((ApplicationConfiguration) snapshotConfiguration.getConfiguration()).ENABLE_SWING_INSPECTOR = true;
return snapshotConfiguration;
}
private static class MyDialog extends DialogWrapper {
private JPanel myRootPanel;
private JTree myComponentTree;
private JTextField myFormNameTextField;
private JLabel myErrorLabel;
private final Project myProject;
private final SnapShotClient myClient;
private final PsiDirectory myDirectory;
@NonNls private static final String SWING_PACKAGE = "javax.swing.";
private MyDialog(Project project, final SnapShotClient client, final PsiDirectory dir) {
super(project, true);
myProject = project;
myClient = client;
myDirectory = dir;
init();
setTitle(UIDesignerBundle.message("snapshot.title"));
final SnapShotTreeModel model = new SnapShotTreeModel(client);
myComponentTree.setModel(model);
myComponentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
myComponentTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
updateOKAction();
}
});
for(int i=0; i<2; i++) {
for(int row=myComponentTree.getRowCount()-1; row >= 0; row--) {
myComponentTree.expandRow(row);
}
}
myComponentTree.getSelectionModel().setSelectionPath(myComponentTree.getPathForRow(0));
myFormNameTextField.setText(suggestFormName());
final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme();
final TextAttributes attributes = globalScheme.getAttributes(JavaHighlightingColors.STRING);
final SimpleTextAttributes titleAttributes =
new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, attributes.getForegroundColor());
myComponentTree.setCellRenderer(new ColoredTreeCellRenderer() {
public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
SnapShotRemoteComponent rc = (SnapShotRemoteComponent) value;
String className = rc.getClassName();
if (className.startsWith(SWING_PACKAGE)) {
append(className.substring(SWING_PACKAGE.length()), SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
else {
append(className, SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
if (rc.getText().length() > 0) {
append(" \"" + rc.getText() + "\"", titleAttributes);
}
if (rc.getLayoutManager().length() > 0) {
append(" (" + rc.getLayoutManager() + ")", SimpleTextAttributes.GRAY_ATTRIBUTES);
}
if (rc.isTopLevel()) {
setIcon(AllIcons.FileTypes.UiForm);
}
else {
final Palette palette = Palette.getInstance(myProject);
final ComponentItem item = palette.getItem(rc.getClassName());
if (item != null) {
setIcon(item.getSmallIcon());
}
else {
setIcon(UIDesignerIcons.Unknown);
}
}
}
});
myFormNameTextField.getDocument().addDocumentListener(new DocumentAdapter() {
protected void textChanged(DocumentEvent e) {
updateOKAction();
}
});
updateOKAction();
}
@NonNls
private String suggestFormName() {
int count = 0;
do {
count++;
}
while(myDirectory.findFile("Form" + count + GuiFormFileType.DOT_DEFAULT_EXTENSION) != null);
return "Form" + count;
}
private void updateOKAction() {
final boolean selectedComponentValid = isSelectedComponentValid();
setOKActionEnabled(isFormNameValid() && selectedComponentValid);
if (myComponentTree.getSelectionPath() != null && !selectedComponentValid) {
myErrorLabel.setText(UIDesignerBundle.message("snapshooter.invalid.container"));
}
else {
myErrorLabel.setText(" ");
}
}
private boolean isSelectedComponentValid() {
final TreePath selectionPath = myComponentTree.getSelectionPath();
if (selectionPath == null) return false;
SnapShotRemoteComponent rc = (SnapShotRemoteComponent) selectionPath.getLastPathComponent();
if (isValidComponent(rc)) return true;
if (selectionPath.getPathCount() == 2) {
// capture frame/dialog root pane when a frame or dialog itself is selected
final SnapShotRemoteComponent[] children = rc.getChildren();
return children != null && children.length > 0 && isValidComponent(children[0]);
}
return false;
}
private boolean isValidComponent(final SnapShotRemoteComponent rc) {
PsiClass componentClass =
JavaPsiFacade.getInstance(myProject).findClass(rc.getClassName().replace('$', '.'), GlobalSearchScope.allScope(myProject));
while(componentClass != null) {
if (JPanel.class.getName().equals(componentClass.getQualifiedName()) ||
JTabbedPane.class.getName().equals(componentClass.getQualifiedName()) ||
JScrollPane.class.getName().equals(componentClass.getQualifiedName()) ||
JSplitPane.class.getName().equals(componentClass.getQualifiedName())) {
return true;
}
componentClass = componentClass.getSuperClass();
}
return false;
}
private boolean isFormNameValid() {
return myFormNameTextField.getText().length() > 0;
}
@Override @NonNls
protected String getDimensionServiceKey() {
return "CreateSnapShotAction.MyDialog";
}
@Override
public JComponent getPreferredFocusedComponent() {
return myFormNameTextField;
}
@NotNull
@Override
protected Action getOKAction() {
final Action okAction = super.getOKAction();
okAction.putValue(Action.NAME, UIDesignerBundle.message("create.snapshot.button"));
return okAction;
}
@Override
protected void doOKAction() {
if (getOKAction().isEnabled()) {
try {
myDirectory.checkCreateFile(getFormName() + GuiFormFileType.DOT_DEFAULT_EXTENSION);
}
catch (IncorrectOperationException e) {
JOptionPane.showMessageDialog(myRootPanel, UIDesignerBundle.message("error.form.already.exists", getFormName()));
return;
}
if (!checkUnknownLayoutManagers(myDirectory.getProject())) return;
close(OK_EXIT_CODE);
}
}
private boolean checkUnknownLayoutManagers(final Project project) {
final Set<String> layoutManagerClasses = new TreeSet<String>();
final SnapShotRemoteComponent rc = (SnapShotRemoteComponent) myComponentTree.getSelectionPath().getLastPathComponent();
assert rc != null;
final Ref<Exception> err = new Ref<Exception>();
Runnable runnable = new Runnable() {
public void run() {
try {
collectUnknownLayoutManagerClasses(project, rc, layoutManagerClasses);
}
catch (IOException e) {
err.set(e);
}
}
};
if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable,
UIDesignerBundle.message("progress.validating.layout.managers"),
false, project)) {
return false;
}
if (!err.isNull()) {
Messages.showErrorDialog(myRootPanel, UIDesignerBundle.message("snapshot.connection.broken"), UIDesignerBundle.message("snapshot.title"));
return false;
}
if (!layoutManagerClasses.isEmpty()) {
StringBuilder builder = new StringBuilder(UIDesignerBundle.message("snapshot.unknown.layout.prefix"));
for(String layoutManagerClass: layoutManagerClasses) {
builder.append(layoutManagerClass).append("\n");
}
builder.append(UIDesignerBundle.message("snapshot.unknown.layout.prompt"));
return Messages.showYesNoDialog(myProject, builder.toString(),
UIDesignerBundle.message("snapshot.title"), Messages.getQuestionIcon()) == Messages.YES;
}
return true;
}
private void collectUnknownLayoutManagerClasses(final Project project, final SnapShotRemoteComponent rc,
final Set<String> layoutManagerClasses) throws IOException {
RadComponentFactory factory = InsertComponentProcessor.getRadComponentFactory(project, rc.getClassName());
if (factory instanceof RadContainer.Factory && rc.getLayoutManager().length() > 0 &&
!LayoutManagerRegistry.isKnownLayoutClass(rc.getLayoutManager())) {
layoutManagerClasses.add(rc.getLayoutManager());
}
SnapShotRemoteComponent[] children = rc.getChildren();
if (children == null) {
children = myClient.listChildren(rc.getId());
rc.setChildren(children);
}
for(SnapShotRemoteComponent child: children) {
collectUnknownLayoutManagerClasses(project, child, layoutManagerClasses);
}
}
@Nullable
protected JComponent createCenterPanel() {
return myRootPanel;
}
public int getSelectedComponentId() {
final TreePath selectionPath = myComponentTree.getSelectionPath();
SnapShotRemoteComponent rc = (SnapShotRemoteComponent) selectionPath.getLastPathComponent();
if (!isValidComponent(rc) && selectionPath.getPathCount() == 2) {
// capture frame/dialog root pane when a frame or dialog itself is selected
final SnapShotRemoteComponent[] children = rc.getChildren();
if (children != null && children.length > 0 && isValidComponent(children [0])) {
return children [0].getId();
}
}
return rc.getId();
}
public String getFormName() {
return myFormNameTextField.getText();
}
}
}