| /* |
| * 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.designSurface; |
| |
| import com.intellij.CommonBundle; |
| import com.intellij.codeInsight.FileModificationService; |
| import com.intellij.ide.palette.impl.PaletteToolWindowManager; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.*; |
| import com.intellij.openapi.roots.libraries.Library; |
| import com.intellij.openapi.roots.libraries.LibraryTable; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.JavaPsiFacade; |
| import com.intellij.psi.PsiClass; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiManager; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.uiDesigner.*; |
| import com.intellij.uiDesigner.compiler.Utils; |
| import com.intellij.uiDesigner.core.Util; |
| import com.intellij.uiDesigner.make.PsiNestedFormLoader; |
| import com.intellij.uiDesigner.palette.ComponentItem; |
| import com.intellij.uiDesigner.palette.ComponentItemDialog; |
| import com.intellij.uiDesigner.palette.Palette; |
| import com.intellij.uiDesigner.quickFixes.CreateFieldFix; |
| import com.intellij.uiDesigner.radComponents.*; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.MouseEvent; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author Anton Katilin |
| * @author Vladimir Kondratyev |
| */ |
| public final class InsertComponentProcessor extends EventProcessor { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.designSurface.InsertComponentProcessor"); |
| |
| private final GuiEditor myEditor; |
| private boolean mySticky; |
| private RadComponent myInsertedComponent; |
| private final GridInsertProcessor myGridInsertProcessor; |
| private ComponentItem myComponentToInsert; |
| private ComponentDropLocation myLastLocation; |
| |
| private static final Map<String, RadComponentFactory> myComponentClassMap = new HashMap<String, RadComponentFactory>(); |
| |
| static { |
| myComponentClassMap.put(JScrollPane.class.getName(), new RadScrollPane.Factory()); |
| myComponentClassMap.put(JPanel.class.getName(), new RadContainer.Factory()); |
| myComponentClassMap.put(VSpacer.class.getName(), new RadVSpacer.Factory()); |
| myComponentClassMap.put(HSpacer.class.getName(), new RadHSpacer.Factory()); |
| myComponentClassMap.put(JTabbedPane.class.getName(), new RadTabbedPane.Factory()); |
| myComponentClassMap.put(JSplitPane.class.getName(), new RadSplitPane.Factory()); |
| myComponentClassMap.put(JToolBar.class.getName(), new RadToolBar.Factory()); |
| myComponentClassMap.put(JTable.class.getName(), new RadTable.Factory()); |
| } |
| |
| public InsertComponentProcessor(@NotNull final GuiEditor editor) { |
| myEditor = editor; |
| myGridInsertProcessor = new GridInsertProcessor(editor); |
| } |
| |
| public void setSticky(final boolean sticky) { |
| mySticky = sticky; |
| } |
| |
| public void setComponentToInsert(final ComponentItem componentToInsert) { |
| myComponentToInsert = componentToInsert; |
| } |
| |
| public void setLastLocation(final ComponentDropLocation location) { |
| final ComponentItem componentToInsert = getComponentToInsert(); |
| assert componentToInsert != null; |
| ComponentItemDragObject dragObject = new ComponentItemDragObject(componentToInsert); |
| if (location.canDrop(dragObject)) { |
| myLastLocation = location; |
| } |
| else { |
| ComponentDropLocation locationToRight = location.getAdjacentLocation(ComponentDropLocation.Direction.RIGHT); |
| ComponentDropLocation locationToBottom = location.getAdjacentLocation(ComponentDropLocation.Direction.DOWN); |
| if (locationToRight != null && locationToRight.canDrop(dragObject)) { |
| myLastLocation = locationToRight; |
| } |
| else if (locationToBottom != null && locationToBottom.canDrop(dragObject)) { |
| myLastLocation = locationToBottom; |
| } |
| else { |
| myLastLocation = location; |
| } |
| } |
| |
| if (myLastLocation.canDrop(dragObject)) { |
| myLastLocation.placeFeedback(myEditor.getActiveDecorationLayer(), dragObject); |
| } |
| } |
| |
| protected void processKeyEvent(final KeyEvent e) { |
| if (e.getID() == KeyEvent.KEY_PRESSED) { |
| if (e.getKeyCode() == KeyEvent.VK_ENTER) { |
| if (myLastLocation != null) { |
| myEditor.getMainProcessor().stopCurrentProcessor(); |
| processComponentInsert(getComponentToInsert(), myLastLocation); |
| } |
| } |
| else { |
| ComponentItem componentToInsert = getComponentToInsert(); |
| if (componentToInsert == null) { |
| cancelOperation(); |
| } |
| else { |
| myLastLocation = moveDropLocation(myEditor, myLastLocation, new ComponentItemDragObject(componentToInsert), e); |
| } |
| } |
| } |
| } |
| |
| @NotNull |
| public static String suggestBinding(final RadRootContainer rootContainer, @NotNull final String componentClassName) { |
| String shortClassName = getShortClassName(componentClassName); |
| |
| LOG.assertTrue(shortClassName.length() > 0); |
| |
| return getUniqueBinding(rootContainer, shortClassName); |
| } |
| |
| public static String getShortClassName(@NonNls final String componentClassName) { |
| final int lastDotIndex = componentClassName.lastIndexOf('.'); |
| String shortClassName = componentClassName.substring(lastDotIndex + 1); |
| |
| // Here is euristic. Chop first 'J' letter for standard Swing classes. |
| // Without 'J' bindings look better. |
| if ( |
| shortClassName.length() > 1 && Character.isUpperCase(shortClassName.charAt(1)) && |
| componentClassName.startsWith("javax.swing.") && |
| StringUtil.startsWithChar(shortClassName, 'J') |
| ) { |
| shortClassName = shortClassName.substring(1); |
| } |
| shortClassName = StringUtil.decapitalize(shortClassName); |
| return shortClassName; |
| } |
| |
| public static String getUniqueBinding(RadRootContainer root, final String baseName) { |
| // Generate member name based on current code style |
| //noinspection ForLoopThatDoesntUseLoopVariable |
| for (int i = 0; true; i++) { |
| final String nameCandidate = baseName + (i + 1); |
| final String binding = JavaCodeStyleManager.getInstance(root.getProject()).propertyNameToVariableName( |
| nameCandidate, |
| VariableKind.FIELD |
| ); |
| |
| if (FormEditingUtil.findComponentWithBinding(root, binding) == null) { |
| return binding; |
| } |
| } |
| } |
| |
| /** |
| * Tries to create binding for {@link #myInsertedComponent} |
| * |
| * @param editor |
| * @param insertedComponent |
| * @param forceBinding |
| */ |
| public static void createBindingWhenDrop(final GuiEditor editor, final RadComponent insertedComponent, final boolean forceBinding) { |
| final ComponentItem item = Palette.getInstance(editor.getProject()).getItem(insertedComponent.getComponentClassName()); |
| if ((item != null && item.isAutoCreateBinding()) || insertedComponent.isCustomCreateRequired() || forceBinding) { |
| doCreateBindingWhenDrop(editor, insertedComponent); |
| } |
| } |
| |
| private static void doCreateBindingWhenDrop(final GuiEditor editor, final RadComponent insertedComponent) { |
| // Now if the inserted component is a input control, we need to automatically create binding |
| final String binding = suggestBinding(editor.getRootContainer(), insertedComponent.getComponentClassName()); |
| insertedComponent.setBinding(binding); |
| insertedComponent.setDefaultBinding(true); |
| |
| createBindingField(editor, insertedComponent); |
| } |
| |
| public static void createBindingField(final GuiEditor editor, final RadComponent insertedComponent) { |
| // Try to create field in the corresponding bound class |
| final String classToBind = editor.getRootContainer().getClassToBind(); |
| if (classToBind != null) { |
| final PsiClass aClass = FormEditingUtil.findClassToBind(editor.getModule(), classToBind); |
| if (aClass != null && aClass.findFieldByName(insertedComponent.getBinding(), true) == null) { |
| if (!FileModificationService.getInstance().preparePsiElementForWrite(aClass)) { |
| return; |
| } |
| ApplicationManager.getApplication().runWriteAction( |
| new Runnable() { |
| public void run() { |
| CreateFieldFix.runImpl(editor.getProject(), |
| editor.getRootContainer(), |
| aClass, |
| insertedComponent.getComponentClassName(), |
| insertedComponent.getBinding(), |
| false, // silently skip all errors (if any) |
| null); |
| } |
| } |
| ); |
| } |
| } |
| } |
| |
| protected void processMouseEvent(final MouseEvent e) { |
| if (e.getID() == MouseEvent.MOUSE_PRESSED) { |
| final ComponentItem componentItem = getComponentToInsert(); |
| if (componentItem != null) { |
| processComponentInsert(e.getPoint(), componentItem); |
| } |
| } |
| else if (e.getID() == MouseEvent.MOUSE_MOVED) { |
| final ComponentItem componentToInsert = getComponentToInsert(); |
| if (componentToInsert != null) { |
| ComponentItemDragObject dragObject = new ComponentItemDragObject(componentToInsert); |
| myLastLocation = myGridInsertProcessor.processDragEvent(e.getPoint(), dragObject); |
| if (myLastLocation.canDrop(dragObject)) { |
| setCursor(FormEditingUtil.getCopyDropCursor()); |
| } |
| else { |
| setCursor(FormEditingUtil.getMoveNoDropCursor()); |
| } |
| } |
| } |
| } |
| |
| @Nullable |
| private ComponentItem getComponentToInsert() { |
| return (myComponentToInsert != null) |
| ? myComponentToInsert |
| : PaletteToolWindowManager.getInstance(myEditor).getActiveItem(ComponentItem.class); |
| } |
| |
| public void processComponentInsert(@NotNull final Point point, final ComponentItem item) { |
| final ComponentDropLocation location = GridInsertProcessor.getDropLocation(myEditor.getRootContainer(), point); |
| processComponentInsert(item, location); |
| } |
| |
| public void processComponentInsert(ComponentItem item, final ComponentDropLocation location) { |
| myEditor.getActiveDecorationLayer().removeFeedback(); |
| myEditor.setDesignTimeInsets(2); |
| |
| item = replaceAnyComponentItem(myEditor, item, UIDesignerBundle.message("palette.non.palette.component.title")); |
| if (item == null) { |
| return; |
| } |
| |
| if (!validateNestedFormInsert(item)) { |
| return; |
| } |
| |
| if (!checkAddDependencyOnInsert(item)) { |
| return; |
| } |
| |
| if (!myEditor.ensureEditable()) { |
| return; |
| } |
| |
| final boolean forceBinding = item.isAutoCreateBinding(); |
| myInsertedComponent = createInsertedComponent(myEditor, item); |
| setCursor(Cursor.getDefaultCursor()); |
| if (myInsertedComponent == null) { |
| if (!mySticky) { |
| PaletteToolWindowManager.getInstance(myEditor).clearActiveItem(); |
| } |
| return; |
| } |
| |
| final ComponentItemDragObject dragObject = new ComponentItemDragObject(item); |
| if (location.canDrop(dragObject)) { |
| CommandProcessor.getInstance().executeCommand( |
| myEditor.getProject(), |
| new Runnable() { |
| public void run() { |
| createBindingWhenDrop(myEditor, myInsertedComponent, forceBinding); |
| |
| final RadComponent[] components = new RadComponent[]{myInsertedComponent}; |
| location.processDrop(myEditor, components, null, dragObject); |
| |
| FormEditingUtil.selectSingleComponent(myEditor, myInsertedComponent); |
| |
| if (location.getContainer() != null && location.getContainer().isXY()) { |
| Dimension newSize = myInsertedComponent.getPreferredSize(); |
| Util.adjustSize(myInsertedComponent.getDelegee(), myInsertedComponent.getConstraints(), newSize); |
| myInsertedComponent.setSize(newSize); |
| } |
| |
| if (myInsertedComponent.getParent() instanceof RadRootContainer && |
| myInsertedComponent instanceof RadAtomicComponent) { |
| GridBuildUtil.convertToGrid(myEditor); |
| FormEditingUtil.selectSingleComponent(myEditor, myInsertedComponent); |
| } |
| |
| checkBindTopLevelPanel(); |
| |
| if (!mySticky) { |
| PaletteToolWindowManager.getInstance(myEditor).clearActiveItem(); |
| } |
| |
| myEditor.refreshAndSave(false); |
| } |
| }, UIDesignerBundle.message("command.insert.component"), null); |
| } |
| myComponentToInsert = null; |
| } |
| |
| private boolean checkAddDependencyOnInsert(final ComponentItem item) { |
| if (item.getClassName().equals(HSpacer.class.getName()) || item.getClassName().equals(VSpacer.class.getName())) { |
| // this is mostly required for IDEA developers, so that developers don't receive prompt to offer ui-designer-impl dependency |
| return true; |
| } |
| PsiManager manager = PsiManager.getInstance(myEditor.getProject()); |
| final GlobalSearchScope projectScope = GlobalSearchScope.allScope(myEditor.getProject()); |
| final GlobalSearchScope moduleScope = GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(myEditor.getModule()); |
| final PsiClass componentClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(item.getClassName(), projectScope); |
| if (componentClass != null && JavaPsiFacade.getInstance(manager.getProject()).findClass(item.getClassName(), moduleScope) == null) { |
| final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myEditor.getProject()).getFileIndex(); |
| List<OrderEntry> entries = fileIndex.getOrderEntriesForFile(componentClass.getContainingFile().getVirtualFile()); |
| if (entries.size() > 0) { |
| if (entries.get(0) instanceof ModuleSourceOrderEntry) { |
| if (!checkAddModuleDependency(item, (ModuleSourceOrderEntry)entries.get(0))) return false; |
| } |
| else if (entries.get(0) instanceof LibraryOrderEntry) { |
| if (!checkAddLibraryDependency(item, (LibraryOrderEntry)entries.get(0))) return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private boolean checkAddModuleDependency(final ComponentItem item, final ModuleSourceOrderEntry moduleSourceOrderEntry) { |
| final Module ownerModule = moduleSourceOrderEntry.getOwnerModule(); |
| int rc = Messages.showYesNoCancelDialog( |
| myEditor, |
| UIDesignerBundle.message("add.module.dependency.prompt", item.getClassName(), ownerModule.getName(), myEditor.getModule().getName()), |
| UIDesignerBundle.message("add.module.dependency.title"), |
| Messages.getQuestionIcon()); |
| if (rc == Messages.CANCEL) return false; |
| if (rc == Messages.YES) { |
| ModuleRootModificationUtil.addDependency(myEditor.getModule(), ownerModule); |
| } |
| return true; |
| } |
| |
| private boolean checkAddLibraryDependency(final ComponentItem item, final LibraryOrderEntry libraryOrderEntry) { |
| int rc = Messages.showYesNoCancelDialog( |
| myEditor, |
| UIDesignerBundle.message("add.library.dependency.prompt", item.getClassName(), libraryOrderEntry.getPresentableName(), |
| myEditor.getModule().getName()), |
| UIDesignerBundle.message("add.library.dependency.title"), |
| Messages.getQuestionIcon()); |
| if (rc == Messages.CANCEL) return false; |
| if (rc == Messages.YES) { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| public void run() { |
| final ModifiableRootModel model = ModuleRootManager.getInstance(myEditor.getModule()).getModifiableModel(); |
| if (libraryOrderEntry.isModuleLevel()) { |
| copyModuleLevelLibrary(libraryOrderEntry.getLibrary(), model); |
| } |
| else { |
| model.addLibraryEntry(libraryOrderEntry.getLibrary()); |
| } |
| model.commit(); |
| } |
| }); |
| } |
| return true; |
| } |
| |
| private static void copyModuleLevelLibrary(final Library fromLibrary, final ModifiableRootModel toModel) { |
| final LibraryTable.ModifiableModel libraryTableModel = toModel.getModuleLibraryTable().getModifiableModel(); |
| Library library = libraryTableModel.createLibrary(null); |
| final Library.ModifiableModel libraryModel = library.getModifiableModel(); |
| for (OrderRootType rootType : OrderRootType.getAllTypes()) { |
| for (String url : fromLibrary.getUrls(rootType)) { |
| libraryModel.addRoot(url, rootType); |
| } |
| } |
| libraryModel.commit(); |
| libraryTableModel.commit(); |
| } |
| |
| private boolean validateNestedFormInsert(final ComponentItem item) { |
| PsiFile boundForm = item.getBoundForm(); |
| if (boundForm != null) { |
| try { |
| final String formName = FormEditingUtil.buildResourceName(boundForm); |
| final String targetForm = FormEditingUtil.buildResourceName(myEditor.getPsiFile()); |
| Utils.validateNestedFormLoop(formName, new PsiNestedFormLoader(myEditor.getModule()), targetForm); |
| } |
| catch (Exception ex) { |
| Messages.showErrorDialog(myEditor, ex.getMessage(), CommonBundle.getErrorTitle()); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public static RadContainer createPanelComponent(GuiEditor editor) { |
| RadComponent c = createInsertedComponent(editor, Palette.getInstance(editor.getProject()).getPanelItem()); |
| LOG.assertTrue(c != null); |
| return (RadContainer)c; |
| } |
| |
| @Nullable |
| public static ComponentItem replaceAnyComponentItem(GuiEditor editor, ComponentItem item, final String title) { |
| if (item.isAnyComponent()) { |
| ComponentItem newItem = item.clone(); |
| ComponentItemDialog dlg = new ComponentItemDialog(editor.getProject(), editor, newItem, true); |
| dlg.setTitle(title); |
| dlg.show(); |
| if (!dlg.isOK()) { |
| return null; |
| } |
| |
| return newItem; |
| } |
| return item; |
| } |
| |
| @Nullable |
| public static RadComponent createInsertedComponent(GuiEditor editor, ComponentItem item) { |
| RadComponent result; |
| final String id = FormEditingUtil.generateId(editor.getRootContainer()); |
| |
| final ClassLoader loader = LoaderFactory.getInstance(editor.getProject()).getLoader(editor.getFile()); |
| RadComponentFactory factory = getRadComponentFactory(item.getClassName(), loader); |
| if (factory != null) { |
| try { |
| result = factory.newInstance(editor, item.getClassName(), id); |
| } |
| catch (Exception e) { |
| LOG.error(e); |
| return null; |
| } |
| } |
| else { |
| PsiFile boundForm = item.getBoundForm(); |
| if (boundForm != null) { |
| final String formFileName = FormEditingUtil.buildResourceName(boundForm); |
| try { |
| result = new RadNestedForm(editor, formFileName, id); |
| } |
| catch (Exception ex) { |
| String errorMessage = UIDesignerBundle.message("error.instantiating.nested.form", formFileName, |
| (ex.getMessage() != null ? ex.getMessage() : ex.toString())); |
| result = RadErrorComponent.create( |
| editor, |
| id, |
| item.getClassName(), |
| null, |
| errorMessage |
| ); |
| } |
| } |
| else { |
| try { |
| final Class aClass = Class.forName(item.getClassName(), true, loader); |
| if (item.isContainer()) { |
| LOG.debug("Creating custom container instance"); |
| result = new RadContainer(editor, aClass, id); |
| } |
| else { |
| result = new RadAtomicComponent(editor, aClass, id); |
| } |
| } |
| catch (final UnsupportedClassVersionError ucve) { |
| result = RadErrorComponent.create(editor, id, item.getClassName(), null, |
| UIDesignerBundle.message("unsupported.component.class.version") |
| ); |
| } |
| catch (final Exception exc) { |
| //noinspection NonConstantStringShouldBeStringBuffer |
| String errorDescription = Utils.validateJComponentClass(loader, item.getClassName(), true); |
| if (errorDescription == null) { |
| errorDescription = UIDesignerBundle.message("error.class.cannot.be.instantiated", item.getClassName()); |
| final String message = FormEditingUtil.getExceptionMessage(exc); |
| if (message != null) { |
| errorDescription += ": " + message; |
| } |
| } |
| result = RadErrorComponent.create( |
| editor, |
| id, |
| item.getClassName(), |
| null, |
| errorDescription |
| ); |
| } |
| } |
| } |
| result.init(editor, item); |
| return result; |
| } |
| |
| @Nullable |
| public static RadComponentFactory getRadComponentFactory(Project project, final String className) { |
| ClassLoader loader = LoaderFactory.getInstance(project).getProjectClassLoader(); |
| return getRadComponentFactory(className, loader); |
| } |
| |
| @Nullable |
| private static RadComponentFactory getRadComponentFactory(final String className, final ClassLoader loader) { |
| Class componentClass; |
| try { |
| componentClass = Class.forName(className, false, loader); |
| } |
| catch (ClassNotFoundException e) { |
| return myComponentClassMap.get(className); |
| } |
| return getRadComponentFactory(componentClass); |
| } |
| |
| @Nullable |
| public static RadComponentFactory getRadComponentFactory(Class componentClass) { |
| while (componentClass != null) { |
| RadComponentFactory c = myComponentClassMap.get(componentClass.getName()); |
| if (c != null) return c; |
| componentClass = componentClass.getSuperclass(); |
| // if a component item is a JPanel subclass, a RadContainer should be created for it only |
| // if it's marked as "Is Container" |
| if (JPanel.class.equals(componentClass)) return null; |
| } |
| return null; |
| } |
| |
| private void checkBindTopLevelPanel() { |
| if (myEditor.getRootContainer().getComponentCount() == 1) { |
| final RadComponent component = myEditor.getRootContainer().getComponent(0); |
| if (component.getBinding() == null) { |
| if (component == myInsertedComponent || |
| (component instanceof RadContainer && ((RadContainer)component).getComponentCount() == 1 && |
| component == myInsertedComponent.getParent())) { |
| doCreateBindingWhenDrop(myEditor, component); |
| } |
| } |
| } |
| } |
| |
| protected boolean cancelOperation() { |
| myEditor.setDesignTimeInsets(2); |
| myEditor.getActiveDecorationLayer().removeFeedback(); |
| return true; |
| } |
| |
| public Cursor processMouseMoveEvent(final MouseEvent e) { |
| final ComponentItem componentItem = PaletteToolWindowManager.getInstance(myEditor).getActiveItem(ComponentItem.class); |
| if (componentItem != null) { |
| return myGridInsertProcessor.processMouseMoveEvent(e.getPoint(), false, new ComponentItemDragObject(componentItem)); |
| } |
| return FormEditingUtil.getMoveNoDropCursor(); |
| } |
| |
| @Override |
| public boolean needMousePressed() { |
| return true; |
| } |
| } |