2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com
.intellij
.uiDesigner
.snapShooter
;
19 import com
.intellij
.execution
.ExecutionException
;
20 import com
.intellij
.execution
.RunManagerEx
;
21 import com
.intellij
.execution
.RunnerRegistry
;
22 import com
.intellij
.execution
.application
.ApplicationConfiguration
;
23 import com
.intellij
.execution
.application
.ApplicationConfigurationType
;
24 import com
.intellij
.execution
.executors
.DefaultRunExecutor
;
25 import com
.intellij
.execution
.impl
.RunnerAndConfigurationSettingsImpl
;
26 import com
.intellij
.execution
.runners
.ExecutionEnvironment
;
27 import com
.intellij
.execution
.runners
.ProgramRunner
;
28 import com
.intellij
.execution
.util
.JreVersionDetector
;
29 import com
.intellij
.ide
.IdeView
;
30 import com
.intellij
.uiDesigner
.GuiFormFileType
;
31 import com
.intellij
.openapi
.actionSystem
.AnAction
;
32 import com
.intellij
.openapi
.actionSystem
.AnActionEvent
;
33 import com
.intellij
.openapi
.actionSystem
.LangDataKeys
;
34 import com
.intellij
.openapi
.actionSystem
.PlatformDataKeys
;
35 import com
.intellij
.openapi
.application
.ApplicationManager
;
36 import com
.intellij
.openapi
.command
.CommandProcessor
;
37 import com
.intellij
.openapi
.diagnostic
.Logger
;
38 import com
.intellij
.openapi
.editor
.SyntaxHighlighterColors
;
39 import com
.intellij
.openapi
.editor
.colors
.EditorColorsManager
;
40 import com
.intellij
.openapi
.editor
.colors
.EditorColorsScheme
;
41 import com
.intellij
.openapi
.editor
.markup
.TextAttributes
;
42 import com
.intellij
.openapi
.progress
.ProgressManager
;
43 import com
.intellij
.openapi
.project
.Project
;
44 import com
.intellij
.openapi
.roots
.ProjectFileIndex
;
45 import com
.intellij
.openapi
.roots
.ProjectRootManager
;
46 import com
.intellij
.openapi
.ui
.DialogWrapper
;
47 import com
.intellij
.openapi
.ui
.Messages
;
48 import com
.intellij
.openapi
.util
.IconLoader
;
49 import com
.intellij
.openapi
.util
.Ref
;
50 import com
.intellij
.openapi
.vfs
.CharsetToolkit
;
51 import com
.intellij
.psi
.*;
52 import com
.intellij
.psi
.search
.GlobalSearchScope
;
53 import com
.intellij
.ui
.ColoredTreeCellRenderer
;
54 import com
.intellij
.ui
.DocumentAdapter
;
55 import com
.intellij
.ui
.SimpleTextAttributes
;
56 import com
.intellij
.uiDesigner
.UIDesignerBundle
;
57 import com
.intellij
.uiDesigner
.designSurface
.InsertComponentProcessor
;
58 import com
.intellij
.uiDesigner
.palette
.ComponentItem
;
59 import com
.intellij
.uiDesigner
.palette
.Palette
;
60 import com
.intellij
.uiDesigner
.radComponents
.LayoutManagerRegistry
;
61 import com
.intellij
.uiDesigner
.radComponents
.RadComponentFactory
;
62 import com
.intellij
.uiDesigner
.radComponents
.RadContainer
;
63 import com
.intellij
.util
.IncorrectOperationException
;
64 import org
.jetbrains
.annotations
.NonNls
;
65 import org
.jetbrains
.annotations
.Nullable
;
68 import javax
.swing
.event
.DocumentEvent
;
69 import javax
.swing
.event
.TreeSelectionEvent
;
70 import javax
.swing
.event
.TreeSelectionListener
;
71 import javax
.swing
.tree
.TreePath
;
72 import javax
.swing
.tree
.TreeSelectionModel
;
73 import java
.io
.IOException
;
74 import java
.util
.ArrayList
;
75 import java
.util
.List
;
77 import java
.util
.TreeSet
;
82 public class CreateSnapShotAction
extends AnAction
{
83 private static final Logger LOG
= Logger
.getInstance("com.intellij.uiDesigner.snapShooter.CreateSnapShotAction");
86 public void update(AnActionEvent e
) {
87 final Project project
= e
.getData(PlatformDataKeys
.PROJECT
);
88 final IdeView view
= e
.getData(LangDataKeys
.IDE_VIEW
);
89 e
.getPresentation().setVisible(project
!= null && view
!= null && hasDirectoryInPackage(project
, view
));
92 private static boolean hasDirectoryInPackage(final Project project
, final IdeView view
) {
93 ProjectFileIndex projectFileIndex
= ProjectRootManager
.getInstance(project
).getFileIndex();
94 PsiDirectory
[] dirs
= view
.getDirectories();
95 for (PsiDirectory dir
: dirs
) {
96 if (projectFileIndex
.isInSourceContent(dir
.getVirtualFile()) && JavaDirectoryService
.getInstance().getPackage(dir
) != null) {
103 public void actionPerformed(AnActionEvent e
) {
104 final Project project
= e
.getData(PlatformDataKeys
.PROJECT
);
105 final IdeView view
= e
.getData(LangDataKeys
.IDE_VIEW
);
106 if (project
== null || view
== null) {
110 final PsiDirectory dir
= view
.getOrChooseDirectory();
111 if (dir
== null) return;
113 final SnapShotClient client
= new SnapShotClient();
114 List
<RunnerAndConfigurationSettingsImpl
> appConfigurations
= new ArrayList
<RunnerAndConfigurationSettingsImpl
>();
115 RunnerAndConfigurationSettingsImpl snapshotConfiguration
= null;
116 boolean connected
= false;
118 ApplicationConfigurationType cfgType
= ApplicationConfigurationType
.getInstance();
119 RunnerAndConfigurationSettingsImpl
[] racsi
= RunManagerEx
.getInstanceEx(project
).getConfigurationSettings(cfgType
);
121 for(RunnerAndConfigurationSettingsImpl config
: racsi
) {
122 if (config
.getConfiguration() instanceof ApplicationConfiguration
) {
123 ApplicationConfiguration appConfig
= (ApplicationConfiguration
) config
.getConfiguration();
124 appConfigurations
.add(config
);
125 if (appConfig
.ENABLE_SWING_INSPECTOR
) {
126 SnapShooterConfigurationSettings settings
= SnapShooterConfigurationSettings
.get(appConfig
);
127 snapshotConfiguration
= config
;
128 if (settings
.getLastPort() > 0) {
130 client
.connect(settings
.getLastPort());
133 catch(IOException ex
) {
138 if (connected
) break;
142 if (snapshotConfiguration
== null) {
143 snapshotConfiguration
= promptForSnapshotConfiguration(project
, appConfigurations
);
144 if (snapshotConfiguration
== null) return;
148 int rc
= Messages
.showYesNoDialog(project
, UIDesignerBundle
.message("snapshot.run.prompt"),
149 UIDesignerBundle
.message("snapshot.title"), Messages
.getQuestionIcon());
151 final ApplicationConfiguration appConfig
= (ApplicationConfiguration
) snapshotConfiguration
.getConfiguration();
152 final SnapShooterConfigurationSettings settings
= SnapShooterConfigurationSettings
.get(appConfig
);
153 settings
.setNotifyRunnable(new Runnable() {
155 SwingUtilities
.invokeLater(new Runnable() {
157 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.prepare.notice"),
158 UIDesignerBundle
.message("snapshot.title"), Messages
.getInformationIcon());
160 client
.connect(settings
.getLastPort());
162 catch(IOException ex
) {
163 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.connection.error"),
164 UIDesignerBundle
.message("snapshot.title"), Messages
.getErrorIcon());
167 runSnapShooterSession(client
, project
, dir
, view
);
174 final ProgramRunner runner
= RunnerRegistry
.getInstance().getRunner(DefaultRunExecutor
.EXECUTOR_ID
, appConfig
);
175 LOG
.assertTrue(runner
!= null, "Runner MUST not be null!");
176 runner
.execute(DefaultRunExecutor
.getRunExecutorInstance(),
177 new ExecutionEnvironment(runner
, snapshotConfiguration
, e
.getDataContext()));
179 catch (ExecutionException ex
) {
180 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.run.error", ex
.getMessage()),
181 UIDesignerBundle
.message("snapshot.title"), Messages
.getErrorIcon());
185 runSnapShooterSession(client
, project
, dir
, view
);
189 private static void runSnapShooterSession(final SnapShotClient client
, final Project project
, final PsiDirectory dir
, final IdeView view
) {
191 client
.suspendSwing();
193 catch (IOException e1
) {
194 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.connection.error"),
195 UIDesignerBundle
.message("snapshot.title"), Messages
.getInformationIcon());
199 final MyDialog dlg
= new MyDialog(project
, client
, dir
);
201 if (dlg
.getExitCode() == DialogWrapper
.OK_EXIT_CODE
) {
202 final int id
= dlg
.getSelectedComponentId();
203 final Ref
<Object
> result
= new Ref
<Object
>();
204 ProgressManager
.getInstance().runProcessWithProgressSynchronously(new Runnable() {
207 result
.set(client
.createSnapshot(id
));
209 catch (Exception ex
) {
213 }, UIDesignerBundle
.message("progress.creating.snapshot"), false, project
);
215 String snapshot
= null;
216 if (result
.get() instanceof String
) {
217 snapshot
= (String
) result
.get();
220 Exception ex
= (Exception
) result
.get();
221 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.create.error", ex
.getMessage()),
222 UIDesignerBundle
.message("snapshot.title"), Messages
.getErrorIcon());
225 if (snapshot
!= null) {
226 final String snapshot1
= snapshot
;
227 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
229 CommandProcessor
.getInstance().executeCommand(project
, new Runnable() {
232 PsiFile formFile
= PsiFileFactory
.getInstance(dir
.getProject())
233 .createFileFromText(dlg
.getFormName() + GuiFormFileType
.DOT_DEFAULT_EXTENSION
, snapshot1
);
234 formFile
= (PsiFile
)dir
.add(formFile
);
235 formFile
.getVirtualFile().setCharset(CharsetToolkit
.UTF8_CHARSET
);
236 formFile
.getViewProvider().getDocument().setText(snapshot1
);
237 view
.selectElement(formFile
);
239 catch (IncorrectOperationException ex
) {
240 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.save.error", ex
.getMessage()),
241 UIDesignerBundle
.message("snapshot.title"), Messages
.getErrorIcon());
251 client
.resumeSwing();
253 catch (IOException ex
) {
254 Messages
.showErrorDialog(project
, UIDesignerBundle
.message("snapshot.connection.broken"),
255 UIDesignerBundle
.message("snapshot.title"));
262 private static RunnerAndConfigurationSettingsImpl
promptForSnapshotConfiguration(final Project project
,
263 final List
<RunnerAndConfigurationSettingsImpl
> configurations
) {
264 if (configurations
.isEmpty()) {
265 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.no.configuration.error"),
266 UIDesignerBundle
.message("snapshot.title"), Messages
.getInformationIcon());
270 for(int i
=configurations
.size()-1; i
>= 0; i
--) {
271 if (!new JreVersionDetector().isJre50Configured((ApplicationConfiguration
) configurations
.get(i
).getConfiguration())) {
272 configurations
.remove(i
);
276 if (configurations
.isEmpty()) {
277 Messages
.showMessageDialog(project
, UIDesignerBundle
.message("snapshot.no.compatible.configuration.error"),
278 UIDesignerBundle
.message("snapshot.title"), Messages
.getInformationIcon());
282 final RunnerAndConfigurationSettingsImpl snapshotConfiguration
;
283 if (configurations
.size() == 1) {
284 final int rc
= Messages
.showYesNoDialog(
286 UIDesignerBundle
.message("snapshot.confirm.configuration.prompt", configurations
.get(0).getConfiguration().getName()),
287 UIDesignerBundle
.message("snapshot.title"),
288 Messages
.getQuestionIcon());
292 snapshotConfiguration
= configurations
.get(0);
295 String
[] names
= new String
[configurations
.size()];
296 for(int i
=0; i
<configurations
.size(); i
++) {
297 names
[i
] = configurations
.get(i
).getConfiguration().getName();
299 int rc
= Messages
.showChooseDialog(
301 UIDesignerBundle
.message("snapshot.choose.configuration.prompt"),
302 UIDesignerBundle
.message("snapshot.title"),
303 Messages
.getQuestionIcon(),
307 if (rc
< 0) return null;
308 snapshotConfiguration
= configurations
.get(rc
);
310 ((ApplicationConfiguration
) snapshotConfiguration
.getConfiguration()).ENABLE_SWING_INSPECTOR
= true;
311 return snapshotConfiguration
;
314 private static class MyDialog
extends DialogWrapper
{
315 private JPanel myRootPanel
;
316 private JTree myComponentTree
;
317 private JTextField myFormNameTextField
;
318 private JLabel myErrorLabel
;
319 private final Project myProject
;
320 private final SnapShotClient myClient
;
321 private final PsiDirectory myDirectory
;
322 @NonNls private static final String SWING_PACKAGE
= "javax.swing.";
324 private final Icon myUnknownComponentIcon
= IconLoader
.getIcon("/com/intellij/uiDesigner/icons/unknown.png");
325 private final Icon myFormIcon
= IconLoader
.getIcon("/fileTypes/uiForm.png");
327 private MyDialog(Project project
, final SnapShotClient client
, final PsiDirectory dir
) {
328 super(project
, true);
333 setTitle(UIDesignerBundle
.message("snapshot.title"));
334 final SnapShotTreeModel model
= new SnapShotTreeModel(client
);
335 myComponentTree
.setModel(model
);
336 myComponentTree
.getSelectionModel().setSelectionMode(TreeSelectionModel
.SINGLE_TREE_SELECTION
);
337 myComponentTree
.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
338 public void valueChanged(TreeSelectionEvent e
) {
342 for(int i
=0; i
<2; i
++) {
343 for(int row
=myComponentTree
.getRowCount()-1; row
>= 0; row
--) {
344 myComponentTree
.expandRow(row
);
347 myComponentTree
.getSelectionModel().setSelectionPath(myComponentTree
.getPathForRow(0));
348 myFormNameTextField
.setText(suggestFormName());
350 final EditorColorsScheme globalScheme
= EditorColorsManager
.getInstance().getGlobalScheme();
351 final TextAttributes attributes
= globalScheme
.getAttributes(SyntaxHighlighterColors
.STRING
);
352 final SimpleTextAttributes titleAttributes
=
353 new SimpleTextAttributes(SimpleTextAttributes
.STYLE_PLAIN
, attributes
.getForegroundColor());
355 myComponentTree
.setCellRenderer(new ColoredTreeCellRenderer() {
356 public void customizeCellRenderer(JTree tree
, Object value
, boolean selected
, boolean expanded
, boolean leaf
, int row
, boolean hasFocus
) {
357 SnapShotRemoteComponent rc
= (SnapShotRemoteComponent
) value
;
359 String className
= rc
.getClassName();
360 if (className
.startsWith(SWING_PACKAGE
)) {
361 append(className
.substring(SWING_PACKAGE
.length()), SimpleTextAttributes
.REGULAR_ATTRIBUTES
);
364 append(className
, SimpleTextAttributes
.REGULAR_ATTRIBUTES
);
367 if (rc
.getText().length() > 0) {
368 append(" \"" + rc
.getText() + "\"", titleAttributes
);
370 if (rc
.getLayoutManager().length() > 0) {
371 append(" (" + rc
.getLayoutManager() + ")", SimpleTextAttributes
.GRAY_ATTRIBUTES
);
374 if (rc
.isTopLevel()) {
378 final Palette palette
= Palette
.getInstance(myProject
);
379 final ComponentItem item
= palette
.getItem(rc
.getClassName());
381 setIcon(item
.getSmallIcon());
384 setIcon(myUnknownComponentIcon
);
389 myFormNameTextField
.getDocument().addDocumentListener(new DocumentAdapter() {
390 protected void textChanged(DocumentEvent e
) {
398 private String
suggestFormName() {
403 while(myDirectory
.findFile("Form" + count
+ GuiFormFileType
.DOT_DEFAULT_EXTENSION
) != null);
404 return "Form" + count
;
407 private void updateOKAction() {
408 final boolean selectedComponentValid
= isSelectedComponentValid();
409 setOKActionEnabled(isFormNameValid() && selectedComponentValid
);
410 if (myComponentTree
.getSelectionPath() != null && !selectedComponentValid
) {
411 myErrorLabel
.setText(UIDesignerBundle
.message("snapshooter.invalid.container"));
414 myErrorLabel
.setText(" ");
418 private boolean isSelectedComponentValid() {
419 final TreePath selectionPath
= myComponentTree
.getSelectionPath();
420 if (selectionPath
== null) return false;
421 SnapShotRemoteComponent rc
= (SnapShotRemoteComponent
) selectionPath
.getLastPathComponent();
422 if (isValidComponent(rc
)) return true;
423 if (selectionPath
.getPathCount() == 2) {
424 // capture frame/dialog root pane when a frame or dialog itself is selected
425 final SnapShotRemoteComponent
[] children
= rc
.getChildren();
426 return children
!= null && children
.length
> 0 && isValidComponent(children
[0]);
431 private boolean isValidComponent(final SnapShotRemoteComponent rc
) {
432 PsiClass componentClass
=
433 JavaPsiFacade
.getInstance(myProject
).findClass(rc
.getClassName().replace('$', '.'), GlobalSearchScope
.allScope(myProject
));
434 while(componentClass
!= null) {
435 if (JPanel
.class.getName().equals(componentClass
.getQualifiedName()) ||
436 JTabbedPane
.class.getName().equals(componentClass
.getQualifiedName()) ||
437 JScrollPane
.class.getName().equals(componentClass
.getQualifiedName()) ||
438 JSplitPane
.class.getName().equals(componentClass
.getQualifiedName())) {
441 componentClass
= componentClass
.getSuperClass();
447 private boolean isFormNameValid() {
448 return myFormNameTextField
.getText().length() > 0;
452 protected String
getDimensionServiceKey() {
453 return "CreateSnapShotAction.MyDialog";
457 public JComponent
getPreferredFocusedComponent() {
458 return myFormNameTextField
;
462 protected Action
getOKAction() {
463 final Action okAction
= super.getOKAction();
464 okAction
.putValue(Action
.NAME
, UIDesignerBundle
.message("create.snapshot.button"));
469 protected void doOKAction() {
470 if (getOKAction().isEnabled()) {
472 myDirectory
.checkCreateFile(getFormName() + GuiFormFileType
.DOT_DEFAULT_EXTENSION
);
474 catch (IncorrectOperationException e
) {
475 JOptionPane
.showMessageDialog(myRootPanel
, UIDesignerBundle
.message("error.form.already.exists", getFormName()));
478 if (!checkUnknownLayoutManagers(myDirectory
.getProject())) return;
483 private boolean checkUnknownLayoutManagers(final Project project
) {
484 final Set
<String
> layoutManagerClasses
= new TreeSet
<String
>();
485 final SnapShotRemoteComponent rc
= (SnapShotRemoteComponent
) myComponentTree
.getSelectionPath().getLastPathComponent();
487 final Ref
<Exception
> err
= new Ref
<Exception
>();
488 Runnable runnable
= new Runnable() {
491 collectUnknownLayoutManagerClasses(project
, rc
, layoutManagerClasses
);
493 catch (IOException e
) {
498 if (!ProgressManager
.getInstance().runProcessWithProgressSynchronously(runnable
,
499 UIDesignerBundle
.message("progress.validating.layout.managers"),
504 Messages
.showErrorDialog(myRootPanel
, UIDesignerBundle
.message("snapshot.connection.broken"), UIDesignerBundle
.message("snapshot.title"));
507 if (!layoutManagerClasses
.isEmpty()) {
508 StringBuilder builder
= new StringBuilder(UIDesignerBundle
.message("snapshot.unknown.layout.prefix"));
509 for(String layoutManagerClass
: layoutManagerClasses
) {
510 builder
.append(layoutManagerClass
).append("\n");
512 builder
.append(UIDesignerBundle
.message("snapshot.unknown.layout.prompt"));
513 return Messages
.showYesNoDialog(myProject
, builder
.toString(),
514 UIDesignerBundle
.message("snapshot.title"), Messages
.getQuestionIcon()) == 0;
519 private void collectUnknownLayoutManagerClasses(final Project project
, final SnapShotRemoteComponent rc
,
520 final Set
<String
> layoutManagerClasses
) throws IOException
{
521 RadComponentFactory factory
= InsertComponentProcessor
.getRadComponentFactory(project
, rc
.getClassName());
522 if (factory
instanceof RadContainer
.Factory
&& rc
.getLayoutManager().length() > 0 &&
523 !LayoutManagerRegistry
.isKnownLayoutClass(rc
.getLayoutManager())) {
524 layoutManagerClasses
.add(rc
.getLayoutManager());
527 SnapShotRemoteComponent
[] children
= rc
.getChildren();
528 if (children
== null) {
529 children
= myClient
.listChildren(rc
.getId());
530 rc
.setChildren(children
);
532 for(SnapShotRemoteComponent child
: children
) {
533 collectUnknownLayoutManagerClasses(project
, child
, layoutManagerClasses
);
538 protected JComponent
createCenterPanel() {
542 public int getSelectedComponentId() {
543 final TreePath selectionPath
= myComponentTree
.getSelectionPath();
544 SnapShotRemoteComponent rc
= (SnapShotRemoteComponent
) selectionPath
.getLastPathComponent();
545 if (!isValidComponent(rc
) && selectionPath
.getPathCount() == 2) {
546 // capture frame/dialog root pane when a frame or dialog itself is selected
547 final SnapShotRemoteComponent
[] children
= rc
.getChildren();
548 if (children
!= null && children
.length
> 0 && isValidComponent(children
[0])) {
549 return children
[0].getId();
555 public String
getFormName() {
556 return myFormNameTextField
.getText();