/***************************************************************************** * Copyright (c) 2010 CEA LIST. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation *****************************************************************************/ package org.eclipse.papyrus.customization.properties.editor.preview; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.facet.infra.browser.uicore.internal.model.ITreeElement; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.papyrus.customization.properties.Activator; import org.eclipse.papyrus.customization.properties.editor.UIEditor; import org.eclipse.papyrus.customization.properties.messages.Messages; import org.eclipse.papyrus.customization.properties.model.xwt.resource.XWTResource; import org.eclipse.papyrus.views.properties.contexts.Section; import org.eclipse.papyrus.views.properties.contexts.Tab; import org.eclipse.papyrus.views.properties.contexts.View; import org.eclipse.papyrus.views.properties.runtime.DefaultDisplayEngine; import org.eclipse.papyrus.views.properties.runtime.DisplayEngine; import org.eclipse.papyrus.views.properties.widgets.layout.PropertiesLayout; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.part.ViewPart; /** * The Preview section of the Customization editor. * Displays the given view in a Tab Folder. * The preview uses the same Display Engine as the Property view, which should lead * to the same results, with a few exceptions : * - The Tab Folder has an Horizontal layout for its tabs, while the property view has * a vertical layout * - The preview is read-only : all actions are disabled. This means that it is * not possible to preview dialogs, for example * - The Enum and References fields are empty * - Some buttons may be marked as disabled, but will be available at runtime (Or vice-versa) * - The dynamic sections are always displayed * * The preview can be disabled for performance issues * * @author Camille Letavernier */ public class Preview extends ViewPart implements ISelectionChangedListener, IPartListener { private Composite parent; private ScrolledComposite scrolledParent; private CTabFolder contents; private DisplayEngine displayEngine; private String selectedTab; private boolean enabled = true; private View currentView; private Label previewTitle; private Label previewDisabled; private Set<UIEditor> currentEditors = new HashSet<UIEditor>(); private IWorkbenchPage activePage; /** * Constructor. * Constructs a new Preview in a View. The preview will change depending * on the current active UIEditor. */ public Preview() { } /** * Constructor. * Constructs a new Preview in an editor (Embedded preview) * * @param editor */ public Preview(UIEditor editor) { this.currentEditors.add(editor); } /** * Creates the preview control in the given composite. * * @param container * The SWT Composite in which the preview should be displayed */ @Override public void createPartControl(Composite container) { scrolledParent = new ScrolledComposite(container, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); scrolledParent.getVerticalBar().setIncrement(10); scrolledParent.setBackground(new Color(scrolledParent.getDisplay(), 255, 255, 255)); scrolledParent.setBackgroundMode(SWT.INHERIT_DEFAULT); scrolledParent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); parent = new Composite(scrolledParent, SWT.NONE); parent.setLayout(new GridLayout(1, false)); scrolledParent.setContent(parent); Composite controls = new Composite(parent, SWT.NONE); controls.setLayout(new GridLayout(5, false)); controls.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); GridData data; // Label preview = new Label(controls, SWT.NONE); // preview.setImage(Activator.getDefault().getImage("/icons/preview.png")); //$NON-NLS-1$ // data = new GridData(SWT.CENTER, SWT.BEGINNING, false, false); // preview.setLayoutData(data); // Label previewText = new Label(controls, SWT.NONE); // previewText.setText(Messages.Preview_preview); // data = new GridData(SWT.CENTER, SWT.BEGINNING, false, false); // previewText.setLayoutData(data); previewTitle = new Label(controls, SWT.NONE); data = new GridData(SWT.CENTER, SWT.BEGINNING, false, false); previewTitle.setLayoutData(data); // final Button togglePreview = new Button(controls, SWT.CHECK); // togglePreview.setText(Messages.Preview_disablePreview); // togglePreview.addSelectionListener(new SelectionListener() { // // public void widgetSelected(SelectionEvent e) { // enabled = !enabled; // displayView(); // } // // public void widgetDefaultSelected(SelectionEvent e) { // //Nothing // } // // }); previewDisabled = new Label(parent, SWT.NONE); previewDisabled.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); contents = new CTabFolder(parent, SWT.VERTICAL); //If currentEditors is empty, the preview is displayed in a view and //should listen to the workbench to know about the activeEditor if(currentEditors.isEmpty()) { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if(window != null) { activePage = window.getActivePage(); if(activePage != null) { IEditorPart editorPart = activePage.getActiveEditor(); if(editorPart instanceof UIEditor) { setEditor((UIEditor)editorPart); } activePage.addPartListener(this); } else { Activator.log.warn("There is no active page"); //$NON-NLS-1$ } } else { Activator.log.warn("There is no current window"); //$NON-NLS-1$ } } displayView(); } /** * Sets the current Editor * * @param editor */ public void setEditor(UIEditor editor) { editor.addPreview(this); currentEditors.add(editor); } private void refreshDisplay() { Point size = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT); if(scrolledParent.getSize().x > 0) { size.x = scrolledParent.getSize().x - 30; } parent.setSize(size); } /** * Saves the section's XWT Resource in a temporary file, which * can then be interpreted by XWT. Returns the URL to this file. * * @param section * The section for which we want to persist the XWT Resource * @return * The URL to the XWT Resource */ private URL saveTmp(Section section) { if(section.getSectionFile() == null || section.getWidget() == null) { return null; } IPath path = Activator.getDefault().getPreferencesPath(); path = path.append("/preview/"); //$NON-NLS-1$ try { File previewDirectory = path.toFile(); if(!previewDirectory.exists()) { previewDirectory.mkdirs(); } File xwtFile = path.append(section.getSectionFile()).toFile(); if(!xwtFile.exists()) { xwtFile.getParentFile().mkdirs(); xwtFile.createNewFile(); } OutputStream os = new FileOutputStream(xwtFile); Map<Object, Object> options = new HashMap<Object, Object>(); //The outputstream cannot be formatted. If format is true, this is //the real file (and not the preview file) that will be formatted options.put(XWTResource.OPTION_FORMAT, false); if(section.getWidget() == null || section.getWidget().eResource() == null) { return null; } section.getWidget().eResource().save(os, options); return xwtFile.toURI().toURL(); } catch (IOException ex) { Activator.log.error(ex); } return null; } /** * Sets the view to display in the preview * * @param view * The view to display */ public void setView(View view) { this.currentView = view; if(view != null) { if(view.getName() == null) { previewTitle.setText(Messages.Preview_Unnamed); } else { previewTitle.setText(view.getName()); } } else { previewTitle.setText(""); //$NON-NLS-1$ } previewTitle.getParent().layout(); displayView(); } private void setPreviewError(String message) { if(message != null) { previewDisabled.setText(message); previewDisabled.setVisible(true); } else { previewDisabled.setVisible(false); } refreshDisplay(); } /** * Display the current view. When the view to display has changed, * you should call {@link #setView(View)} instead. */ public void displayView() { contents.dispose(); if(!enabled) { setPreviewError(Messages.Preview_previewIsDisabled); return; } if(currentView == null) { setPreviewError(Messages.Preview_noSelectedView); return; } setPreviewError(null); displayEngine = new DefaultDisplayEngine(); Map<Tab, Composite> tabs = new HashMap<Tab, Composite>(); contents = new CTabFolder(parent, SWT.NONE); contents.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); boolean activeTab = false; for(Tab tab : getTabs(currentView)) { CTabItem tabItem = new CTabItem(contents, SWT.NONE); tabItem.setText(tab.getLabel()); if(tab.getLabel().equals(selectedTab) || !activeTab) { contents.setSelection(tabItem); activeTab = true; } Composite tabControl = new Composite(contents, SWT.NONE); tabControl.setLayout(new PropertiesLayout()); tabItem.setControl(tabControl); tabs.put(tab, tabControl); tabControl.setEnabled(false); contents.addListener(SWT.Selection, new Listener() { public void handleEvent(Event event) { selectedTab = contents.getSelection().getText(); } }); } for(Section section : currentView.getSections()) { Composite tabControl = tabs.get(section.getTab()); if (tabControl == null){ Activator.log.warn("The section doesn't have a tab"); //Bug in section deletion: it is still referenced by the views continue; } Composite pView = new Composite(tabControl, SWT.NONE); pView.setLayout(new GridLayout(1, false)); URL sectionURL = saveTmp(section); if(sectionURL != null) { displayEngine.createSection(pView, section, sectionURL, null); } } refreshDisplay(); } private Collection<Tab> getTabs(View view) { List<Tab> tabs = new LinkedList<Tab>(); for(Section section : view.getSections()) { Tab tab = section.getTab(); if(tab != null && !tabs.contains(tab)) { tabs.add(tab); } } Collections.sort(tabs, new Comparator<Tab>() { public int compare(Tab tab1, Tab tab2) { Tab afterTab1 = tab1.getAfterTab(); Tab afterTab2 = tab2.getAfterTab(); if(isAfter(tab1, afterTab2, new HashSet<Tab>())) { return -1; } if(isAfter(tab2, afterTab1, new HashSet<Tab>())) { return 1; } return 0; } }); return tabs; } private boolean isAfter(Tab tab1, Tab tab2, Set<Tab> checkedTabs) { if(checkedTabs.contains(tab2)) { Activator.log.warn("Loop in the afterTabs"); //$NON-NLS-1$ return false; } checkedTabs.add(tab2); if(tab2 == null) { return false; } if(tab1.equals(tab2)) { return true; } return isAfter(tab1, tab2.getAfterTab(), checkedTabs); } /** * The preview listens on * * @param event * The SelectionChangedEvent */ public void selectionChanged(SelectionChangedEvent event) { IStructuredSelection selection = (IStructuredSelection)event.getSelection(); if(selection.size() == 1) { ITreeElement child = null; ITreeElement element = (ITreeElement)selection.getFirstElement(); do { if(element instanceof IAdaptable) { EObject adapter = (EObject)((IAdaptable)element).getAdapter(EObject.class); if(adapter instanceof View) { setView((View)adapter); return; } } child = element; element = element.getTreeParent(); } while(child != element && element != null); } } /** * Activate or deactivate the preview * * @param enabled * If true, the preview will be activated. Otherwise, it will * be disabled */ public void setEnabled(boolean enabled) { this.enabled = enabled; } @Override public void setFocus() { parent.setFocus(); } public void partActivated(IWorkbenchPart part) { if(part instanceof UIEditor) { setEditor((UIEditor)part); } } public void partBroughtToTop(IWorkbenchPart part) { //Nothing } public void partClosed(IWorkbenchPart part) { //Nothing } public void partDeactivated(IWorkbenchPart part) { //Nothing } public void partOpened(IWorkbenchPart part) { //Nothing } @Override public void dispose() { for(UIEditor editor : currentEditors) { editor.removePreview(this); } if(activePage != null) { activePage.removePartListener(this); } super.dispose(); } }