| /* |
| * Copyright 2000-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 com.intellij.designer.designSurface; |
| |
| import com.intellij.designer.*; |
| import com.intellij.designer.actions.AbstractComboBoxAction; |
| import com.intellij.designer.actions.CommonEditActionsProvider; |
| import com.intellij.designer.actions.DesignerActionPanel; |
| import com.intellij.designer.componentTree.TreeComponentDecorator; |
| import com.intellij.designer.componentTree.TreeEditableArea; |
| import com.intellij.designer.designSurface.tools.*; |
| import com.intellij.designer.model.*; |
| import com.intellij.designer.palette.PaletteGroup; |
| import com.intellij.designer.palette.PaletteItem; |
| import com.intellij.designer.palette.PaletteToolWindowManager; |
| import com.intellij.designer.propertyTable.InplaceContext; |
| import com.intellij.designer.propertyTable.PropertyTableTab; |
| import com.intellij.designer.propertyTable.TablePanelActionPolicy; |
| import com.intellij.diagnostic.AttachmentFactory; |
| import com.intellij.diagnostic.LogMessageEx; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.diagnostic.Attachment; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.ui.ThreeComponentsSplitter; |
| import com.intellij.openapi.ui.VerticalFlowLayout; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.ui.HyperlinkLabel; |
| import com.intellij.ui.IdeBorderFactory; |
| import com.intellij.ui.JBColor; |
| import com.intellij.ui.ScrollPaneFactory; |
| import com.intellij.ui.components.JBLabel; |
| import com.intellij.ui.components.JBLayeredPane; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.ExceptionUtil; |
| import com.intellij.util.ThrowableRunnable; |
| import com.intellij.util.containers.IntArrayList; |
| import com.intellij.util.ui.AsyncProcessIcon; |
| import com.intellij.util.ui.UIUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.HyperlinkEvent; |
| import javax.swing.event.HyperlinkListener; |
| import java.awt.*; |
| import java.io.ByteArrayOutputStream; |
| import java.io.PrintStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author Alexander Lobas |
| */ |
| public abstract class DesignerEditorPanel extends JPanel |
| implements DesignerEditorPanelFacade, DataProvider, ModuleProvider, RadPropertyContext { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.designer.designSurface.DesignerEditorPanel"); |
| |
| protected static final Integer LAYER_COMPONENT = JLayeredPane.DEFAULT_LAYER; |
| protected static final Integer LAYER_DECORATION = JLayeredPane.POPUP_LAYER; |
| protected static final Integer LAYER_FEEDBACK = JLayeredPane.DRAG_LAYER; |
| protected static final Integer LAYER_GLASS = LAYER_FEEDBACK + 100; |
| protected static final Integer LAYER_INPLACE_EDITING = LAYER_GLASS + 100; |
| private static final Integer LAYER_PROGRESS = LAYER_INPLACE_EDITING + 100; |
| |
| private final static String DESIGNER_CARD = "designer"; |
| private final static String ERROR_CARD = "error"; |
| private final static String ERROR_STACK_CARD = "stack"; |
| private final static String ERROR_NO_STACK_CARD = "no_stack"; |
| |
| private final DesignerEditor myEditor; |
| private final Project myProject; |
| private Module myModule; |
| protected final VirtualFile myFile; |
| |
| private final CardLayout myLayout = new CardLayout(); |
| private final ThreeComponentsSplitter myContentSplitter = new ThreeComponentsSplitter(); |
| private final JPanel myPanel = new JPanel(myLayout); |
| private JComponent myDesignerCard; |
| |
| protected DesignerActionPanel myActionPanel; |
| |
| protected CaptionPanel myHorizontalCaption; |
| protected CaptionPanel myVerticalCaption; |
| |
| protected JScrollPane myScrollPane; |
| protected JLayeredPane myLayeredPane; |
| protected GlassLayer myGlassLayer; |
| private DecorationLayer myDecorationLayer; |
| private FeedbackLayer myFeedbackLayer; |
| private InplaceEditingLayer myInplaceEditingLayer; |
| |
| protected ToolProvider myToolProvider; |
| protected EditableArea mySurfaceArea; |
| |
| protected RadComponent myRootComponent; |
| |
| protected QuickFixManager myQuickFixManager; |
| |
| private PaletteItem myActivePaletteItem; |
| private List<?> myExpandedComponents; |
| private final Map<String, Property> mySelectionPropertyMap = new HashMap<String, Property>(); |
| private int[][] myExpandedState; |
| private int[][] mySelectionState; |
| private final Map<String, int[][]> mySourceSelectionState = new FixedHashMap<String, int[][]>(16); |
| |
| private FixableMessageAction myWarnAction; |
| |
| private JPanel myErrorPanel; |
| protected JPanel myErrorMessages; |
| private JPanel myErrorStackPanel; |
| private CardLayout myErrorStackLayout; |
| private JTextArea myErrorStack; |
| |
| private JPanel myProgressPanel; |
| private AsyncProcessIcon myProgressIcon; |
| private JLabel myProgressMessage; |
| |
| protected String myLastExecuteCommand; |
| |
| public DesignerEditorPanel(@NotNull DesignerEditor editor, @NotNull Project project, Module module, @NotNull VirtualFile file) { |
| myEditor = editor; |
| myProject = project; |
| myModule = module; |
| myFile = file; |
| |
| initUI(); |
| |
| myToolProvider.loadDefaultTool(); |
| } |
| |
| private void initUI() { |
| setLayout(new BorderLayout()); |
| |
| myContentSplitter.setDividerWidth(0); |
| myContentSplitter.setDividerMouseZoneSize(Registry.intValue("ide.splitter.mouseZone")); |
| add(myContentSplitter, BorderLayout.CENTER); |
| |
| createDesignerCard(); |
| createErrorCard(); |
| createProgressPanel(); |
| |
| UIUtil.invokeLaterIfNeeded(new Runnable() { |
| @Override |
| public void run() { |
| DesignerEditorPanel designer = DesignerEditorPanel.this; |
| getDesignerWindowManager().bind(designer); |
| getPaletteWindowManager().bind(designer); |
| } |
| }); |
| } |
| |
| private void createDesignerCard() { |
| JPanel panel = new JPanel(new LightFillLayout()); |
| myContentSplitter.setInnerComponent(panel); |
| |
| myLayeredPane = new MyLayeredPane(); |
| |
| mySurfaceArea = createEditableArea(); |
| |
| myToolProvider = createToolProvider(); |
| |
| myGlassLayer = createGlassLayer(myToolProvider, mySurfaceArea); |
| myLayeredPane.add(myGlassLayer, LAYER_GLASS); |
| |
| myDecorationLayer = createDecorationLayer(); |
| myLayeredPane.add(myDecorationLayer, LAYER_DECORATION); |
| |
| myFeedbackLayer = createFeedbackLayer(); |
| myLayeredPane.add(myFeedbackLayer, LAYER_FEEDBACK); |
| |
| myInplaceEditingLayer = createInplaceEditingLayer(); |
| myLayeredPane.add(myInplaceEditingLayer, LAYER_INPLACE_EDITING); |
| |
| myScrollPane = createScrollPane(myLayeredPane); |
| |
| myQuickFixManager = new QuickFixManager(this, myGlassLayer, myScrollPane.getViewport()); |
| |
| myActionPanel = createActionPanel(); |
| myWarnAction = new FixableMessageAction(); |
| |
| panel.add(myActionPanel.getToolbarComponent()); |
| panel.add(myPanel); |
| |
| myDesignerCard = createDesignerCardPanel(); |
| myPanel.add(myDesignerCard, DESIGNER_CARD); |
| |
| mySurfaceArea.addSelectionListener(new ComponentSelectionListener() { |
| @Override |
| public void selectionChanged(EditableArea area) { |
| storeSourceSelectionState(); |
| } |
| }); |
| } |
| |
| protected JComponent createDesignerCardPanel() { |
| JPanel content = new JPanel(new GridBagLayout()); |
| |
| GridBagConstraints gbc = new GridBagConstraints(); |
| |
| gbc.gridx = 0; |
| gbc.gridy = 1; |
| gbc.fill = GridBagConstraints.VERTICAL; |
| |
| myVerticalCaption = createCaptionPanel(false); |
| content.add(myVerticalCaption, gbc); |
| |
| gbc.gridx = 1; |
| gbc.gridy = 0; |
| gbc.fill = GridBagConstraints.HORIZONTAL; |
| |
| myHorizontalCaption = createCaptionPanel(true); |
| content.add(myHorizontalCaption, gbc); |
| |
| gbc.gridx = 1; |
| gbc.gridy = 1; |
| gbc.weightx = 1; |
| gbc.weighty = 1; |
| gbc.fill = GridBagConstraints.BOTH; |
| |
| content.add(myScrollPane, gbc); |
| |
| myHorizontalCaption.attachToScrollPane(myScrollPane); |
| myVerticalCaption.attachToScrollPane(myScrollPane); |
| |
| return content; |
| } |
| |
| public final ThreeComponentsSplitter getContentSplitter() { |
| return myContentSplitter; |
| } |
| |
| protected EditableArea createEditableArea() { |
| return new DesignerEditableArea(); |
| } |
| |
| protected ToolProvider createToolProvider() { |
| return new DesignerToolProvider(); |
| } |
| |
| protected GlassLayer createGlassLayer(ToolProvider provider, EditableArea area) { |
| return new GlassLayer(provider, area); |
| } |
| |
| protected DecorationLayer createDecorationLayer() { |
| return new DecorationLayer(this, mySurfaceArea); |
| } |
| |
| protected FeedbackLayer createFeedbackLayer() { |
| return new FeedbackLayer(this); |
| } |
| |
| protected InplaceEditingLayer createInplaceEditingLayer() { |
| return new InplaceEditingLayer(this); |
| } |
| |
| protected CaptionPanel createCaptionPanel(boolean horizontal) { |
| return new CaptionPanel(this, horizontal, true); |
| } |
| |
| protected JScrollPane createScrollPane(@NotNull JLayeredPane content) { |
| JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(content); |
| scrollPane.setBackground(new JBColor(Color.WHITE, UIUtil.getListBackground())); |
| return scrollPane; |
| } |
| |
| protected DesignerActionPanel createActionPanel() { |
| return new DesignerActionPanel(this, myGlassLayer); |
| } |
| |
| @Nullable |
| public final PaletteItem getActivePaletteItem() { |
| return myActivePaletteItem; |
| } |
| |
| public final void activatePaletteItem(@Nullable PaletteItem paletteItem) { |
| myActivePaletteItem = paletteItem; |
| if (paletteItem != null) { |
| myToolProvider.setActiveTool(new CreationTool(true, createCreationFactory(paletteItem))); |
| } |
| else if (myToolProvider.getActiveTool() instanceof CreationTool) { |
| myToolProvider.loadDefaultTool(); |
| } |
| } |
| |
| protected final void showDesignerCard() { |
| myErrorMessages.removeAll(); |
| myErrorStack.setText(null); |
| myLayeredPane.revalidate(); |
| |
| if (myHorizontalCaption != null) { |
| myHorizontalCaption.update(); |
| } |
| if (myVerticalCaption != null) { |
| myVerticalCaption.update(); |
| } |
| |
| myLayout.show(myPanel, DESIGNER_CARD); |
| } |
| |
| private void createErrorCard() { |
| myErrorPanel = new JPanel(new BorderLayout()); |
| myErrorMessages = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 10, 5, true, false)); |
| myErrorPanel.add(myErrorMessages, BorderLayout.PAGE_START); |
| |
| myErrorStack = new JTextArea(50, 20); |
| myErrorStack.setEditable(false); |
| |
| myErrorStackLayout = new CardLayout(); |
| myErrorStackPanel = new JPanel(myErrorStackLayout); |
| myErrorStackPanel.add(new JLabel(), ERROR_NO_STACK_CARD); |
| myErrorStackPanel.add(ScrollPaneFactory.createScrollPane(myErrorStack), ERROR_STACK_CARD); |
| |
| myErrorPanel.add(myErrorStackPanel, BorderLayout.CENTER); |
| |
| myPanel.add(myErrorPanel, ERROR_CARD); |
| } |
| |
| public final void showError(@NotNull String message, @NotNull Throwable e) { |
| if (isProjectClosed()) { |
| return; |
| } |
| |
| while (e instanceof InvocationTargetException) { |
| if (e.getCause() == null) { |
| break; |
| } |
| e = e.getCause(); |
| } |
| |
| ErrorInfo info = new ErrorInfo(); |
| info.myMessage = info.myDisplayMessage = message; |
| info.myThrowable = e; |
| configureError(info); |
| |
| if (info.myShowMessage) { |
| showErrorPage(info); |
| } |
| if (info.myShowLog) { |
| LOG.error(LogMessageEx.createEvent(info.myDisplayMessage, |
| info.myMessage + "\n" + ExceptionUtil.getThrowableText(info.myThrowable), |
| getErrorAttachments(info))); |
| } |
| } |
| |
| protected Attachment[] getErrorAttachments(ErrorInfo info) { |
| return new Attachment[]{AttachmentFactory.createAttachment(myFile)}; |
| } |
| |
| protected abstract void configureError(@NotNull ErrorInfo info); |
| |
| protected void showErrorPage(ErrorInfo info) { |
| storeState(); |
| hideProgress(); |
| myRootComponent = null; |
| |
| myErrorMessages.removeAll(); |
| |
| if (info.myShowStack) { |
| ByteArrayOutputStream stream = new ByteArrayOutputStream(); |
| info.myThrowable.printStackTrace(new PrintStream(stream)); |
| myErrorStack.setText(stream.toString()); |
| myErrorStackLayout.show(myErrorStackPanel, ERROR_STACK_CARD); |
| } |
| else { |
| myErrorStack.setText(null); |
| myErrorStackLayout.show(myErrorStackPanel, ERROR_NO_STACK_CARD); |
| } |
| |
| addErrorMessage(new FixableMessageInfo(true, info.myDisplayMessage, "", "", null, null), Messages.getErrorIcon()); |
| for (FixableMessageInfo message : info.myMessages) { |
| addErrorMessage(message, message.myErrorIcon ? Messages.getErrorIcon() : Messages.getWarningIcon()); |
| } |
| |
| myErrorPanel.revalidate(); |
| myLayout.show(myPanel, ERROR_CARD); |
| |
| getDesignerToolWindow().refresh(true); |
| repaint(); |
| } |
| |
| protected void addErrorMessage(final FixableMessageInfo message, Icon icon) { |
| if (message.myLinkText.length() > 0 || message.myAfterLinkText.length() > 0) { |
| HyperlinkLabel warnLabel = new HyperlinkLabel(); |
| warnLabel.setOpaque(false); |
| warnLabel.setHyperlinkText(message.myBeforeLinkText, message.myLinkText, message.myAfterLinkText); |
| warnLabel.setIcon(icon); |
| |
| if (message.myQuickFix != null) { |
| warnLabel.addHyperlinkListener(new HyperlinkListener() { |
| public void hyperlinkUpdate(final HyperlinkEvent e) { |
| if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { |
| message.myQuickFix.run(); |
| } |
| } |
| }); |
| } |
| myErrorMessages.add(warnLabel); |
| } |
| else { |
| JBLabel warnLabel = new JBLabel(); |
| warnLabel.setOpaque(false); |
| warnLabel.setText("<html><body>" + message.myBeforeLinkText.replace("\n", "<br>") + "</body></html>"); |
| warnLabel.setIcon(icon); |
| myErrorMessages.add(warnLabel); |
| } |
| if (message.myAdditionalFixes != null && message.myAdditionalFixes.size() > 0) { |
| JPanel fixesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0)); |
| fixesPanel.setBorder(IdeBorderFactory.createEmptyBorder(3, 0, 10, 0)); |
| fixesPanel.setOpaque(false); |
| fixesPanel.add(Box.createHorizontalStrut(icon.getIconWidth())); |
| |
| for (Pair<String, Runnable> pair : message.myAdditionalFixes) { |
| HyperlinkLabel fixLabel = new HyperlinkLabel(); |
| fixLabel.setOpaque(false); |
| fixLabel.setHyperlinkText(pair.getFirst()); |
| final Runnable fix = pair.getSecond(); |
| |
| fixLabel.addHyperlinkListener(new HyperlinkListener() { |
| @Override |
| public void hyperlinkUpdate(HyperlinkEvent e) { |
| if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { |
| fix.run(); |
| } |
| } |
| }); |
| fixesPanel.add(fixLabel); |
| } |
| myErrorMessages.add(fixesPanel); |
| } |
| } |
| |
| protected final void showWarnMessages(@Nullable List<FixableMessageInfo> messages) { |
| if (messages == null) { |
| myWarnAction.hide(); |
| } |
| else { |
| myWarnAction.show(messages); |
| } |
| } |
| |
| private void createProgressPanel() { |
| myProgressIcon = new AsyncProcessIcon("Designer progress"); |
| myProgressMessage = new JLabel(); |
| |
| JPanel progressBlock = new JPanel(); |
| progressBlock.add(myProgressIcon); |
| progressBlock.add(myProgressMessage); |
| progressBlock.setBorder(IdeBorderFactory.createRoundedBorder()); |
| |
| myProgressPanel = new JPanel(new GridBagLayout()); |
| myProgressPanel.add(progressBlock, |
| new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), |
| 0, 0) |
| ); |
| myProgressPanel.setOpaque(false); |
| } |
| |
| protected final void showProgress(String message) { |
| myProgressMessage.setText(message); |
| if (myProgressPanel.getParent() == null) { |
| myGlassLayer.setEnabled(false); |
| myProgressIcon.resume(); |
| myLayeredPane.add(myProgressPanel, LAYER_PROGRESS); |
| myLayeredPane.repaint(); |
| } |
| } |
| |
| protected final void hideProgress() { |
| myGlassLayer.setEnabled(true); |
| myProgressIcon.suspend(); |
| myLayeredPane.remove(myProgressPanel); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // |
| // |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| |
| @NotNull |
| @Override |
| public final Module getModule() { |
| if (myModule.isDisposed()) { |
| myModule = findModule(myProject, myFile); |
| if (myModule == null) { |
| throw new IllegalArgumentException("No module for file " + myFile + " in project " + myProject); |
| } |
| } |
| return myModule; |
| } |
| |
| @Nullable |
| protected Module findModule(Project project, VirtualFile file) { |
| return ModuleUtilCore.findModuleForFile(file, project); |
| } |
| |
| public final DesignerEditor getEditor() { |
| return myEditor; |
| } |
| |
| public VirtualFile getFile() { |
| return myFile; |
| } |
| |
| @Override |
| public final Project getProject() { |
| return myProject; |
| } |
| |
| public final boolean isProjectClosed() { |
| return myProject.isDisposed() || !myProject.isOpen(); |
| } |
| |
| public EditableArea getSurfaceArea() { |
| return mySurfaceArea; |
| } |
| |
| public ToolProvider getToolProvider() { |
| return myToolProvider; |
| } |
| |
| public DesignerActionPanel getActionPanel() { |
| return myActionPanel; |
| } |
| |
| public InplaceEditingLayer getInplaceEditingLayer() { |
| return myInplaceEditingLayer; |
| } |
| |
| public JComponent getPreferredFocusedComponent() { |
| return myDesignerCard.isVisible() ? myGlassLayer : myErrorPanel; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // State |
| // |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| |
| @Nullable |
| public List<?> getExpandedComponents() { |
| return myExpandedComponents; |
| } |
| |
| public void setExpandedComponents(@Nullable List<?> expandedComponents) { |
| myExpandedComponents = expandedComponents; |
| } |
| |
| public Property getSelectionProperty(@Nullable String key) { |
| return mySelectionPropertyMap.get(key); |
| } |
| |
| public void setSelectionProperty(@Nullable String key, Property selectionProperty) { |
| mySelectionPropertyMap.put(key, selectionProperty); |
| } |
| |
| protected void storeState() { |
| if (myRootComponent != null && myExpandedState == null && mySelectionState == null) { |
| myExpandedState = new int[myExpandedComponents == null ? 0 : myExpandedComponents.size()][]; |
| for (int i = 0; i < myExpandedState.length; i++) { |
| IntArrayList path = new IntArrayList(); |
| componentToPath((RadComponent)myExpandedComponents.get(i), path); |
| myExpandedState[i] = path.toArray(); |
| } |
| |
| mySelectionState = getSelectionState(); |
| |
| myExpandedComponents = null; |
| |
| InputTool tool = myToolProvider.getActiveTool(); |
| if (!(tool instanceof MarqueeTracker) && |
| !(tool instanceof CreationTool) && |
| !(tool instanceof PasteTool)) { |
| myToolProvider.loadDefaultTool(); |
| } |
| } |
| } |
| |
| private void storeSourceSelectionState() { |
| if (!CommonEditActionsProvider.isDeleting) { |
| mySourceSelectionState.put(getEditorText(), getSelectionState()); |
| } |
| } |
| |
| private int[][] getSelectionState() { |
| return getSelectionState(mySurfaceArea.getSelection()); |
| } |
| |
| protected static int[][] getSelectionState(List<RadComponent> selection) { |
| int[][] selectionState = new int[selection.size()][]; |
| |
| for (int i = 0; i < selectionState.length; i++) { |
| IntArrayList path = new IntArrayList(); |
| componentToPath(selection.get(i), path); |
| selectionState[i] = path.toArray(); |
| } |
| |
| return selectionState; |
| } |
| |
| private static void componentToPath(RadComponent component, IntArrayList path) { |
| RadComponent parent = component.getParent(); |
| |
| if (parent != null) { |
| path.add(0, parent.getChildren().indexOf(component)); |
| componentToPath(parent, path); |
| } |
| } |
| |
| protected void restoreState() { |
| DesignerToolWindowContent toolManager = getDesignerToolWindow(); |
| |
| if (myExpandedState != null) { |
| List<RadComponent> expanded = new ArrayList<RadComponent>(); |
| for (int[] path : myExpandedState) { |
| pathToComponent(expanded, myRootComponent, path, 0); |
| } |
| myExpandedComponents = expanded; |
| toolManager.expandFromState(); |
| myExpandedState = null; |
| } |
| |
| List<RadComponent> selection = new ArrayList<RadComponent>(); |
| |
| int[][] selectionState = mySourceSelectionState.get(getEditorText()); |
| if (selectionState != null) { |
| for (int[] path : selectionState) { |
| pathToComponent(selection, myRootComponent, path, 0); |
| } |
| } |
| |
| if (selection.isEmpty()) { |
| if (mySelectionState != null) { |
| for (int[] path : mySelectionState) { |
| pathToComponent(selection, myRootComponent, path, 0); |
| } |
| } |
| } |
| |
| if (selection.isEmpty()) { |
| toolManager.refresh(true); |
| } |
| else { |
| mySurfaceArea.setSelection(selection); |
| } |
| |
| mySelectionState = null; |
| } |
| |
| protected static void pathToComponent(List<RadComponent> components, RadComponent component, int[] path, int index) { |
| if (index == path.length) { |
| components.add(component); |
| } |
| else { |
| List<RadComponent> children = component.getChildren(); |
| int componentIndex = path[index]; |
| if (0 <= componentIndex && componentIndex < children.size()) { |
| pathToComponent(components, children.get(componentIndex), path, index + 1); |
| } |
| } |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // |
| // |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| |
| public abstract String getPlatformTarget(); |
| |
| protected RadComponent findTarget(int x, int y, @Nullable ComponentTargetFilter filter) { |
| if (myRootComponent != null) { |
| FindComponentVisitor visitor = new FindComponentVisitor(myLayeredPane, filter, x, y); |
| myRootComponent.accept(visitor, false); |
| return visitor.getResult(); |
| } |
| return null; |
| } |
| |
| protected abstract ComponentDecorator getRootSelectionDecorator(); |
| |
| @Nullable |
| protected EditOperation processRootOperation(OperationContext context) { |
| return null; |
| } |
| |
| protected abstract boolean execute(ThrowableRunnable<Exception> operation, boolean updateProperties); |
| |
| protected abstract void executeWithReparse(ThrowableRunnable<Exception> operation); |
| |
| protected abstract void execute(List<EditOperation> operations); |
| |
| public abstract List<PaletteGroup> getPaletteGroups(); |
| |
| /** |
| * Returns a suitable version label from the version attribute from a {@link PaletteItem} version |
| */ |
| @NotNull |
| public String getVersionLabel(@Nullable String version) { |
| return StringUtil.notNullize(version); |
| } |
| |
| public boolean isDeprecated(@Nullable String deprecatedIn) { |
| return !StringUtil.isEmpty(deprecatedIn); |
| } |
| |
| protected InputTool createDefaultTool() { |
| return new SelectionTool(); |
| } |
| |
| @NotNull |
| protected abstract ComponentCreationFactory createCreationFactory(PaletteItem paletteItem); |
| |
| @Nullable |
| public abstract ComponentPasteFactory createPasteFactory(String xmlComponents); |
| |
| public abstract String getEditorText(); |
| |
| public void activate() { |
| } |
| |
| public void deactivate() { |
| } |
| |
| @NotNull |
| public DesignerEditorState createState() { |
| return new DesignerEditorState(myFile, getZoom()); |
| } |
| |
| public boolean isEditorValid() { |
| return myFile.isValid(); |
| } |
| |
| @Override |
| public Object getData(@NonNls String dataId) { |
| return myActionPanel.getData(dataId); |
| } |
| |
| public void dispose() { |
| Disposer.dispose(myProgressIcon); |
| getDesignerWindowManager().dispose(this); |
| getPaletteWindowManager().dispose(this); |
| Disposer.dispose(myContentSplitter); |
| } |
| |
| protected AbstractToolWindowManager getDesignerWindowManager() { |
| return DesignerToolWindowManager.getInstance(myProject); |
| } |
| |
| protected AbstractToolWindowManager getPaletteWindowManager() { |
| return PaletteToolWindowManager.getInstance(myProject); |
| } |
| |
| public DesignerToolWindowContent getDesignerToolWindow() { |
| return DesignerToolWindowManager.getInstance(this); |
| } |
| |
| protected PaletteToolWindowContent getPaletteToolWindow() { |
| return PaletteToolWindowManager.getInstance(this); |
| } |
| |
| @Nullable |
| public WrapInProvider getWrapInProvider() { |
| return null; |
| } |
| |
| @Nullable |
| public RadComponent getRootComponent() { |
| return myRootComponent; |
| } |
| |
| public Object[] getTreeRoots() { |
| return myRootComponent == null ? ArrayUtil.EMPTY_OBJECT_ARRAY : new Object[]{myRootComponent}; |
| } |
| |
| public abstract TreeComponentDecorator getTreeDecorator(); |
| |
| public void handleTreeArea(TreeEditableArea treeArea) { |
| } |
| |
| @NotNull |
| public TablePanelActionPolicy getTablePanelActionPolicy() { |
| return TablePanelActionPolicy.ALL; |
| } |
| |
| @Nullable |
| public PropertyTableTab[] getPropertyTableTabs() { |
| return null; |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // Zooming |
| // |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| |
| public boolean isZoomSupported() { |
| return false; |
| } |
| |
| public void zoom(@NotNull ZoomType type) { |
| } |
| |
| public void setZoom(double zoom) { |
| } |
| |
| public double getZoom() { |
| return 1; |
| } |
| |
| protected void viewZoomed() { |
| // Hide quickfix light bulbs; position can be obsolete after the zoom level has changed |
| myQuickFixManager.hideHint(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // Inspection |
| // |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| |
| public void loadInspections(ProgressIndicator progress) { |
| } |
| |
| public void updateInspections() { |
| myQuickFixManager.update(); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| // |
| // |
| // |
| ////////////////////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Size of the scene, in scroll pane view port pixels. |
| */ |
| @NotNull |
| protected Dimension getSceneSize(Component target) { |
| int width = 0; |
| int height = 0; |
| |
| if (myRootComponent != null) { |
| Rectangle bounds = myRootComponent.getBounds(target); |
| width = Math.max(width, (int)bounds.getMaxX()); |
| height = Math.max(height, (int)bounds.getMaxY()); |
| |
| for (RadComponent component : myRootComponent.getChildren()) { |
| Rectangle childBounds = component.getBounds(target); |
| width = Math.max(width, (int)childBounds.getMaxX()); |
| height = Math.max(height, (int)childBounds.getMaxY()); |
| } |
| } |
| |
| width += 50; |
| height += 40; |
| |
| return new Dimension(width, height); |
| } |
| |
| protected class DesignerEditableArea extends ComponentEditableArea { |
| public DesignerEditableArea() { |
| super(myLayeredPane); |
| } |
| |
| @Override |
| protected void fireSelectionChanged() { |
| super.fireSelectionChanged(); |
| myLayeredPane.revalidate(); |
| myLayeredPane.repaint(); |
| } |
| |
| @Override |
| public void scrollToSelection() { |
| List<RadComponent> selection = getSelection(); |
| if (selection.size() == 1) { |
| Rectangle bounds = selection.get(0).getBounds(myLayeredPane); |
| if (bounds != null) { |
| myLayeredPane.scrollRectToVisible(bounds); |
| } |
| } |
| } |
| |
| @Override |
| public RadComponent findTarget(int x, int y, @Nullable ComponentTargetFilter filter) { |
| return DesignerEditorPanel.this.findTarget(x, y, filter); |
| } |
| |
| @Override |
| public InputTool findTargetTool(int x, int y) { |
| return myDecorationLayer.findTargetTool(x, y); |
| } |
| |
| @Override |
| public void showSelection(boolean value) { |
| myDecorationLayer.showSelection(value); |
| } |
| |
| @Override |
| public ComponentDecorator getRootSelectionDecorator() { |
| return DesignerEditorPanel.this.getRootSelectionDecorator(); |
| } |
| |
| @Nullable |
| public EditOperation processRootOperation(OperationContext context) { |
| return DesignerEditorPanel.this.processRootOperation(context); |
| } |
| |
| @Override |
| public FeedbackLayer getFeedbackLayer() { |
| return myFeedbackLayer; |
| } |
| |
| @Override |
| public RadComponent getRootComponent() { |
| return myRootComponent; |
| } |
| |
| @Override |
| public ActionGroup getPopupActions() { |
| return myActionPanel.getPopupActions(this); |
| } |
| |
| @Override |
| public String getPopupPlace() { |
| return ActionPlaces.GUI_DESIGNER_EDITOR_POPUP; |
| } |
| } |
| |
| protected class DesignerToolProvider extends ToolProvider { |
| @Override |
| public void loadDefaultTool() { |
| setActiveTool(createDefaultTool()); |
| } |
| |
| @Override |
| public void setActiveTool(InputTool tool) { |
| if (getActiveTool() instanceof CreationTool && !(tool instanceof CreationTool)) { |
| getPaletteToolWindow().clearActiveItem(); |
| } |
| if (!(tool instanceof SelectionTool)) { |
| hideInspections(); |
| } |
| super.setActiveTool(tool); |
| } |
| |
| @Override |
| public boolean execute(final ThrowableRunnable<Exception> operation, String command, final boolean updateProperties) { |
| myLastExecuteCommand = command; |
| final Ref<Boolean> result = Ref.create(Boolean.TRUE); |
| CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { |
| public void run() { |
| result.set(DesignerEditorPanel.this.execute(operation, updateProperties)); |
| } |
| }, command, null); |
| return result.get(); |
| } |
| |
| @Override |
| public void executeWithReparse(final ThrowableRunnable<Exception> operation, String command) { |
| myLastExecuteCommand = command; |
| CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { |
| public void run() { |
| DesignerEditorPanel.this.executeWithReparse(operation); |
| } |
| }, command, null); |
| } |
| |
| @Override |
| public void execute(final List<EditOperation> operations, String command) { |
| myLastExecuteCommand = command; |
| CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() { |
| public void run() { |
| DesignerEditorPanel.this.execute(operations); |
| } |
| }, command, null); |
| } |
| |
| @Override |
| public void startInplaceEditing(@Nullable InplaceContext inplaceContext) { |
| myInplaceEditingLayer.startEditing(inplaceContext); |
| } |
| |
| @Override |
| public void hideInspections() { |
| myQuickFixManager.hideHint(); |
| } |
| |
| @Override |
| public void showError(@NonNls String message, Throwable e) { |
| DesignerEditorPanel.this.showError(message, e); |
| } |
| |
| @Override |
| public boolean isZoomSupported() { |
| return DesignerEditorPanel.this.isZoomSupported(); |
| } |
| |
| @Override |
| public void zoom(@NotNull ZoomType type) { |
| DesignerEditorPanel.this.zoom(type); |
| } |
| |
| @Override |
| public void setZoom(double zoom) { |
| DesignerEditorPanel.this.setZoom(zoom); |
| } |
| |
| @Override |
| public double getZoom() { |
| return DesignerEditorPanel.this.getZoom(); |
| } |
| } |
| |
| private final class MyLayeredPane extends JBLayeredPane implements Scrollable { |
| public void doLayout() { |
| for (int i = getComponentCount() - 1; i >= 0; i--) { |
| Component component = getComponent(i); |
| component.setBounds(0, 0, getWidth(), getHeight()); |
| } |
| } |
| |
| public Dimension getMinimumSize() { |
| return getPreferredSize(); |
| } |
| |
| public Dimension getPreferredSize() { |
| Rectangle bounds = myScrollPane.getViewport().getBounds(); |
| Dimension size = getSceneSize(this); |
| |
| size.width = Math.max(size.width, bounds.width); |
| size.height = Math.max(size.height, bounds.height); |
| |
| return size; |
| } |
| |
| public Dimension getPreferredScrollableViewportSize() { |
| return getPreferredSize(); |
| } |
| |
| public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { |
| return 10; |
| } |
| |
| public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { |
| if (orientation == SwingConstants.HORIZONTAL) { |
| return visibleRect.width - 10; |
| } |
| return visibleRect.height - 10; |
| } |
| |
| public boolean getScrollableTracksViewportWidth() { |
| return false; |
| } |
| |
| public boolean getScrollableTracksViewportHeight() { |
| return false; |
| } |
| } |
| |
| private class FixableMessageAction extends AbstractComboBoxAction<FixableMessageInfo> { |
| private final DefaultActionGroup myActionGroup = new DefaultActionGroup(); |
| private String myTitle; |
| private boolean myIsAdded; |
| |
| public FixableMessageAction() { |
| myActionPanel.getActionGroup().add(myActionGroup); |
| |
| Presentation presentation = getTemplatePresentation(); |
| presentation.setDescription("Warnings"); |
| presentation.setIcon(AllIcons.Ide.Warning_notifications); |
| } |
| |
| public void show(List<FixableMessageInfo> messages) { |
| if (!myIsAdded) { |
| myTitle = Integer.toString(messages.size()); |
| setItems(messages, null); |
| myActionGroup.add(this); |
| myActionPanel.update(); |
| myIsAdded = true; |
| } |
| } |
| |
| public void hide() { |
| if (myIsAdded) { |
| myActionGroup.remove(this); |
| myActionPanel.update(); |
| myIsAdded = false; |
| } |
| } |
| |
| @NotNull |
| @Override |
| protected DefaultActionGroup createPopupActionGroup(JComponent button) { |
| DefaultActionGroup actionGroup = new DefaultActionGroup(); |
| for (final FixableMessageInfo message : myItems) { |
| AnAction action; |
| if ((message.myQuickFix != null && (message.myLinkText.length() > 0 || message.myAfterLinkText.length() > 0)) || |
| (message.myAdditionalFixes != null && message.myAdditionalFixes.size() > 0)) { |
| final AnAction[] defaultAction = new AnAction[1]; |
| DefaultActionGroup popupGroup = new DefaultActionGroup() { |
| @Override |
| public boolean canBePerformed(DataContext context) { |
| return true; |
| } |
| |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| defaultAction[0].actionPerformed(e); |
| } |
| }; |
| popupGroup.setPopup(true); |
| action = popupGroup; |
| |
| if (message.myQuickFix != null && (message.myLinkText.length() > 0 || message.myAfterLinkText.length() > 0)) { |
| AnAction popupAction = new AnAction() { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| message.myQuickFix.run(); |
| } |
| }; |
| popupAction.getTemplatePresentation().setText(cleanText(message.myLinkText + message.myAfterLinkText)); |
| popupGroup.add(popupAction); |
| defaultAction[0] = popupAction; |
| } |
| if (message.myAdditionalFixes != null && message.myAdditionalFixes.size() > 0) { |
| for (final Pair<String, Runnable> pair : message.myAdditionalFixes) { |
| AnAction popupAction = new AnAction() { |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| pair.second.run(); |
| } |
| }; |
| popupAction.getTemplatePresentation().setText(cleanText(pair.first)); |
| popupGroup.add(popupAction); |
| if (defaultAction[0] == null) { |
| defaultAction[0] = popupAction; |
| } |
| } |
| } |
| } |
| else { |
| action = new EmptyAction(true); |
| } |
| actionGroup.add(action); |
| update(message, action.getTemplatePresentation(), true); |
| } |
| return actionGroup; |
| } |
| |
| @Override |
| protected void update(FixableMessageInfo item, Presentation presentation, boolean popup) { |
| if (popup) { |
| presentation.setText(cleanText(item.myBeforeLinkText)); |
| } |
| else { |
| presentation.setText(myTitle); |
| } |
| } |
| |
| private String cleanText(String text) { |
| if (text != null) { |
| text = text.trim(); |
| text = StringUtil.replace(text, " ", " "); |
| text = StringUtil.replace(text, "\n", " "); |
| |
| StringBuilder builder = new StringBuilder(); |
| int length = text.length(); |
| boolean whitespace = false; |
| |
| for (int i = 0; i < length; i++) { |
| char ch = text.charAt(i); |
| if (ch == ' ') { |
| if (!whitespace) { |
| whitespace = true; |
| builder.append(ch); |
| } |
| } |
| else { |
| whitespace = false; |
| builder.append(ch); |
| } |
| } |
| |
| text = builder.toString(); |
| } |
| return text; |
| } |
| |
| @Override |
| protected boolean selectionChanged(FixableMessageInfo item) { |
| return false; |
| } |
| } |
| |
| public static final class ErrorInfo { |
| public String myMessage; |
| public String myDisplayMessage; |
| |
| public final List<FixableMessageInfo> myMessages = new ArrayList<FixableMessageInfo>(); |
| |
| public Throwable myThrowable; |
| |
| public boolean myShowMessage = true; |
| public boolean myShowStack = true; |
| public boolean myShowLog; |
| } |
| |
| public static final class FixableMessageInfo { |
| public final boolean myErrorIcon; |
| public final String myBeforeLinkText; |
| public final String myLinkText; |
| public final String myAfterLinkText; |
| public final Runnable myQuickFix; |
| public final List<Pair<String, Runnable>> myAdditionalFixes; |
| |
| public FixableMessageInfo(boolean errorIcon, |
| String beforeLinkText, |
| String linkText, |
| String afterLinkText, |
| Runnable quickFix, |
| List<Pair<String, Runnable>> additionalFixes) { |
| myErrorIcon = errorIcon; |
| myBeforeLinkText = beforeLinkText; |
| myLinkText = linkText; |
| myAfterLinkText = afterLinkText; |
| myQuickFix = quickFix; |
| myAdditionalFixes = additionalFixes; |
| } |
| } |
| |
| private static class FixedHashMap<K, V> extends HashMap<K, V> { |
| private final int mySize; |
| private final List<K> myKeys = new LinkedList<K>(); |
| |
| public FixedHashMap(int size) { |
| mySize = size; |
| } |
| |
| @Override |
| public V put(K key, V value) { |
| if (!myKeys.contains(key)) { |
| if (myKeys.size() >= mySize) { |
| remove(myKeys.remove(0)); |
| } |
| myKeys.add(key); |
| } |
| return super.put(key, value); |
| } |
| |
| @Override |
| public V get(Object key) { |
| if (myKeys.contains(key)) { |
| int index = myKeys.indexOf(key); |
| int last = myKeys.size() - 1; |
| myKeys.set(index, myKeys.get(last)); |
| myKeys.set(last, (K)key); |
| } |
| return super.get(key); |
| } |
| } |
| } |