package com.intellij.flex.uiDesigner.preview; import com.intellij.flex.uiDesigner.*; import com.intellij.flex.uiDesigner.io.IOUtil; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.components.ProjectComponent; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.*; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.AsyncResult; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowAnchor; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter; import com.intellij.openapi.wm.ex.ToolWindowManagerEx; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiManager; import com.intellij.psi.xml.XmlFile; import com.intellij.ui.content.Content; import com.intellij.ui.content.ContentManager; import com.intellij.util.Alarm; import com.intellij.util.PlatformIcons; import com.intellij.util.messages.MessageBusConnection; 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 java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; public class MxmlPreviewToolWindowManager implements ProjectComponent { private static final String SETTINGS_TOOL_WINDOW_VISIBLE = "mxml.preview.tool.window.visible"; private static final String LAST_PREVIEW_IMAGE_FILE_NAME = "lastPreview"; private final Project project; private final FileEditorManager fileEditorManager; private final Alarm toolWindowUpdateAlarm; private ToolWindow toolWindow; private MxmlPreviewToolWindowForm toolWindowForm; private boolean toolWindowVisible; private int loadingDecoratorStarted; private boolean lastPreviewChecked; public MxmlPreviewToolWindowManager(final Project project, final FileEditorManager fileEditorManager) { this.project = project; this.fileEditorManager = fileEditorManager; toolWindowUpdateAlarm = new Alarm(); } @Nullable public VirtualFile getServedFile() { return toolWindowVisible && toolWindowForm != null ? toolWindowForm.getFile() : null; } @Override public void projectOpened() { project.getMessageBus().connect(project).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new MyFileEditorManagerListener()); } @Override public void projectClosed() { if (toolWindowForm == null) { return; } try { VirtualFile file = toolWindowForm.getFile(); if (file != null) { saveLastImage(file); } } finally { Disposer.dispose(toolWindowForm.getPreviewPanel()); toolWindowForm = null; toolWindow = null; toolWindowVisible = false; } } private void saveLastImage(final VirtualFile file) { BufferedImage image = toolWindowForm.getPreviewPanel().getImage(); if (image != null) { try { IOUtil.saveImage(toolWindowForm.getPreviewPanel().getImage(), new File(DesignerApplicationManager.APP_DIR, LAST_PREVIEW_IMAGE_FILE_NAME), out -> { try { out.writeLong(file.getTimeStamp()); out.writeUTF(file.getUrl()); } catch (IOException e) { throw new RuntimeException(e); } }); } catch (Throwable e) { LogMessageUtil.LOG.warn("Can't save image for last document", e); } } } @Override @NotNull @NonNls public String getComponentName() { return "MxmlPreviewToolWindowManager"; } private void initToolWindow() { toolWindowForm = new MxmlPreviewToolWindowForm(); String toolWindowId = FlashUIDesignerBundle.message("mxml.preview.tool.window.title"); toolWindow = ToolWindowManager.getInstance(project).registerToolWindow(toolWindowId, false, ToolWindowAnchor.RIGHT, project, false); toolWindow.setIcon(PlatformIcons.UI_FORM_ICON); PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(project); toolWindowVisible = propertiesComponent.getBoolean(SETTINGS_TOOL_WINDOW_VISIBLE); if (toolWindowVisible) { toolWindow.show(null); } else { toolWindow.hide(null); } ((ToolWindowManagerEx)ToolWindowManager.getInstance(project)).addToolWindowManagerListener(new ToolWindowManagerAdapter() { @Override public void stateChanged() { if (project.isDisposed() || toolWindow == null || !toolWindow.isAvailable()) { return; } final boolean currentVisible = toolWindow.isVisible(); if (currentVisible == toolWindowVisible) { return; } toolWindowVisible = currentVisible; PropertiesComponent propertiesComponent = PropertiesComponent.getInstance(project); if (currentVisible) { propertiesComponent.setValue(SETTINGS_TOOL_WINDOW_VISIBLE, true); if (!lastPreviewChecked) { lastPreviewChecked = true; if (checkLastImage()) { return; } } render(true, false); } else { propertiesComponent.unsetValue(SETTINGS_TOOL_WINDOW_VISIBLE); } } }); JPanel contentPanel = toolWindowForm.getContentPanel(); ContentManager contentManager = toolWindow.getContentManager(); Content content = contentManager.getFactory().createContent(contentPanel, null, false); content.setCloseable(false); content.setPreferredFocusableComponent(contentPanel); contentManager.addContent(content); contentManager.setSelectedContent(content, true); MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(project); connection.subscribe(DesignerApplicationManager.MESSAGE_TOPIC, new DocumentRenderedListener() { private boolean isApplicable(DocumentFactoryManager.DocumentInfo info) { return toolWindowVisible && toolWindowForm.getFile() != null && info.equals(DocumentFactoryManager.getInstance().getNullableInfo(toolWindowForm.getFile())); } @Override public void documentRendered(DocumentFactoryManager.DocumentInfo info) { if (isApplicable(info) && !toolWindowForm.waitingForGetDocument) { UIUtil.invokeLaterIfNeeded(() -> render(false, false)); } } @Override public void errorOccurred() { } }); } private boolean checkLastImage() { File file = new File(DesignerApplicationManager.APP_DIR, LAST_PREVIEW_IMAGE_FILE_NAME); if (!file.exists()) { return false; } try { final VirtualFile virtualFile = toolWindowForm.getFile(); BufferedImage image = IOUtil.readImage(file, in -> { try { return in.readLong() == virtualFile.getTimeStamp() && in.readUTF().equals(virtualFile.getUrl()); } catch (IOException e) { throw new RuntimeException(e); } }); if (image != null) { toolWindowForm.getPreviewPanel().setImage(image); return true; } } catch (IOException e) { LogMessageUtil.LOG.warn("Can't read image for last document", e); } return false; } private void render(final boolean isSlow, final boolean clearPreviousOnError) { if (!toolWindowVisible) { return; } final VirtualFile file = toolWindowForm.getFile(); if (file == null) { return; } toolWindowForm.getPreviewPanel().clearCannotRender(); if (isSlow) { toolWindowForm.getPreviewPanel().getLoadingDecorator().startLoading(false); loadingDecoratorStarted++; } toolWindowForm.waitingForGetDocument = true; XmlFile xmlFile = (XmlFile)PsiManager.getInstance(project).findFile(file); LogMessageUtil.LOG.assertTrue(xmlFile != null); AsyncResult<BufferedImage> result = DesignerApplicationManager.getInstance().getDocumentImage(xmlFile); result.doWhenDone(new QueuedAsyncResultHandler<BufferedImage>() { @Override protected boolean isExpired() { return toolWindowForm == null || !file.equals(toolWindowForm.getFile()); } @Override public void process(final BufferedImage image) { UIUtil.invokeLaterIfNeeded(() -> toolWindowForm.getPreviewPanel().setImage(image)); } }); result.doWhenProcessed(() -> { toolWindowForm.waitingForGetDocument = false; if (isSlow && --loadingDecoratorStarted == 0 && toolWindowForm != null) { toolWindowForm.getPreviewPanel().getLoadingDecorator().stopLoading(); } }); result.doWhenRejected(() -> { if (clearPreviousOnError) { toolWindowForm.getPreviewPanel().setImage(null); } UIUtil.invokeLaterIfNeeded(() -> toolWindowForm.getPreviewPanel().showCannotRender()); }); } private boolean isApplicableEditor(Editor editor) { final Document document = editor.getDocument(); return DesignerApplicationManager.isApplicable(project, PsiDocumentManager.getInstance(project).getPsiFile(document)); } private class MyFileEditorManagerListener implements Runnable, FileEditorManagerListener { private boolean waitingForSmartMode; @Override public void selectionChanged(@NotNull FileEditorManagerEvent event) { final FileEditor newFileEditor = event.getNewEditor(); Editor mxmlEditor = null; if (newFileEditor instanceof TextEditor) { final Editor editor = ((TextEditor)newFileEditor).getEditor(); if (DesignerApplicationManager.dumbAwareIsApplicable(project, PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()))) { mxmlEditor = editor; } } if (mxmlEditor == null) { processFileEditorChange(null); return; } final DumbService dumbService = DumbService.getInstance(project); if (dumbService.isDumb()) { openWhenSmart(dumbService); } else { if (!isApplicableEditor(mxmlEditor)) { mxmlEditor = null; } processFileEditorChange(mxmlEditor); } } private void openWhenSmart(DumbService dumbService) { if (!waitingForSmartMode) { waitingForSmartMode = true; dumbService.runWhenSmart(this); } } @Override public void run() { waitingForSmartMode = false; final Editor selectedTextEditor; final AccessToken token = ReadAction.start(); try { selectedTextEditor = fileEditorManager.getSelectedTextEditor(); } finally { token.finish(); } if (selectedTextEditor != null && isApplicableEditor(selectedTextEditor)) { processFileEditorChange(selectedTextEditor); } } } private void processFileEditorChange(@Nullable final Editor newEditor) { toolWindowUpdateAlarm.cancelAllRequests(); toolWindowUpdateAlarm.addRequest(() -> { if (project.isDisposed() || !project.isOpen()) { return; } if (toolWindow == null) { if (newEditor == null) { return; } initToolWindow(); // idea inspection bug //noinspection ConstantConditions assert toolWindow != null; } VirtualFile psiFile = newEditor == null ? null : FileDocumentManager.getInstance().getFile(newEditor.getDocument()); if (psiFile == null) { return; } final boolean doRender = !Comparing.equal(toolWindowForm.getFile(), psiFile); if (doRender) { toolWindowForm.setFile(psiFile); } if (toolWindowVisible) { render(true, true); } }, 300); } }