package org.xmind.ui.internal.views; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.IJobChangeListener; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.draw2d.AbstractHintLayout; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.xmind.core.Core; import org.xmind.core.IBoundary; import org.xmind.core.IControlPoint; import org.xmind.core.IRelationship; import org.xmind.core.ISheet; import org.xmind.core.ITopic; import org.xmind.core.IWorkbook; import org.xmind.core.event.CoreEvent; import org.xmind.core.event.ICoreEventListener; import org.xmind.core.event.ICoreEventRegistration; import org.xmind.core.event.ICoreEventSource; import org.xmind.core.internal.command.Logger; import org.xmind.core.io.ByteArrayStorage; import org.xmind.core.io.IStorage; import org.xmind.core.style.IStyle; import org.xmind.core.style.IStyleSheet; import org.xmind.core.util.FileUtils; import org.xmind.gef.draw2d.SizeableImageFigure; import org.xmind.gef.image.ImageExportUtils; import org.xmind.gef.image.ResizeConstants; import org.xmind.gef.ui.editor.IGraphicalEditor; import org.xmind.gef.ui.editor.IGraphicalEditorPage; import org.xmind.gef.util.Properties; import org.xmind.ui.gallery.GalleryViewer; import org.xmind.ui.gallery.IDecorationContext; import org.xmind.ui.gallery.ILabelDecorator; import org.xmind.ui.internal.MindMapMessages; import org.xmind.ui.internal.MindMapUIPlugin; import org.xmind.ui.mindmap.IMindMapImages; import org.xmind.ui.mindmap.IMindMapViewer; import org.xmind.ui.mindmap.MindMap; import org.xmind.ui.mindmap.MindMapImageExporter; import org.xmind.ui.mindmap.MindMapUI; public class ThemeLabelProvider extends LabelProvider implements ILabelDecorator { private static final String THEME_PREVIEWS_DIR = ".themePreviews"; //$NON-NLS-1$ // All images are made the same size private static final int IMAGE_WIDTH = 800; private static final int IMAGE_HEIGHT = 400; // Makes background layer large enough so that it can fill the image private static final int BACKGROUND_ENLARGEMENT_MARGINS = 400; private static Image defaultImage; private static class ThemePreviewImageProviderManager implements ICoreEventListener, ISchedulingRule { public static final ThemePreviewImageProviderManager INSTANCE = new ThemePreviewImageProviderManager(); private Map<String, Map<IStyle, Image>> imageGroups = new HashMap<String, Map<IStyle, Image>>(); private Map<String, Map<IStyle, ThemePreviewImageLoader>> loaderGroups = new HashMap<String, Map<IStyle, ThemePreviewImageLoader>>(); // private Map<IStyle, Image> images = new HashMap<IStyle, Image>(); // // private Map<IStyle, ThemePreviewImageLoader> loaders = new HashMap<IStyle, ThemeLabelProvider.ThemePreviewImageLoader>(); private ListenerList listeners = new ListenerList(); private ICoreEventRegistration themeRemovalEventReg = null; private IJobChangeListener loaderListener = new JobChangeAdapter() { @Override public void done(IJobChangeEvent event) { super.done(event); ThemePreviewImageLoader loader = (ThemePreviewImageLoader) event .getJob(); Map<IStyle, ThemePreviewImageLoader> loaderGroup = loaderGroups .get(loader.getStructureClass()); if (loaderGroup != null && !loaderGroup.isEmpty()) loaderGroup.remove(loader.getTheme()); Image image = loader.getPreviewImage(); if (image != null) { setPreviewImage(loader.getTheme(), loader.getStructureClass(), image); } } }; private ThemePreviewImageProviderManager() { checkLocalLanguage(); } private void checkLocalLanguage() { String lang = System.getProperty("osgi.nl"); //$NON-NLS-1$ File root = MindMapUIPlugin.getDefault().getStateLocation() .toFile(); File cacheDir = new File(root, THEME_PREVIEWS_DIR); FileUtils.ensureDirectory(cacheDir); File langFile = new File(cacheDir, lang); if (!langFile.exists()) { File[] files = cacheDir.listFiles(); for (File file : files) { if (file.exists()) { file.delete(); } } try { langFile.createNewFile(); } catch (IOException e) { } } } public void addThemePreviewImageListener(ThemeLabelProvider listener) { boolean wasEmpty = listeners.isEmpty(); listeners.add(listener); boolean isEmpty = listeners.isEmpty(); if (wasEmpty && !isEmpty) { if (themeRemovalEventReg != null) { themeRemovalEventReg.unregister(); } themeRemovalEventReg = ((ICoreEventSource) MindMapUI .getResourceManager().getUserThemeSheet()) .registerCoreEventListener(Core.StyleRemove, this); } } public void removeThemePreviewImageListener( ThemeLabelProvider listener) { boolean wasEmpty = listeners.isEmpty(); listeners.remove(listener); boolean isEmpty = listeners.isEmpty(); if (!wasEmpty && isEmpty) { if (themeRemovalEventReg != null) { themeRemovalEventReg.unregister(); themeRemovalEventReg = null; } } } private void fireThemePreviewImageChanged(final IStyle theme) { for (final Object listener : listeners.getListeners()) { SafeRunner.run(new SafeRunnable() { public void run() throws Exception { ((ThemeLabelProvider) listener) .themePreviewImageChanged(theme); } }); } } public void handleCoreEvent(CoreEvent event) { if (Core.StyleRemove.equals(event.getType())) { IStyle theme = (IStyle) event.getTarget(); deletePreviewImageCacheForTheme(theme); } } public Image getThemePreviewImage(IStyle theme, String structureClass) { Image image = null; Map<IStyle, Image> imageGroup = imageGroups.get(structureClass); if (imageGroup != null) image = imageGroup.get(theme); if (image != null) return image; Map<IStyle, ThemePreviewImageLoader> loaderGroup = loaderGroups .get(structureClass); if (loaderGroup == null) { loaderGroup = new HashMap<IStyle, ThemeLabelProvider.ThemePreviewImageLoader>(); loaderGroups.put(structureClass, loaderGroup); } if (!loaderGroup.containsKey(theme)) { // if (!loaders.containsKey(theme)) { ThemePreviewImageLoader loader = new ThemePreviewImageLoader( theme, structureClass, Display.getCurrent()); loader.setRule(this); loader.setSystem(true); loader.addJobChangeListener(loaderListener); loaderGroup.put(theme, loader); MindMapUIPlugin.getDefault().registerJob(loader); loader.schedule(); } return null; } private void deletePreviewImageCacheForTheme(IStyle theme) { for (String structureClass : imageGroups.keySet()) { File cacheFile = getPreviewImageCacheFileForTheme(theme, structureClass); if (cacheFile.exists()) { cacheFile.delete(); } deletePreviewImageCacheInMemory(theme, structureClass); } } private void deletePreviewImageCacheInMemory(final IStyle theme, String structureClass) { Display display = null; // final Image image = images.remove(theme); Map<IStyle, Image> imageGroup = imageGroups.get(structureClass); if (imageGroup != null && !imageGroup.isEmpty()) { final Image image = imageGroup.remove(theme); if (image != null) { display = (Display) image.getDevice(); if (!display.isDisposed()) { display.asyncExec(new Runnable() { public void run() { Display display = Display.getCurrent(); if (!display.isDisposed()) { display.asyncExec(new Runnable() { public void run() { image.dispose(); } }); } } }); } } } // final ThemePreviewImageLoader loader = loaders.remove(theme); Map<IStyle, ThemePreviewImageLoader> loaderGroup = loaderGroups .get(structureClass); if (loaderGroup != null && !loaderGroup.isEmpty()) { final ThemePreviewImageLoader loader = loaderGroup .remove(theme); if (loader != null) { loader.cancel(); } if (display != null && !display.isDisposed()) { display.syncExec(new Runnable() { public void run() { fireThemePreviewImageChanged(theme); } }); } } } private void setPreviewImage(final IStyle theme, String structureClass, Image image) { Display display = null; Map<IStyle, Image> imageGroup = imageGroups.get(structureClass); if (imageGroup == null) { imageGroup = new HashMap<IStyle, Image>(); imageGroups.put(structureClass, imageGroup); } final Image oldImage = imageGroup.put(theme, image); if (oldImage != null) { display = (Display) oldImage.getDevice(); if (!display.isDisposed()) { display.asyncExec(new Runnable() { public void run() { Display display = Display.getCurrent(); if (!display.isDisposed()) { display.asyncExec(new Runnable() { public void run() { oldImage.dispose(); } }); } } }); } } display = (Display) image.getDevice(); if (!display.isDisposed()) { display.syncExec(new Runnable() { public void run() { fireThemePreviewImageChanged(theme); } }); } } public boolean contains(ISchedulingRule rule) { return rule == this; } public boolean isConflicting(ISchedulingRule rule) { return rule == this; } } private static class ThemePreviewImageLoader extends Job { private IStyle theme; private String structureClass; private Display display; private Image previewImage; public ThemePreviewImageLoader(IStyle theme, String structureClass, Display display) { super(NLS.bind(MindMapMessages.ThemeLabel_LoadTheme, theme.getName())); this.theme = theme; this.structureClass = structureClass; this.display = display; this.previewImage = null; } @Override protected IStatus run(IProgressMonitor monitor) { Image image = null; // If cached in file, load preview image from file. File cacheFile = getPreviewImageCacheFile(); if (cacheFile.exists()) { try { image = new Image(display, cacheFile.getAbsolutePath()); } catch (Throwable error) { Logger.log( "Failed to load cached theme preview image from file " //$NON-NLS-1$ + cacheFile.getAbsolutePath(), error); cacheFile.delete(); } } if (monitor.isCanceled()) { if (image != null) image.dispose(); return Status.CANCEL_STATUS; } // If not cached in file, make a preview image and save it to cache file. if (image == null) { try { image = createPreviewImage(); } catch (Throwable fatalError) { return new Status(IStatus.ERROR, MindMapUIPlugin.PLUGIN_ID, "Failed to create preview image for theme (" //$NON-NLS-1$ + theme.getName() + ")", //$NON-NLS-1$ fatalError); } if (image != null) { if (monitor.isCanceled()) { image.dispose(); return Status.CANCEL_STATUS; } FileUtils.ensureFileParent(cacheFile); try { OutputStream out = new FileOutputStream(cacheFile); try { ImageExportUtils.saveImage(image, out, SWT.IMAGE_PNG); } finally { out.close(); } } catch (IOException error) { Logger.log( "Failed to save theme preview image to cache file " //$NON-NLS-1$ + cacheFile.getAbsolutePath(), error); } } } if (image != null) { if (monitor.isCanceled()) { image.dispose(); cacheFile.delete(); return Status.CANCEL_STATUS; } this.previewImage = image; } return Status.OK_STATUS; } private Image createPreviewImage() { return createPreviewImageForTheme(theme, structureClass, display); } private File getPreviewImageCacheFile() { return getPreviewImageCacheFileForTheme(theme, structureClass); } public Image getPreviewImage() { return previewImage; } public IStyle getTheme() { return theme; } public String getStructureClass() { return structureClass; } } private static class Layout extends AbstractHintLayout { private IDecorationContext properties; public Layout(IDecorationContext properties) { this.properties = properties; } public void layout(IFigure container) { Rectangle area = container.getClientArea(); for (Object child : container.getChildren()) { IFigure figure = (IFigure) child; Dimension childSize = figure.getPreferredSize(-1, -1); int childWidth = Math.min(area.width, childSize.width); int childHeight = Math.min(area.height, childSize.height); figure.setBounds( new Rectangle(area.x, area.y, childWidth, childHeight)); } } @Override protected Dimension calculatePreferredSize(IFigure figure, int wHint, int hHint) { if (wHint > -1) wHint = Math.max(0, wHint - figure.getInsets().getWidth()); if (hHint > -1) hHint = Math.max(0, hHint - figure.getInsets().getHeight()); Insets insets = figure.getInsets(); Dimension contentSize = (Dimension) properties .getProperty(GalleryViewer.FrameContentSize, null); if (contentSize != null) return new Dimension(contentSize.width + insets.getWidth(), contentSize.height + insets.getHeight()); Dimension d = new Dimension(); List children = figure.getChildren(); IFigure child; for (int i = 0; i < children.size(); i++) { child = (IFigure) children.get(i); if (!isObservingVisibility() || child.isVisible()) d.union(child.getPreferredSize(wHint, hHint)); } d.expand(figure.getInsets().getWidth(), figure.getInsets().getHeight()); d.union(getBorderPreferredSize(figure)); return d; } } private String structureClass; public ThemeLabelProvider() { ThemePreviewImageProviderManager.INSTANCE .addThemePreviewImageListener(this); } public ThemeLabelProvider(String structureClass) { this.structureClass = structureClass; ThemePreviewImageProviderManager.INSTANCE .addThemePreviewImageListener(this); } @Override public String getText(Object element) { if (element instanceof IStyle && IStyle.THEME.equals(((IStyle) element).getType())) { return ((IStyle) element).getName(); } return super.getText(element); } @Override public Image getImage(Object element) { if (element instanceof IStyle && IStyle.THEME.equals(((IStyle) element).getType())) { return ThemePreviewImageProviderManager.INSTANCE .getThemePreviewImage((IStyle) element, (structureClass == null || "".equals(structureClass)) //$NON-NLS-1$ ? getCurrentCentralStructure() : structureClass); } return super.getImage(element); } @Override public void dispose() { ThemePreviewImageProviderManager.INSTANCE .removeThemePreviewImageListener(this); super.dispose(); } private void themePreviewImageChanged(IStyle theme) { fireLabelProviderChanged(new LabelProviderChangedEvent(this, theme)); } private static File getPreviewImageCacheFileForTheme(IStyle theme, String structureClass) { File root = MindMapUIPlugin.getDefault().getStateLocation().toFile(); File cacheDir = new File(root, THEME_PREVIEWS_DIR); String themeId = theme.getId(); String parentId; IStyleSheet sheet = theme.getOwnedStyleSheet(); if (sheet == MindMapUI.getResourceManager().getSystemThemeSheet()) { parentId = "system"; //$NON-NLS-1$ } else if (sheet == MindMapUI.getResourceManager().getUserThemeSheet()) { parentId = "user"; //$NON-NLS-1$ } else { parentId = "other"; //$NON-NLS-1$ } String fileName = String.format("%s-%s-%s.png", parentId, //$NON-NLS-1$ structureClass, themeId); return new File(cacheDir, fileName); } private static Image createPreviewImageForTheme(IStyle theme, String structureClass, Display display) { IWorkbook workbook = createTemplateWorkbook(structureClass); IStyle appliedTheme = workbook.getStyleSheet().importStyle(theme); if (appliedTheme != null) { workbook.getPrimarySheet().setThemeId(appliedTheme.getId()); } final MindMapImageExporter exporter = new MindMapImageExporter(display); // Enlarge viewer margins to make background large enough Properties props = new Properties(); props.set(IMindMapViewer.VIEWER_MARGIN, BACKGROUND_ENLARGEMENT_MARGINS); exporter.setSource(new MindMap(workbook.getPrimarySheet()), props, new Insets(MindMapUI.DEFAULT_EXPORT_MARGIN)); exporter.setResize(ResizeConstants.RESIZE_STRETCH, IMAGE_WIDTH, IMAGE_HEIGHT); final Image[] image = new Image[1]; if (!display.isDisposed()) { display.syncExec(new Runnable() { public void run() { image[0] = exporter.createImage(); } }); } return image[0]; } private static IWorkbook createTemplateWorkbook(String structureClass) { IStorage tempStorage = new ByteArrayStorage(); IWorkbook workbook = Core.getWorkbookBuilder() .createWorkbook(tempStorage); ISheet sheet = workbook.getPrimarySheet(); ITopic rootTopic = sheet.getRootTopic(); rootTopic.setTitleText(MindMapMessages.TitleText_CentralTopic); rootTopic.setStructureClass( (structureClass == null || "".equals(structureClass)) //$NON-NLS-1$ ? "org.xmind.ui.map.clockwise" : structureClass); //$NON-NLS-1$ ITopic mainTopic1 = workbook.createTopic(); mainTopic1 .setTitleText(NLS.bind(MindMapMessages.TitleText_MainTopic, 1)); rootTopic.add(mainTopic1); ITopic subTopic1 = workbook.createTopic(); subTopic1.setTitleText(NLS.bind(MindMapMessages.TitleText_Subtopic, 1)); mainTopic1.add(subTopic1); ITopic subTopic2 = workbook.createTopic(); subTopic2.setTitleText(NLS.bind(MindMapMessages.TitleText_Subtopic, 2)); mainTopic1.add(subTopic2); ITopic subTopic3 = workbook.createTopic(); subTopic3.setTitleText(NLS.bind(MindMapMessages.TitleText_Subtopic, 3)); mainTopic1.add(subTopic3); ITopic floatingTopic = workbook.createTopic(); floatingTopic.setTitleText(MindMapMessages.TitleText_FloatingTopic); floatingTopic.setPosition(0, -120); rootTopic.add(floatingTopic, ITopic.DETACHED); IBoundary boundary = workbook.createBoundary(); boundary.setTitleText(MindMapMessages.TitleText_Boundary); boundary.setStartIndex(0); boundary.setEndIndex(0); rootTopic.addBoundary(boundary); IRelationship relationship = workbook.createRelationship(); relationship.setTitleText(MindMapMessages.TitleText_Relationship); relationship.setEnd1Id(mainTopic1.getId()); relationship.setEnd2Id(floatingTopic.getId()); IControlPoint cp1 = relationship.getControlPoint(0); cp1.setPosition(50, -100); IControlPoint cp2 = relationship.getControlPoint(1); cp2.setPosition(100, 0); sheet.addRelationship(relationship); return workbook; } public IFigure decorateFigure(IFigure figure, Object element, IDecorationContext context) { List children = figure.getChildren(); boolean needInitFigureContent = children.isEmpty(); if (needInitFigureContent) { SizeableImageFigure themeContentFigure = new SizeableImageFigure( getImage(element)); SizeableImageFigure defaultImageFigure = new SizeableImageFigure( getDefaultImage()); figure.add(themeContentFigure); figure.add(defaultImageFigure); if (context != null) { figure.setLayoutManager(new Layout(context)); boolean imageConstrained = Boolean.TRUE.equals(context .getProperty(GalleryViewer.ImageConstrained, false)); boolean imageStretched = Boolean.TRUE.equals(context .getProperty(GalleryViewer.ImageStretched, false)); themeContentFigure.setConstrained(imageConstrained); themeContentFigure.setStretched(imageStretched); defaultImageFigure.setConstrained(imageConstrained); defaultImageFigure.setStretched(imageStretched); } } int supportSize = 2; children = figure.getChildren(); if (children.size() == supportSize) { Object themeContentFigure = children.get(0); Object defaultImageFigure = children.get(supportSize - 1); if (themeContentFigure instanceof SizeableImageFigure && defaultImageFigure instanceof SizeableImageFigure) { ((SizeableImageFigure) themeContentFigure) .setImage(getImage(element)); IStyle defaultTheme = MindMapUI.getResourceManager() .getDefaultTheme(); ((SizeableImageFigure) defaultImageFigure).setImage( element == defaultTheme ? getDefaultImage() : null); } } return figure; } private static Image getDefaultImage() { if (defaultImage == null) { ImageDescriptor desc = MindMapUI.getImages() .get(IMindMapImages.STAR, true); if (desc != null) { try { defaultImage = desc.createImage(false); } catch (Throwable e) { //e.printStackTrace(); } } } return defaultImage; } private String getCurrentCentralStructure() { if (this.structureClass != null && !"".equals(this.structureClass)) //$NON-NLS-1$ return this.structureClass; String defaultStructureClass = "org.xmind.ui.map.clockwise"; //$NON-NLS-1$ IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench() .getActiveWorkbenchWindow(); if (activeWorkbenchWindow == null) return defaultStructureClass; IWorkbenchPage activePage = activeWorkbenchWindow.getActivePage(); if (activePage == null) return defaultStructureClass; IEditorPart activeEditor = activePage.getActiveEditor(); if (activeEditor == null || !(activeEditor instanceof IGraphicalEditor)) return defaultStructureClass; IGraphicalEditorPage page = ((IGraphicalEditor) activeEditor) .getActivePageInstance(); if (page == null) return defaultStructureClass; ISheet sheet = (ISheet) page.getAdapter(ISheet.class); if (sheet == null) return defaultStructureClass; ITopic topic = sheet.getRootTopic(); return topic.getStructureClass(); } }