| /******************************************************************************* |
| * Copyright (c) 2004, 2010 BREDEX GmbH. |
| * 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: |
| * BREDEX GmbH - initial API and implementation and/or initial documentation |
| *******************************************************************************/ |
| package org.eclipse.jubula.client.ui.rcp.editors; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.apache.commons.lang.Validate; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jubula.client.core.businessprocess.IComponentNameMapper; |
| import org.eclipse.jubula.client.core.events.DataChangedEvent; |
| import org.eclipse.jubula.client.core.events.DataEventDispatcher; |
| import org.eclipse.jubula.client.core.events.DataEventDispatcher.DataState; |
| import org.eclipse.jubula.client.core.events.DataEventDispatcher.IDataChangedListener; |
| import org.eclipse.jubula.client.core.events.DataEventDispatcher.IProjectLoadedListener; |
| import org.eclipse.jubula.client.core.model.IExecTestCasePO; |
| import org.eclipse.jubula.client.core.model.INodePO; |
| import org.eclipse.jubula.client.core.model.IPersistentObject; |
| import org.eclipse.jubula.client.core.model.ISpecTestCasePO; |
| import org.eclipse.jubula.client.core.persistence.EditSupport; |
| import org.eclipse.jubula.client.core.persistence.NodePM; |
| import org.eclipse.jubula.client.core.persistence.PMAlreadyLockedException; |
| import org.eclipse.jubula.client.core.persistence.PMDirtyVersionException; |
| import org.eclipse.jubula.client.core.persistence.PMException; |
| import org.eclipse.jubula.client.ui.rcp.Plugin; |
| import org.eclipse.jubula.client.ui.rcp.controllers.IEditorOperation; |
| import org.eclipse.jubula.client.ui.rcp.controllers.PMExceptionHandler; |
| import org.eclipse.jubula.client.ui.rcp.events.GuiEventDispatcher; |
| import org.eclipse.jubula.client.ui.rcp.i18n.Messages; |
| import org.eclipse.jubula.client.ui.rcp.views.JBPropertiesPage; |
| import org.eclipse.jubula.client.ui.utils.ErrorHandlingUtil; |
| import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs; |
| import org.eclipse.swt.dnd.Clipboard; |
| import org.eclipse.ui.IEditorInput; |
| import org.eclipse.ui.IEditorPart; |
| import org.eclipse.ui.IEditorSite; |
| import org.eclipse.ui.ISelectionListener; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.PartInitException; |
| import org.eclipse.ui.part.EditorPart; |
| import org.eclipse.ui.views.properties.IPropertySheetPage; |
| |
| |
| /** |
| * Encapsulates the functionality expected of all editors in Jubula. This |
| * includes locking/unlocking persistent objects and reacting to changes in |
| * Jubula's data model. |
| * This class can be instantiated and used by any Jubula editor. |
| * |
| * @author BREDEX GmbH |
| * @created Oct 22, 2008 |
| */ |
| public class JBEditorHelper implements ILockedObjects, |
| IProjectLoadedListener, IDataChangedListener { |
| |
| /** Specifies the result of an edit request */ |
| public enum EditableState { NotChecked, OK, Locked, Stale, Invalid } |
| |
| /** the editable state */ |
| private EditableState m_editableState = EditableState.NotChecked; |
| |
| /** checks if there are changes in the editor */ |
| private boolean m_isDirty = false; |
| |
| /** If this editor is active or not */ |
| private boolean m_isActive = false; |
| |
| /** Listens to the activations of this editor */ |
| private ActiveListener m_activeListener = null; |
| |
| /** |
| * This part's reference to the clipboard. |
| * Note that the part shares this clipboard with the entire operating |
| * system, and this instance is only for easier access to the clipboard. |
| * The clipboard does not exclusively belong to the part. |
| */ |
| private Clipboard m_clipboard; |
| |
| /** the editor that is assisted by this helper */ |
| private IJBEditor m_editor; |
| |
| /** |
| * Constructor |
| * |
| * @param editor The editor that will be assisted by this helper. May not |
| * be <code>null</code>. |
| */ |
| public JBEditorHelper(IJBEditor editor) { |
| Validate.notNull(editor); |
| m_editor = editor; |
| } |
| |
| /** |
| * Requests the editable state and displays a message when |
| * the state is not {@link EditableState#OK} |
| * @return the editable state |
| */ |
| public EditableState requestEditableState() { |
| if (getEditSupport() == null) { |
| return EditableState.Invalid; |
| } |
| if (m_editableState == EditableState.NotChecked |
| || m_editableState == EditableState.Locked) { |
| try { |
| getEditSupport().lockWorkVersion(); |
| m_editableState = EditableState.OK; |
| } catch (PMAlreadyLockedException e) { |
| m_editableState = EditableState.Locked; |
| PMExceptionHandler.handlePMExceptionForEditor(e, m_editor); |
| } catch (PMDirtyVersionException e) { |
| m_editableState = EditableState.NotChecked; |
| PMExceptionHandler.handlePMExceptionForEditor(e, m_editor); |
| } catch (PMException e) { |
| m_editableState = EditableState.Invalid; |
| PMExceptionHandler.handlePMExceptionForEditor(e, m_editor); |
| } |
| } |
| return m_editableState; |
| } |
| |
| |
| /** |
| * resets the editable state to not checked. |
| */ |
| public void resetEditableState() { |
| m_editableState = EditableState.NotChecked; |
| } |
| |
| /** |
| * @return the edit support |
| */ |
| public EditSupport getEditSupport() { |
| IEditorInput editorInput = m_editor.getEditorInput(); |
| return (editorInput instanceof PersistableEditorInput) |
| ? ((PersistableEditorInput)editorInput).getEditSupport() |
| : null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Should always be called from the assisted editor's |
| * <code>dispose()</code> method. |
| */ |
| public void dispose() { |
| if (m_clipboard != null) { |
| m_clipboard.clearContents(); |
| m_clipboard.dispose(); |
| } |
| if (getEditSupport() != null) { |
| getEditSupport().close(); |
| } |
| DataEventDispatcher ded = DataEventDispatcher.getInstance(); |
| ded.removeDataChangedListener(m_editor); |
| ded.removeProjectLoadedListener(this); |
| // clear corresponding views |
| ded.firePartClosed(m_editor); |
| |
| if (m_editor.getEditorInput() instanceof PersistableEditorInput) { |
| ((PersistableEditorInput)m_editor.getEditorInput()).dispose(); |
| } |
| |
| if (m_activeListener != null) { |
| m_editor.getSite().getPage().getWorkbenchWindow() |
| .getSelectionService() |
| .removeSelectionListener(m_activeListener); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Should always be called as a fallback from the assisted editor's |
| * <code>getAdapter</code> method. |
| */ |
| public Object getAdapter(Class adapter) { |
| if (adapter == ILockedObjects.class |
| || adapter == IJBEditor.class) { |
| return this; |
| } else if (adapter.equals(IPropertySheetPage.class)) { |
| return new JBPropertiesPage(true, getEditSupport().getCompMapper()); |
| } else if (adapter.equals(IComponentNameMapper.class)) { |
| return getEditSupport().getCompMapper(); |
| } |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @return a list with objects, which are currently locked by this editor |
| */ |
| public List<IPersistentObject> getLockedObjects() { |
| List<IPersistentObject> result = |
| getEditSupport().getLockedObjects(); |
| return result; |
| } |
| |
| |
| /** |
| * validates, if the given object is locked by a dirty editor |
| * @param po object to validate |
| * @return the editor hold the lock for given object or null, if there is |
| * no editor, which holds a lock for the given object |
| */ |
| public static IEditorPart findEditor2LockedObj(IPersistentObject po) { |
| IEditorPart[] dirtyEditors = Plugin.getDefault().getDirtyEditors(); |
| |
| for (IEditorPart editor : dirtyEditors) { |
| ILockedObjects lockedPOs = (ILockedObjects)editor. |
| getAdapter(ILockedObjects.class); |
| if (lockedPOs != null) { |
| List<IPersistentObject> l = lockedPOs.getLockedObjects(); |
| for (IPersistentObject lpo : l) { |
| if (lpo.getId().equals(po.getId())) { |
| return editor; |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Should not be overwritten, but setTitleToolTip() doesn't work. |
| * {@inheritDoc} |
| */ |
| public String getTitleToolTip() { |
| return m_editor.getPartName(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Should always be called from the assisted editor's <code>init</code> |
| * method. Calls assisted editor's <code>initTextAndInput</code> method. |
| */ |
| public void init(IEditorSite site, IEditorInput input) |
| throws PartInitException { |
| |
| m_clipboard = new Clipboard(site.getShell().getDisplay()); |
| PersistableEditorInput editorInput = (PersistableEditorInput)input |
| .getAdapter(PersistableEditorInput.class); |
| if (editorInput != null) { |
| resetEditableState(); |
| m_editor.initTextAndInput(site, input); |
| } else { |
| String msg = org.eclipse.jubula.client.ui.i18n.Messages |
| .EditorInitCreateError; |
| throw new PartInitException(msg); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void handleDataChanged(DataChangedEvent... events) { |
| for (DataChangedEvent e : events) { |
| handleDataChanged(e.getPo(), e.getDataState()); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * This should always be called by the assisted editor during its |
| * <code>handleDataChanged</code> method. |
| */ |
| public void handleDataChanged(final IPersistentObject po, |
| final DataState dataState) { |
| IPersistentObject workVersion = getEditSupport().getWorkVersion(); |
| switch (dataState) { |
| case Added: |
| |
| break; |
| case Deleted: |
| if (po.equals(workVersion)) { |
| m_editor.getSite().getPage().closeEditor(m_editor, false); |
| } |
| break; |
| case Renamed: |
| if (!isDirty() && editorContainsPo(po)) { |
| handleProjectLoaded(); |
| } |
| break; |
| case StructureModified: |
| if (po.equals(workVersion) |
| && !po.getVersion().equals(workVersion.getVersion())) { |
| try { |
| m_editor.reOpenEditor(po); |
| } catch (PMException e) { |
| ErrorHandlingUtil.createMessageDialog( |
| MessageIDs.E_REFRESH_FAILED, |
| null, new String[] { |
| Messages.ErrorMessageEDITOR_CLOSE }); |
| m_editor.getSite().getPage().closeEditor( |
| m_editor, false); |
| } |
| } |
| break; |
| default: |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public final void handleProjectLoaded() { |
| Plugin.getDisplay().syncExec(new Runnable() { |
| public void run() { |
| if (!isDirty()) { |
| final IPersistentObject original = |
| getEditSupport().getOriginal(); |
| if (original != null) { |
| try { |
| Plugin.startLongRunning(); |
| m_editor.reOpenEditor(original); |
| } catch (PMException e) { |
| ErrorHandlingUtil.createMessageDialog( |
| MessageIDs.E_REFRESH_FAILED, |
| null, new String[] { |
| Messages.ErrorMessageEDITOR_CLOSE }); |
| m_editor.getSite().getPage().closeEditor( |
| m_editor, false); |
| } finally { |
| Plugin.stopLongRunning(); |
| } |
| } else { |
| m_editor.getSite().getPage().closeEditor( |
| m_editor, false); |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * add listener |
| * |
| * Should always be called during creation of assisted editor. |
| */ |
| protected void addListeners() { |
| m_activeListener = new ActiveListener(); |
| m_editor.getSite().getPage().getWorkbenchWindow().getSelectionService() |
| .addSelectionListener(m_activeListener); |
| final DataEventDispatcher ded = DataEventDispatcher.getInstance(); |
| ded.addDataChangedListener(m_editor, true); |
| ded.addProjectLoadedListener(this, true); |
| } |
| |
| /** |
| * @param po object to compare |
| * @return if the given po object is contained in editor tree |
| */ |
| protected boolean editorContainsPo(IPersistentObject po) { |
| if (!(po instanceof INodePO) |
| || !(getEditSupport().getWorkVersion() instanceof INodePO)) { |
| return false; |
| } |
| INodePO compObj = (INodePO)getEditSupport().getWorkVersion(); |
| List<INodePO> nodeList = new ArrayList<INodePO>(); |
| nodeList.add((INodePO)po); |
| List<INodePO> editorNodes = collectNodes(nodeList, |
| compObj.getNodeListIterator()); |
| for (Object object : editorNodes) { |
| if (((INodePO)object).equals(po)) { |
| return true; |
| } |
| } |
| // validate location of reuses for modified po object too |
| if (po instanceof ISpecTestCasePO) { |
| ISpecTestCasePO specTc = (ISpecTestCasePO)po; |
| List<IExecTestCasePO> execTCs = |
| NodePM.getInternalExecTestCases(specTc.getGuid(), |
| specTc.getParentProjectId()); |
| for (IExecTestCasePO execTc : execTCs) { |
| for (Object object : editorNodes) { |
| if (((INodePO)object).equals(execTc)) { |
| return true; |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @param nodeList container for nodes in editor |
| * @param it iterator for current node |
| * @return list with all nodes contained in editor |
| */ |
| private List<INodePO> collectNodes(List<INodePO> nodeList, |
| Iterator<INodePO> it) { |
| while (it.hasNext()) { |
| INodePO node = it.next(); |
| nodeList.add(node); |
| if (node.getNodeListSize() > 0) { |
| return collectNodes(nodeList, node.getNodeListIterator()); |
| } |
| } |
| return nodeList; |
| } |
| |
| /** |
| * @return true if input is changed |
| */ |
| public boolean isDirty() { |
| return m_isDirty; |
| } |
| |
| /** |
| * Calls assisted editor's <code>fireDirtyProperty</code> method. |
| * |
| * @param isDirty The isDirty to set. |
| */ |
| public void setDirty(boolean isDirty) { |
| m_isDirty = isDirty; |
| m_editor.fireDirtyProperty(isDirty); |
| GuiEventDispatcher.getInstance() |
| .fireEditorDirtyStateListener(m_editor, isDirty); |
| } |
| |
| /** |
| * @return Returns the editableState. |
| */ |
| public EditableState getEditableState() { |
| return m_editableState; |
| } |
| |
| /** |
| * |
| * @author BREDEX GmbH |
| * @created Feb 20, 2008 |
| */ |
| private class ActiveListener implements ISelectionListener { |
| /** |
| * {@inheritDoc} |
| */ |
| public void selectionChanged(IWorkbenchPart part, |
| ISelection selection) { |
| if (part instanceof EditorPart) { |
| m_isActive = (part == m_editor); |
| } |
| } |
| } |
| |
| /** |
| * @return <code>true</code> if the supported editor is currently active. |
| * This is the open editor most recently brought to top. Otherwise, |
| * <code>false</code>. |
| */ |
| public boolean isActive() { |
| return m_isActive; |
| } |
| |
| /** |
| * |
| * @return a reference to the clipboard. |
| */ |
| public Clipboard getClipboard() { |
| return m_clipboard; |
| } |
| |
| /** |
| * Performs the given <code>operation</code> within the context of the |
| * receiver's editor. The operation is "wrapped" between a request for an |
| * editable state and a reset of editable state (if the editor is not marked |
| * as dirty by the end of the operation. The operation is executed in the |
| * same thread in which this method is called. |
| * |
| * @param operation The operation to perform. |
| */ |
| public void doEditorOperation(IEditorOperation operation) { |
| if (requestEditableState() == EditableState.OK) { |
| try { |
| operation.run(getEditSupport().getWorkVersion()); |
| } finally { |
| final IJBEditor editor = m_editor; |
| if (!editor.isDirty()) { |
| try { |
| editor.reOpenEditor(getEditSupport().getOriginal()); |
| } catch (PMException e) { |
| PMExceptionHandler.handlePMExceptionForEditor( |
| e, editor); |
| } |
| } |
| } |
| } |
| } |
| } |