/******************************************************************************* * Copyright (c) 2007 VTT Technical Research Centre of Finland and others. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.proconf.g3d.base; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.List; import javax.vecmath.Tuple3d; import javax.vecmath.Vector3d; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Menu; import org.eclipse.ui.IActionBars; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.internal.WorkbenchWindow; import org.simantics.db.Graph; import org.simantics.db.GraphRequestAdapter; import org.simantics.db.GraphRequestStatus; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.events.GraphChangeEvent; import org.simantics.db.management.ISessionContext; import org.simantics.layer0.utils.EntityFactory; import org.simantics.layer0.utils.IEntity; import org.simantics.proconf.g3d.Activator; import org.simantics.proconf.g3d.actions.CameraAction; import org.simantics.proconf.g3d.actions.ContextAction; import org.simantics.proconf.g3d.actions.InteractiveAction; import org.simantics.proconf.g3d.base.SelectionAdapter.SelectionType; import org.simantics.proconf.g3d.common.JmeComposite; import org.simantics.proconf.g3d.common.JmeSinglePassRenderingComponent; import org.simantics.proconf.g3d.common.OrbitalCamera; import org.simantics.proconf.g3d.dialogs.JMEDialog; import org.simantics.proconf.g3d.dnd.ShapeDropTarget; import org.simantics.proconf.g3d.gizmo.Gizmo; import org.simantics.proconf.g3d.input.InputProvider; import org.simantics.proconf.g3d.input.SWTInputProvider; import org.simantics.proconf.g3d.scenegraph.IGeometryNode; import org.simantics.proconf.g3d.scenegraph.IGraphicsNode; import org.simantics.proconf.g3d.tools.ScenegraphLockTraverser; import org.simantics.utils.ui.ErrorLogger; import org.simantics.utils.ui.jface.MenuTools; import com.jme.math.Ray; import com.jme.math.Vector2f; import com.jme.math.Vector3f; import com.jme.renderer.ColorRGBA; public abstract class ThreeDimensionalEditorBase implements Runnable { private Resource inputResource; private List editorContributions = new ArrayList(); private EditorContribution currentEditorContribution; protected List actions = new ArrayList(); private List contributionSelectionActions = new ArrayList(); protected Composite parent; protected ISessionContext sessionContext; protected Session session; protected ScenegraphAdapter adapter; protected SelectionAdapter selectionAdapter; protected Action refreshAction; protected Action configureJMEAction; private Action lockScenegraphAction; protected Menu contextMenu; private JmeComposite renderingComposite = null; protected OrbitalCamera camera = new OrbitalCamera(); protected boolean viewChanged = true; private InteractiveAction currentAction = null; private Gizmo currentGizmo = null; private InteractiveAction cameraAction = null; private JmeRenderingComponent component = null; protected InputProvider input = null; protected ShapeDropTarget dropTarget; // protected IEditorActionBarContributor actionBarContributor; protected IActionBars actionBars; protected IToolBarManager toolBarManager; protected IMenuManager menuManager; public ThreeDimensionalEditorBase(ISessionContext session) { this.sessionContext = session; this.session = session.getSession(); component = new JmeSinglePassRenderingComponent(); } public ThreeDimensionalEditorBase(ISessionContext session, JmeRenderingComponent component) { this.sessionContext = session; this.session = session.getSession(); this.component = component; } protected void setRenderingComponent(JmeRenderingComponent component) { assert(renderingComposite == null); // ensure that this is called before initialization this.component = component; } // public void setActionBarContributor(IEditorActionBarContributor contributor) { // actionBarContributor = contributor; // } public void setActionBars(IActionBars actionBars) { this.actionBars = actionBars; this.menuManager = actionBars.getMenuManager(); this.toolBarManager = actionBars.getToolBarManager(); } public ISessionContext getSessionContext() { return sessionContext; } public Session getSession() { return session; } /** * Creates basic UI for ThreeDimenionalEditors. * Note : inputResource has not been set at this point. * * @param graph * @param parent */ public void createControl(Graph graph, Composite parent) { this.parent = parent; renderingComposite = new JmeComposite(parent,component); // add listeners to force repaint on size changes renderingComposite.getCanvas().addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { viewChanged = true; } }); input = new SWTInputProvider(); renderingComposite.initGL(); camera.setCamera(component.getCamera()); camera.updateCamera(); makeActions(graph); hookContextMenu(); // provide selection events for properies view this.adapter = createScenegraphAdapter(); this.selectionAdapter = createSelectionAdapter(); this.selectionAdapter.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { setCurrentAction(getDefaultAction()); } }); hookDragAndDrop(); hookInput(); VisualizationScheduler.getInstance().addVisualization(this); if (editorContributions.size() > 0) { // setActiveEditorContribution(editorContributions.get(0)); // } else if (editorContributions.size() > 1) { // create actions for selecting contribution for (EditorContribution ec : editorContributions) { final EditorContribution e = ec; Action a = new Action(e.getName(),Action.AS_RADIO_BUTTON) { @Override public void run() { setActiveEditorContribution(e); } }; contributionSelectionActions.add(a); } } } public void addEditorContribution(EditorContribution e) { if (parent != null) throw new RuntimeException("Editor contributions must be added before editor is created."); editorContributions.add(e); } private void initializeEditorContributions(Graph graph) { for (EditorContribution e : editorContributions) { e.initialize(graph); } if (editorContributions.size() > 0) parent.getDisplay().asyncExec(new Runnable() { @Override public void run() { setActiveEditorContribution(editorContributions.get(0)); } }); } private void hookInput() { renderingComposite.getCanvas().addKeyListener((SWTInputProvider) input); renderingComposite.getCanvas().addMouseListener((SWTInputProvider) input); renderingComposite.getCanvas().addMouseMoveListener((SWTInputProvider) input); renderingComposite.getCanvas().addMouseTrackListener((SWTInputProvider) input); renderingComposite.getCanvas().addFocusListener((SWTInputProvider) input); } protected abstract ScenegraphAdapter createScenegraphAdapter(); protected abstract SelectionAdapter createSelectionAdapter(); public JmeComposite getRenderingComposite() { return renderingComposite; } public JmeRenderingComponent getRenderingComponent() { return component; } public ScenegraphAdapter getScenegraphAdapter() { return adapter; } public SelectionAdapter getSelectionAdapter() { return selectionAdapter; } public OrbitalCamera getCamera() { return camera; } public void setViewChanged(boolean b) { viewChanged = b; } protected void hookContextMenu() { MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { final IMenuManager m = manager; GraphRequestAdapter r = new GraphRequestAdapter() { @Override public GraphRequestStatus perform(Graph g) throws Exception { ThreeDimensionalEditorBase.this.fillContextMenu(g,m); return GraphRequestStatus.transactionComplete(); } }; session.syncRead(r); } }); contextMenu = menuMgr.createContextMenu(renderingComposite); } protected void fillContextMenu(Graph graph,IMenuManager manager) { manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); List selected = selectionAdapter.getSelectedResources(); for (ContextAction action : actions) { if(action.usable(graph,selected)) { manager.add(action); } } if (currentEditorContribution != null) { currentEditorContribution.fillContextMenu(graph, manager, selectionAdapter.getCurrentSelection()); for (ContextAction action : currentEditorContribution.getActions()) { if(action.usable(graph,selected)) { manager.add(action); } } } } protected void fillLocalToolBar() { if (currentEditorContribution != null) currentEditorContribution.fillLocalToolBar(toolBarManager); } protected void fillLocalPullDown() { for (Action a : contributionSelectionActions) { IMenuManager menu = MenuTools.getOrCreate(getMenuID(),"View", menuManager); menu.add(a); } MenuTools.getOrCreate(getMenuID(),"Advanced", menuManager).add(refreshAction); if (configureJMEAction != null) { MenuTools.getOrCreate(getMenuID(),"Advanced", menuManager).add(configureJMEAction); } MenuTools.getOrCreate(getMenuID(),"Advanced", menuManager).add(lockScenegraphAction); if (currentEditorContribution != null) currentEditorContribution.fillLocalPullDown(menuManager); } public String getMenuID() { return Long.toString(getInputResource().getResourceId()); } protected void makeActions(Graph graph) { refreshAction = new Action() { public void run() { GraphRequestAdapter r = new GraphRequestAdapter() { public GraphRequestStatus perform(Graph g) throws Exception { // Stack stack = new Stack(); // stack.push(adapter.getNode(adapter.getRootResource())); // while(!stack.isEmpty()) { // IGraphicsNode node = stack.pop(); // stack.addAll(node.getChildren()); // if (node instanceof IGeometryNode) { // ((IGeometryNode)node).updateGeometry(g); // } // } for (IGraphicsNode node : adapter.getNodes()) if (node instanceof IGeometryNode) ((IGeometryNode)node).updateGeometry(g); viewChanged = true; return GraphRequestStatus.transactionComplete(); }; }; session.asyncRead(r); } }; refreshAction.setText("Refresh"); refreshAction.setToolTipText("Refreshes the visualization"); refreshAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages().getImageDescriptor( ISharedImages.IMG_TOOL_UP)); if (getRenderingComponent() instanceof JmeSinglePassRenderingComponent) { configureJMEAction = new Action() { public void run() { JmeSinglePassRenderingComponent c = (JmeSinglePassRenderingComponent)getRenderingComponent(); JMEDialog dialog = new JMEDialog(ThreeDimensionalEditorBase.this.parent.getShell()); c.getDisplaySystem().setCurrent(); dialog.setBounds(c.isShowBounds()); dialog.setNormals(c.isShowNormals()); dialog.setWireframe(c.isShowWireframe()); ColorRGBA col = c.getDisplaySystem().getRenderer().getBackgroundColor(); dialog.setFloatColor(new float[]{col.r,col.g,col.b}); if (dialog.open() == JMEDialog.CANCEL) return; c.setShowBounds(dialog.isBounds()); c.setShowNormals(dialog.isNormals()); c.setShowWireframe(dialog.isWireframe()); if (dialog.getFloatColor() != null) { c.getDisplaySystem().setCurrent(); c.getDisplaySystem().getRenderer().setBackgroundColor(new ColorRGBA(dialog.getFloatColor()[0],dialog.getFloatColor()[1],dialog.getFloatColor()[2],0.f)); } } }; configureJMEAction.setText("Configure JME"); configureJMEAction.setImageDescriptor(Activator.imageDescriptorFromPlugin("fi.vtt.proconf.ode", "icons/silk/wrench.png")); } lockScenegraphAction = new Action("Lock scenegraph",Action.AS_CHECK_BOX) { public void run() { new ScenegraphLockTraverser(adapter.getRoot(),this.isChecked()); } }; cameraAction = new CameraAction(this); currentAction = cameraAction; } public InteractiveAction getDefaultAction() { return cameraAction; } public void createPickRay(Vector3d o, Vector3d d) { Ray r = createPickRay(); o.x = r.origin.x; o.y = r.origin.y; o.z = r.origin.z; d.x = r.direction.x; d.y = r.direction.y; d.z = r.direction.z; d.normalize(); } public Ray createPickRay() { Vector2f screenPos = new Vector2f(); screenPos.set(input.mouseX(),renderingComposite.getBounds().height - input.mouseY()); Ray mouseRay; if (component.getCamera().isParallelProjection()) { Vector3f worldCoords = renderingComposite.getDisplaySystem().getWorldCoordinates(screenPos, 0.0f); mouseRay = new Ray(worldCoords, component.getCamera().getDirection()); } else { Vector3f worldCoords = renderingComposite.getDisplaySystem().getWorldCoordinates(screenPos, 1.0f); mouseRay = new Ray(component.getCamera().getLocation(), worldCoords .subtractLocal(component.getCamera().getLocation())); } return mouseRay; } public Vector2f getScreenCoord(Tuple3d worldCoord) { Vector3f v = renderingComposite.getDisplaySystem().getScreenCoordinates(VecmathJmeTools.get(worldCoord)); return new Vector2f(v.x,v.y); } public InputProvider getInputProvider() { return input; } /** * Changes current action * * @param type */ public void setCurrentAction(InteractiveAction action) { if (currentAction == action) return; if (toolBarManager != null) { toolBarManager.removeAll(); fillLocalToolBar(); } if (currentAction != null) currentAction.deactivate(); currentAction = action; if (currentAction != null) { currentAction.activate(); if (toolBarManager != null) { currentAction.fillToolBar(toolBarManager); } } updateBars(); } public InteractiveAction getCurrentAction() { return currentAction; } public void setActiveEditorContribution(EditorContribution contribution) { if (currentEditorContribution == contribution) return; if (currentAction != getDefaultAction()) return; if (currentEditorContribution != null) currentEditorContribution.disposeControl(); currentEditorContribution = contribution; int index = editorContributions.indexOf(contribution); for (int i = 0; i < contributionSelectionActions.size(); i++) { if (i != index) contributionSelectionActions.get(i).setChecked(false); else contributionSelectionActions.get(i).setChecked(true); } if (currentEditorContribution != null) currentEditorContribution.createControl(parent); actionBars.clearGlobalActionHandlers(); parent.layout(true, true); if (toolBarManager != null) { toolBarManager.removeAll(); fillLocalToolBar(); } if (menuManager != null) { menuManager.removeAll(); fillLocalPullDown(); } updateBars(); } protected void updateBars() { // TODO : actionBars.updateActionBars does not update toolbar, updating toolBar directly layouts code // generated widgets top of contributed (extension) widgets. Only way to achieve proper update // is to use WorkbenchWindow.getCoolBarManager.update(true) actionBars.updateActionBars(); // if (toolBarManager != null) { // toolBarManager.update(true); // } WorkbenchWindow w = (WorkbenchWindow)PlatformUI.getWorkbench().getActiveWorkbenchWindow(); w.getCoolBarManager().update(true); } public void setGizmo(Gizmo gizmo) { if (currentGizmo != null) { currentGizmo.getNode().removeFromParent(); } currentGizmo = gizmo; selectionAdapter.setCurrentGizmo(gizmo); viewChanged = true; } // public void setInfoText(String text) { // if (useInfoComposite) { // infoText.setText(text); // } // } public void showMessage(String message) { MessageDialog.openInformation(parent.getShell(), "Shape Editor", //$NON-NLS-1$ message); } /** * Passing the focus request to the viewer's control. */ public void setFocus() { renderingComposite.getCanvas().setFocus(); } public void dispose() { //System.out.println("ThreeDimensionalEditorBase.dispose()"); VisualizationScheduler.getInstance().removeVisualization(this); if (currentAction != null) currentAction.deactivate(); for (EditorContribution e : editorContributions) e.dispose(); renderingComposite.dispose(); // copy of the set is needed to avoid ConcurrentModificationException adapter.dispose(); component.dispose(); } public final void reload(Graph g, Resource res) { inputResource = res; reloadFrom(EntityFactory.create(g, res)); // at this point we can initialize editor contributions, which may require inputResource initializeEditorContributions(g); } public Resource getInputResource() { return inputResource; } public void update(GraphChangeEvent event) { // System.out.println("Transaction " + this + " : " + event.getTransactionId() + " Arg1: " + event.getArg1() // + " arg2: " + event.getArg2() + " sender: " + event.getSender() + " source: " + event.getSource()); // if (event.added.size() > 0) { // System.out.println("Added:"); // for (Triplet t : event.added) // System.out.println(t); // } // if (event.changed.size() > 0) { // System.out.println("Changed:"); // for (Triplet t : event.changed) // System.out.println(t); // } // if (event.removed.size() > 0) { // System.out.println("Removed:"); // for (Triplet t : event.removed) // System.out.println(t); // } } /** * Loads the initial scene: all further updates to the view are done by * listening changes in the shapes and int the shape group * * @param resource */ protected abstract void reloadFrom(IEntity thing); protected void viewUpdated() { } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ public void run() { if (currentEditorContribution != null) currentEditorContribution.run(); if (parent.isDisposed() || !parent.isVisible()) return; //renderingComposite.getDisplaySystem().setCurrent(); input.update(); if (input.mouseClicked()) { int downMask = MouseEvent.CTRL_DOWN_MASK; if ((input.clickModifiers() & downMask) > 0) { selectionAdapter.setSelectionType(SelectionType.MODIFY); } else { selectionAdapter.setSelectionType(SelectionType.SET); } } if (input.mouseMoved()) { Ray mouseRay = createPickRay(); selectionAdapter.updateHighlights(mouseRay); } if (currentAction == cameraAction && input.mouseClicked()) { selectionAdapter.pickHighlighted(); } if (currentAction == cameraAction && input.mousePressed() && (input.pressModifiers() & MouseEvent.BUTTON3_MASK) > 0) { Point p = renderingComposite.toDisplay(input.mouseX(), input .mouseY()); contextMenu.setLocation(p.x, p.y); contextMenu.setVisible(true); } if (currentAction != null) try { currentAction.update(); } catch (Exception e) { ErrorLogger.defaultLogError("Action error!", e); setCurrentAction(getDefaultAction()); } if (component.update()) viewChanged = true; if (!geometryUpdateRequestAdapter.isRunning() && adapter.needsUpdateGeometry()) { session.asyncRead(geometryUpdateRequestAdapter); } viewChanged |= adapter.isChanged(); if (viewChanged) { viewChanged = false; adapter.setChanged(false); camera.updateCamera(); viewUpdated(); component.render(); } } // TODO : there is some sort of synchronization bug in rendering: // part of the rendered objects are rendered with different camera transformation than others. // re-rendering the scene hides the worst problems. // Using shadows is the reason: shadowed objects are rendered with different transformation than non-shadowed. //private boolean lastChanged = false; private GeometryUpdateRequestAdapter geometryUpdateRequestAdapter = new GeometryUpdateRequestAdapter(); private class GeometryUpdateRequestAdapter extends GraphRequestAdapter { private boolean running; @Override public GraphRequestStatus perform(Graph g) throws Exception { running = true; adapter.updateGeometry(g); return GraphRequestStatus.transactionComplete(); } @Override public void requestCompleted(GraphRequestStatus status) { running = false; adapter.setChanged(true); } public boolean isRunning() { return running; } } protected void hookDragAndDrop() { dropTarget = new ShapeDropTarget(this); } /** * Receives selection changes * * @param part * @param selection */ protected abstract void pageSelectionChanged(IWorkbenchPart part, ISelection selection); /** * EditorPart or ViewPart uses this method to forward getAdapter(Class) * @see org.eclipse.ui.part.WorkbenchPart.getAdapter(Class adapter) * @param adapter * @return */ public Object getAdapter(Class adapter) { return null; } }