/* FeatureIDE - An IDE to support feature-oriented software development * Copyright (C) 2005-2009 FeatureIDE Team, University of Magdeburg * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * See http://www.fosd.de/featureide/ for further information. */ package featureide.fm.ui.editors; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.draw2d.ConnectionLayer; import org.eclipse.gef.DefaultEditDomain; import org.eclipse.gef.EditDomain; import org.eclipse.gef.EditPartViewer; import org.eclipse.gef.GraphicalViewer; import org.eclipse.gef.LayerConstants; import org.eclipse.gef.commands.CommandStack; import org.eclipse.gef.editparts.ScalableFreeformRootEditPart; import org.eclipse.gef.editparts.ZoomManager; import org.eclipse.gef.ui.actions.GEFActionConstants; import org.eclipse.gef.ui.actions.PrintAction; import org.eclipse.gef.ui.actions.RedoAction; import org.eclipse.gef.ui.actions.SelectAllAction; import org.eclipse.gef.ui.actions.UndoAction; import org.eclipse.gef.ui.actions.ZoomInAction; import org.eclipse.gef.ui.actions.ZoomOutAction; import org.eclipse.gef.ui.parts.GraphicalViewerImpl; import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler; import org.eclipse.gef.ui.parts.ScrollingGraphicalViewer; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.text.IDocument; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.ui.IEditorActionBarContributor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.part.MultiPageEditorPart; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.ITextEditor; import org.sat4j.specs.TimeoutException; import featureide.fm.core.FeatureModel; import featureide.fm.core.GrammarFile; import featureide.fm.core.PropertyConstants; import featureide.fm.core.io.IFeatureModelReader; import featureide.fm.core.io.IFeatureModelWriter; import featureide.fm.core.io.ModelWarning; import featureide.fm.core.io.UnsupportedModelException; import featureide.fm.core.io.guidsl.FeatureModelReader; import featureide.fm.core.io.guidsl.FeatureModelWriter; import featureide.fm.core.io.xml.XmlFeatureModelWriter; import featureide.fm.ui.editors.featuremodel.FeatureModelEditorContributor; import featureide.fm.ui.editors.featuremodel.GEFImageWriter; import featureide.fm.ui.editors.featuremodel.GUIDefaults; import featureide.fm.ui.editors.featuremodel.actions.AlternativeAction; import featureide.fm.ui.editors.featuremodel.actions.AndAction; import featureide.fm.ui.editors.featuremodel.actions.CreateCompoundAction; import featureide.fm.ui.editors.featuremodel.actions.CreateLayerAction; import featureide.fm.ui.editors.featuremodel.actions.DeleteAction; import featureide.fm.ui.editors.featuremodel.actions.MandantoryAction; import featureide.fm.ui.editors.featuremodel.actions.OrAction; import featureide.fm.ui.editors.featuremodel.editparts.GraphicalEditPartFactory; import featureide.fm.ui.editors.featuremodel.layouts.FeatureDiagramLayoutManager; import featureide.fm.ui.editors.featuremodel.layouts.LevelOrderLayout; /** * A multi page editor to edit feature models. If the model file contains * errors, markers will be created on save. * * @author Thomas Thuem */ public class FeatureModelEditor extends MultiPageEditorPart implements GUIDefaults, PropertyConstants, PropertyChangeListener { public static final String ID = "featureide.fm.ui.editors.GrammarEditor"; private GraphicalViewerImpl graphicalViewer; private TextEditor textEditor; private int graphicalViewerIndex; private int textEditorIndex; private boolean isPageModified; private FeatureModel featureModel; private IFeatureModelReader featureModelReader; private IFeatureModelWriter featureModelWriter; private IFeatureModelWriter xmlFeatureModelWriter; private CreateLayerAction createLayerAction; private CreateCompoundAction createCompoundAction; private DeleteAction deleteAction; private MandantoryAction mandantoryAction; private AndAction andAction; private OrAction orAction; private AlternativeAction alternativeAction; private PrintAction printAction; private SelectAllAction selectAllAction; private UndoAction undoAction; private RedoAction redoAction; private ZoomInAction zoomIn; private ZoomOutAction zoomOut; private FeatureDiagramLayoutManager layoutManager = new LevelOrderLayout(); private GrammarFile grammarFile; private FeatureModel originalFeatureModel; private ZoomManager zoomManager; private ScalableFreeformRootEditPart rootEditPart; @Override protected void setInput(IEditorInput input) { IFile file = (IFile) input.getAdapter(IFile.class); grammarFile = new GrammarFile(file); setPartName(file.getProject().getName() + " Model"); setTitleToolTip(input.getToolTipText()); super.setInput(input); featureModel = new FeatureModel(); featureModel.addListener(this); featureModelReader = new FeatureModelReader(featureModel); featureModelWriter = new FeatureModelWriter(featureModel); xmlFeatureModelWriter = new XmlFeatureModelWriter(featureModel); originalFeatureModel = new FeatureModel(); try { new FeatureModelReader(originalFeatureModel).readFromFile(file); } catch (Exception e) { } } public FeatureModel getOriginalFeatureModel() { return originalFeatureModel; } @Override protected void createPages() { createDiagramPage(); createSourcePage(); createActions(); createContextMenu(); } void createDiagramPage() { graphicalViewer = new ScrollingGraphicalViewer(); graphicalViewer.setKeyHandler(new GraphicalViewerKeyHandler( graphicalViewer)); graphicalViewer.createControl(getContainer()); initializeGraphicalViewer(); graphicalViewer.setEditDomain(new DefaultEditDomain(this)); initDiagramContent(); graphicalViewerIndex = addPage(graphicalViewer.getControl()); setPageText(graphicalViewerIndex, "Feature Diagram"); zoomManager = rootEditPart.getZoomManager(); zoomManager.setZoomLevels(new double[] {0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 1.00, 1.10, 1.25, 1.50, 2.00, 2.50, 3.00, 4.00}); } void createSourcePage() { textEditor = new TextEditor(); try { textEditorIndex = addPage(textEditor, getEditorInput()); setPageText(textEditorIndex, "Source"); } catch (PartInitException e) { e.printStackTrace(); } } private void createActions() { createLayerAction = new CreateLayerAction(graphicalViewer, featureModel); createCompoundAction = new CreateCompoundAction(graphicalViewer, featureModel); deleteAction = new DeleteAction(graphicalViewer, featureModel); mandantoryAction = new MandantoryAction(graphicalViewer, featureModel); andAction = new AndAction(graphicalViewer, featureModel); orAction = new OrAction(graphicalViewer, featureModel); alternativeAction = new AlternativeAction(graphicalViewer, featureModel); printAction = new PrintAction(this); selectAllAction = new SelectAllAction(this); undoAction = new UndoAction(this); redoAction = new RedoAction(this); zoomIn = new ZoomInAction(zoomManager); zoomOut = new ZoomOutAction(zoomManager); } private void createContextMenu() { MenuManager menu = new MenuManager("#PopupMenu"); menu.setRemoveAllWhenShown(true); menu.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { FeatureModelEditor.this.fillContextMenu(manager); } }); menu.createContextMenu(graphicalViewer.getControl()); graphicalViewer.setContextMenu(menu); getSite().registerContextMenu(menu, graphicalViewer); } private void fillContextMenu(IMenuManager menu) { if (andAction.isEnabled() || orAction.isEnabled() || alternativeAction.isEnabled()) { menu.add(andAction); menu.add(orAction); menu.add(alternativeAction); } else { menu.add(createLayerAction); menu.add(createCompoundAction); menu.add(deleteAction); menu.add(mandantoryAction); } menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); } public IAction getDiagramAction(String workbenchActionID) { if (CreateLayerAction.ID.equals(workbenchActionID)) return createLayerAction; if (CreateCompoundAction.ID.equals(workbenchActionID)) return createCompoundAction; if (DeleteAction.ID.equals(workbenchActionID)) return deleteAction; if (MandantoryAction.ID.equals(workbenchActionID)) return mandantoryAction; if (AndAction.ID.equals(workbenchActionID)) return andAction; if (OrAction.ID.equals(workbenchActionID)) return orAction; if (AlternativeAction.ID.equals(workbenchActionID)) return alternativeAction; if (ActionFactory.PRINT.getId().equals(workbenchActionID)) return printAction; if (ActionFactory.SELECT_ALL.getId().equals(workbenchActionID)) return selectAllAction; if (ActionFactory.UNDO.getId().equals(workbenchActionID)) return undoAction; if (ActionFactory.REDO.getId().equals(workbenchActionID)) return redoAction; if (GEFActionConstants.ZOOM_IN.equals(workbenchActionID)) return zoomIn; if (GEFActionConstants.ZOOM_OUT.equals(workbenchActionID)) return zoomOut; System.out.println("The following workbench action is not registered at the feature diagram editor: " + workbenchActionID); return null; } void initDiagramContent() { graphicalViewer.getControl().getDisplay().asyncExec(new Runnable() { public void run() { graphicalViewer.setContents(featureModel); isPageModified = true; pageChange(graphicalViewerIndex); } }); } public boolean updateDiagramFromTextEditor() { IDocumentProvider provider = textEditor.getDocumentProvider(); IDocument document = provider.getDocument(textEditor.getEditorInput()); String text = document.get(); grammarFile.deleteAllModelMarkers(); try { featureModelReader.readFromString(text); for (ModelWarning warning : featureModelReader.getWarnings()) grammarFile.createModelMarker(warning.message, IMarker.SEVERITY_WARNING, warning.line); try { if (!featureModel.isValid()) grammarFile.createModelMarker( "The feature model is void, i.e., it contains no products", IMarker.SEVERITY_ERROR, 0); } catch (TimeoutException e) { //do nothing, assume the model is correct } } catch (UnsupportedModelException e) { grammarFile.createModelMarker(e.getMessage(), IMarker.SEVERITY_ERROR, e.lineNumber); return false; } return true; } void initializeGraphicalViewer() { graphicalViewer.getControl().setBackground(DIAGRAM_BACKGROUND); graphicalViewer.setEditPartFactory(new GraphicalEditPartFactory()); rootEditPart = new ScalableFreeformRootEditPart(); ((ConnectionLayer) rootEditPart .getLayer(LayerConstants.CONNECTION_LAYER)) .setAntialias(SWT.ON); graphicalViewer.setRootEditPart(rootEditPart); } void updateTextEditorFromDiagram() { String text = featureModelWriter.writeToString(); IDocumentProvider provider = textEditor.getDocumentProvider(); IDocument document = provider.getDocument(textEditor.getEditorInput()); document.set(text); } public void diagramModified() { if (isPageModified) return; boolean wasDirty = isDirty(); isPageModified = true; if (!wasDirty) firePropertyChange(IEditorPart.PROP_DIRTY); } @Override protected void handlePropertyChange(int propertyId) { if (propertyId == IEditorPart.PROP_DIRTY) isPageModified = isDirty(); super.handlePropertyChange(propertyId); } @Override public boolean isDirty() { return isPageModified || super.isDirty(); } @Override public void setFocus() { if (getActivePage() == graphicalViewerIndex) graphicalViewer.getControl().setFocus(); else textEditor.setFocus(); } @Override protected void pageChange(int newPageIndex) { if (newPageIndex == textEditorIndex) { if (isPageModified) updateTextEditorFromDiagram(); } else { // newPageIndex == graphicalViewerIndex if (isDirty() || grammarFile.hasModelMarkers()) if (!updateDiagramFromTextEditor()) { // there are errors in the file, stay at this editor page isPageModified = false; setActivePage(textEditorIndex); return; } } isPageModified = false; IEditorActionBarContributor contributor = getEditorSite() .getActionBarContributor(); if (contributor instanceof FeatureModelEditorContributor) ((FeatureModelEditorContributor) contributor).setActivePage(this, newPageIndex); super.pageChange(newPageIndex); } @Override public void doSave(IProgressMonitor monitor) { if (getActivePage() == graphicalViewerIndex && isPageModified) { updateTextEditorFromDiagram(); setActivePage(textEditorIndex); setActivePage(graphicalViewerIndex); } else updateDiagramFromTextEditor(); isPageModified = false; featureModel.performRenamings(); textEditor.doSave(monitor); try { new FeatureModelReader(originalFeatureModel) .readFromFile(grammarFile.getResource()); } catch (Exception e) { e.printStackTrace(); } } @Override public boolean isSaveAsAllowed() { return true; } @Override public void doSaveAs() { FileDialog fileDialog = new FileDialog(getEditorSite().getShell(), SWT.SAVE); String[] extensions = { "*.bmp", // "*.gif", // "*.ico", "*.jpg", "*.m", "*.png", // "*.tif", "*.xml" }; fileDialog.setFilterExtensions(extensions); String[] filterNames = { "Windows Bitmap *.bmp", // "CompuServe GIF *.gif", // "Windows Icon *.ico", "JPEG *.jpg", "GUIDSL Grammar *.m", "Portable Network Graphics *.png", // "TIFF *.tif", "XML Export *.xml"}; fileDialog.setFilterNames(filterNames); fileDialog.setOverwrite(true); String filePath = fileDialog.open(); if (filePath == null) return; File file = new File(filePath); if (filePath.endsWith(".m")) { featureModelWriter.writeToFile(file); } else if (filePath.endsWith(".xml")) { xmlFeatureModelWriter.writeToFile(file); } else { GEFImageWriter.writeToFile(graphicalViewer, file); } } public void propertyChange(PropertyChangeEvent event) { String prop = event.getPropertyName(); if (prop.equals(MODEL_DATA_CHANGED)) { refreshGraphicalViewer(); isPageModified = true; firePropertyChange(PROP_DIRTY); } else if (prop.equals(MODEL_DATA_LOADED)) { refreshGraphicalViewer(); } } private void refreshGraphicalViewer() { if (graphicalViewer.getContents() == null) return; // refresh size of all feature figures graphicalViewer.getContents().refresh(); // layout all features Point size = graphicalViewer.getControl().getSize(); layoutManager.setControlSize(size.x, size.y); layoutManager.layout(featureModel); // refresh position of all feature figures graphicalViewer.getContents().refresh(); } @SuppressWarnings("unchecked") @Override public Object getAdapter(Class adapter) { if (GraphicalViewer.class.equals(adapter) || EditPartViewer.class.equals(adapter)) return graphicalViewer; if (ZoomManager.class.equals(adapter)) return zoomManager; if (CommandStack.class.equals(adapter)) return graphicalViewer.getEditDomain().getCommandStack(); if (EditDomain.class.equals(adapter)) return graphicalViewer.getEditDomain(); if (IGotoMarker.class.equals(adapter)) if (getActivePage() == graphicalViewerIndex) setActivePage(textEditorIndex); return super.getAdapter(adapter); } @Override public int getActivePage() { return super.getActivePage(); } public ITextEditor getSourceEditor() { return textEditor; } public FeatureModel getFeatureModel() { return featureModel; } public GrammarFile getGrammarFile() { return grammarFile; } }