/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ /* Locally modified at Basis Technology to add features and work around bugs. * Copyright (C) 2006 Basis Technology Corp. * * Since the OLE/COM support can't do events (what a surprise) we need to use a timer * to notice when the file changes so that we can show dirtyness accurately. * */ package com.basistech.awb.office; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.util.Vector; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceColors; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.ole.win32.OLE; import org.eclipse.swt.ole.win32.OleAutomation; import org.eclipse.swt.ole.win32.OleClientSite; import org.eclipse.swt.ole.win32.OleFrame; import org.eclipse.swt.ole.win32.Variant; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.WorkspaceModifyOperation; import org.eclipse.ui.dialogs.SaveAsDialog; import org.eclipse.ui.ide.ResourceUtil; import org.eclipse.ui.part.EditorPart; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.progress.UIJob; import com.basistech.awb.AWBCorePlugin; import com.basistech.awb.IAWBEditorBelatedInput; import com.basistech.awb.IAWBEditorInputProvider; import com.basistech.awb.texteditor.AWBEditorInput; /** */ /** * @author benson@basistech.com */ public class OleEditor extends EditorPart implements IAWBEditorBelatedInput { private boolean microsoftWord = false; private class CheckDirty extends UIJob { /** * @param name */ public CheckDirty(String name) { super(name); setSystem(true); } /* (non-Javadoc) * @see org.eclipse.ui.progress.UIJob#runInUIThread(org.eclipse.core.runtime.IProgressMonitor) */ public IStatus runInUIThread(IProgressMonitor monitor) { if(!monitor.isCanceled()) { OleEditor.this.refreshDirty(); schedule(1000); } return Status.OK_STATUS; } } private CheckDirty checkDirtyJob = null; /** * The resource listener updates the receiver when * a change has occured. */ private IResourceChangeListener resourceListener = new IResourceChangeListener() { /* * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) */ public void resourceChanged(IResourceChangeEvent event) { IResourceDelta mainDelta = event.getDelta(); if (mainDelta == null) return; IResourceDelta affectedElement = mainDelta.findMember(resource.getFullPath()); if (affectedElement != null) processDelta(affectedElement); } /* * Process the delta for the receiver */ private boolean processDelta(final IResourceDelta delta) { Runnable changeRunnable = null; switch (delta.getKind()) { case IResourceDelta.REMOVED: if ((IResourceDelta.MOVED_TO & delta.getFlags()) != 0) { changeRunnable = new Runnable() { public void run() { IPath path = delta.getMovedToPath(); IFile newFile = delta.getResource().getWorkspace().getRoot().getFile(path); if (newFile != null) { sourceChanged(newFile); } } }; } else { changeRunnable = new Runnable() { public void run() { sourceDeleted = true; getSite().getPage().closeEditor(OleEditor.this, true); } }; } break; } if (changeRunnable != null) update(changeRunnable); return true; // because we are sitting on files anyway } }; private OleFrame clientFrame; private OleClientSite clientSite; private File source; private IFile resource; private Image oleTitleImage; //The sourceDeleted flag makes sure that the receiver is not //dirty when shutting down boolean sourceDeleted = false; //The sourceChanged flag indicates whether or not the save from the ole component //can be used or if the input changed boolean sourceChanged = false; /** * Keep track of whether we have an active client so we do not * deactivate multiple times */ private boolean clientActive = false; /** * Keep track of whether we have activated OLE or not as some applications * will only allow single activations. */ private boolean oleActivated = false; private IPartListener partListener = new IPartListener() { public void partActivated(IWorkbenchPart part) { activateClient(part); if(checkDirtyJob != null) checkDirtyJob.schedule(); } public void partBroughtToTop(IWorkbenchPart part) { } public void partClosed(IWorkbenchPart part) { } public void partOpened(IWorkbenchPart part) { } public void partDeactivated(IWorkbenchPart part) { if(checkDirtyJob != null) checkDirtyJob.cancel(); deactivateClient(part); } }; private static final String RENAME_ERROR_TITLE = OleMessages.getString("OleEditor.errorSaving"); //$NON-NLS-1$ private static final String OLE_EXCEPTION_TITLE = OleMessages.getString("OleEditor.oleExceptionTitle"); //$NON-NLS-1$ private static final String OLE_EXCEPTION_MESSAGE = OleMessages.getString("OleEditor.oleExceptionMessage"); //$NON-NLS-1$ private static final String OLE_CREATE_EXCEPTION_MESSAGE = OleMessages.getString("OleEditor.oleCreationExceptionMessage"); //$NON-NLS-1$ private static final String SAVE_ERROR_TITLE = OleMessages.getString("OleEditor.savingTitle"); //$NON-NLS-1$ private static final String SAVE_ERROR_MESSAGE = OleMessages.getString("OleEditor.savingMessage"); //$NON-NLS-1$ /** * Return a new ole editor. */ public OleEditor() { //Do nothing } private void activateClient(IWorkbenchPart part) { if (part == this) { oleActivate(); this.clientActive = true; } } /** * createPartControl method comment. */ public void createPartControl(Composite parent) { // Create a frame. clientFrame = new OleFrame(parent, SWT.CLIP_CHILDREN); clientFrame.setBackground(JFaceColors.getBannerBackground(clientFrame.getDisplay())); initializeWorkbenchMenus(); // Set the input file. IFile file = ResourceUtil.getFile(getEditorInput()); if (file != null) { setResource(file); resource.getWorkspace().addResourceChangeListener(resourceListener); } createClientSite(); } /** * Create the client site for the reciever */ private void createClientSite() { //If there was an OLE Error or nothing has been created yet if (clientFrame == null || clientFrame.isDisposed()) return; // Create a OLE client site. try { clientSite = new OleClientSite(clientFrame, SWT.NONE, source); } catch (SWTException exception) { IStatus errorStatus = new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID, IStatus.ERROR, OLE_CREATE_EXCEPTION_MESSAGE, exception); ErrorDialog.openError(null, OLE_EXCEPTION_TITLE, errorStatus.getMessage(), errorStatus); return; } clientSite.setBackground(JFaceColors.getBannerBackground(clientFrame.getDisplay())); } private void deactivateClient(IWorkbenchPart part) { //Check the client active flag. Set it to false when we have deactivated //to prevent multiple deactivations. if (part == this && clientActive) { if (clientSite != null) clientSite.deactivateInPlaceClient(); this.clientActive = false; this.oleActivated = false; } } /** * Display an error dialog with the supplied title and message. * @param title * @param message */ private void displayErrorDialog(String title, String message) { Shell parent = null; if (getClientSite() != null) parent = getClientSite().getShell(); MessageDialog.openError(parent, title, message); } /** * @see IWorkbenchPart#dispose */ public void dispose() { if(checkDirtyJob != null) checkDirtyJob.cancel(); if (resource != null) resource.getWorkspace().removeResourceChangeListener(resourceListener); //can dispose the title image because it was created in init if (oleTitleImage != null) { oleTitleImage.dispose(); oleTitleImage = null; } if (getSite() != null && getSite().getPage() != null) getSite().getPage().removePartListener(partListener); } /** * Print this object's contents */ public void doPrint() { if (clientSite == null) return; BusyIndicator.showWhile(clientSite.getDisplay(), new Runnable() { public void run() { clientSite.exec(OLE.OLECMDID_PRINT, OLE.OLECMDEXECOPT_PROMPTUSER, null, null); // note: to check for success: above == SWTOLE.S_OK } }); } /** * Save the viewer's contents to the source file system file */ public void doSave(final IProgressMonitor monitor) { if (clientSite == null) return; BusyIndicator.showWhile(clientSite.getDisplay(), new Runnable() { /* * (non-Javadoc) * @see java.lang.Runnable#run() */ public void run() { //Do not try and use the component provided save if the source has //changed in Eclipse if (!sourceChanged) { int result = clientSite.queryStatus(OLE.OLECMDID_SAVE); if ((result & OLE.OLECMDF_ENABLED) != 0) { result = clientSite.exec(OLE.OLECMDID_SAVE, OLE.OLECMDEXECOPT_PROMPTUSER, null, null); if (result == OLE.S_OK) { try { resource.refreshLocal(IResource.DEPTH_ZERO, monitor); } catch (CoreException ex) { //Do nothing on a failed refresh } return; } displayErrorDialog(OLE_EXCEPTION_TITLE, OLE_EXCEPTION_MESSAGE + String.valueOf(result)); return; } } if (saveFile(source)) { try { resource.refreshLocal(IResource.DEPTH_ZERO, monitor); } catch (CoreException ex) { //Do nothing on a failed refresh } } else displayErrorDialog(SAVE_ERROR_TITLE, SAVE_ERROR_MESSAGE + source.getName()); } }); } /** * Save the viewer's contents into the provided resource. */ public void doSaveAs() { if (clientSite == null) return; WorkspaceModifyOperation op = saveNewFileOperation(); Shell shell = clientSite.getShell(); try { new ProgressMonitorDialog(shell).run(false, true, op); } catch (InterruptedException interrupt) { //Nothing to reset so do nothing } catch (InvocationTargetException invocationException) { MessageDialog.openError(shell, RENAME_ERROR_TITLE, invocationException.getTargetException().getMessage()); } } /** * Answer self's client site * * @return org.eclipse.swt.ole.win32.OleClientSite */ public OleClientSite getClientSite() { return clientSite; } /** * Answer the file system representation of self's input element * * @return java.io.File */ public File getSourceFile() { return source; } private boolean lastOfficeDirty = false; private void refreshDirty() { boolean last = lastOfficeDirty; // modified in wordNeedsSave if(wordNeedsSave() != last) firePropertyChange(PROP_DIRTY); } private boolean wordNeedsSave() { if(clientSite.isDisposed()) // otherwise, death by null pointer. return false; OleAutomation dispInterface = new OleAutomation(clientSite); // Get Application int[] appId = dispInterface.getIDsOfNames(new String[] { "Application" }); //$NON-NLS-1$ if (appId != null) { Variant pVarResult = dispInterface.getProperty(appId[0]); if (pVarResult != null) { OleAutomation application = pVarResult.getAutomation(); int[] dispid = application.getIDsOfNames(new String[] { "ActiveDocument" }); //$NON-NLS-1$ if (dispid != null) { pVarResult = application.getProperty(dispid[0]); if (pVarResult != null) { OleAutomation docInterface = pVarResult.getAutomation(); dispid = docInterface.getIDsOfNames(new String[] { "Saved" }); if (dispid != null) { pVarResult = docInterface.getProperty(dispid[0]); if (pVarResult != null) { lastOfficeDirty = !pVarResult.getBoolean(); return lastOfficeDirty; } } docInterface.dispose(); } } application.dispose(); } dispInterface.dispose(); } return false; } private void handleWord() { microsoftWord = true; OleAutomation dispInterface = new OleAutomation(clientSite); // Get Application int[] appId = dispInterface.getIDsOfNames(new String[] { "Application" }); //$NON-NLS-1$ if (appId != null) { Variant pVarResult = dispInterface.getProperty(appId[0]); if (pVarResult != null) { OleAutomation application = pVarResult.getAutomation(); int[] dispid = application.getIDsOfNames(new String[] { "DisplayScrollBars" }); //$NON-NLS-1$ if (dispid != null) { Variant rgvarg = new Variant(true); application.setProperty(dispid[0], rgvarg); } application.dispose(); } } dispInterface.dispose(); // once we have marked it as word, we have better information in isDirty // and the workbench needs to know about it. firePropertyChange(PROP_DIRTY); checkDirtyJob = new CheckDirty("check for mods in Word document"); checkDirtyJob.schedule(1000); } /* (non-Javadoc) * Initializes the editor when created from scratch. * * This method is called soon after part construction and marks * the start of the extension lifecycle. At the end of the * extension lifecycle shutdown will be invoked * to terminate the lifecycle. * * @param container an interface for communication with the part container * @param input The initial input element for the editor. In most cases * it is an IFile but other types are acceptable. * @see IWorkbenchPart#shutdown */ public void init(IEditorSite site, IEditorInput input) throws PartInitException { // Check input. IFile file = ResourceUtil.getFile(input); if (file == null) throw new PartInitException(OleMessages.format("OleEditor.invalidInput", new Object[] { input })); //$NON-NLS-1$ //Cannot create this with a file and no physical location if (file.getLocation() == null || !(new File(file.getLocation().toOSString()).exists())) throw new PartInitException(OleMessages.format("OleEditor.noFileInput", new Object[] { file.getLocation() })); //$NON-NLS-1$ // Save input. setSite(site); setInput(input); // Update titles. decorateFromInput(); setTitleToolTip(input.getToolTipText()); ImageDescriptor desc = input.getImageDescriptor(); if (desc != null) { oleTitleImage = desc.createImage(); setTitleImage(oleTitleImage); } // Listen for part activation. site.getPage().addPartListener(partListener); } /** * Initialize the workbench menus for proper merging */ protected void initializeWorkbenchMenus() { //If there was an OLE Error or nothing has been created yet if (clientFrame == null || clientFrame.isDisposed()) return; // Get the browser menubar. If one does not exist then // create it. Shell shell = clientFrame.getShell(); Menu menuBar = shell.getMenuBar(); if (menuBar == null) { menuBar = new Menu(shell, SWT.BAR); shell.setMenuBar(menuBar); } // Swap the file and window menus. MenuItem[] windowMenu = new MenuItem[1]; MenuItem[] fileMenu = new MenuItem[1]; Vector containerItems = new Vector(); IWorkbenchWindow window = getSite().getWorkbenchWindow(); for (int i = 0; i < menuBar.getItemCount(); i++) { MenuItem item = menuBar.getItem(i); String id = ""; //$NON-NLS-1$ if (item.getData() instanceof IMenuManager) id = ((IMenuManager) item.getData()).getId(); if (id.equals(IWorkbenchActionConstants.M_FILE)) fileMenu[0] = item; else if (id.equals(IWorkbenchActionConstants.M_WINDOW)) windowMenu[0] = item; else { if (window.isApplicationMenu(id)) { containerItems.addElement(item); } } } MenuItem[] containerMenu = new MenuItem[containerItems.size()]; containerItems.copyInto(containerMenu); clientFrame.setFileMenus(fileMenu); clientFrame.setContainerMenus(containerMenu); clientFrame.setWindowMenus(windowMenu); } /* * (non-Javadoc) * @see org.eclipse.ui.ISaveablePart#isDirty() */ public boolean isDirty() { /*Return only if we have a clientSite which is dirty as this can be asked before anything is opened*/ if(microsoftWord) return this.clientSite != null && wordNeedsSave(); else return this.clientSite != null; } /* * (non-Javadoc) * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed() */ public boolean isSaveAsAllowed() { return true; } // why does this previously unexported class have an API that // is not called for by any interface at all? /** * @return false if it was not opened and true * only if it is dirty */ //public boolean isSaveNeeded() { // return getClientSite() != null && isDirty(); //} /** * Save the supplied file using the SWT API. * @param file java.io.File * @return true if the save was successful */ private boolean saveFile(File file) { File tempFile = new File(file.getAbsolutePath() + ".tmp"); //$NON-NLS-1$ file.renameTo(tempFile); boolean saved = false; if (OLE.isOleFile(file) || usesStorageFiles(clientSite.getProgramID())) { saved = clientSite.save(file, true); } else { saved = clientSite.save(file, false); } if (saved) { // save was successful so discard the backup tempFile.delete(); return true; } // save failed so restore the backup tempFile.renameTo(file); return false; } /** * Save the new File using the client site. * @return WorkspaceModifyOperation */ private WorkspaceModifyOperation saveNewFileOperation() { return new WorkspaceModifyOperation() { public void execute(final IProgressMonitor monitor) throws CoreException { SaveAsDialog dialog = new SaveAsDialog(clientFrame.getShell()); IFile sFile = ResourceUtil.getFile(getEditorInput()); if (sFile != null) { dialog.setOriginalFile(sFile); dialog.open(); IPath newPath = dialog.getResult(); if (newPath == null) return; if (dialog.getReturnCode() == Window.OK) { String projectName = newPath.segment(0); newPath = newPath.removeFirstSegments(1); IProject project = resource.getWorkspace().getRoot().getProject(projectName); newPath = project.getLocation().append(newPath); File newFile = newPath.toFile(); if (saveFile(newFile)) { IFile newResource = resource.getWorkspace().getRoot().getFileForLocation(newPath); if (newResource != null) { sourceChanged(newResource); newResource.refreshLocal(IResource.DEPTH_ZERO, monitor); } } else { displayErrorDialog(SAVE_ERROR_TITLE, SAVE_ERROR_MESSAGE + newFile.getName()); return; } } } } }; } /* * (non-Javadoc) * @see org.eclipse.ui.IWorkbenchPart#setFocus() */ public void setFocus() { //Do not take focus } /** * Make ole active so that the controls are rendered. */ private void oleActivate() { //If there was an OLE Error or nothing has been created yet if (clientSite == null || clientFrame == null || clientFrame.isDisposed()) return; if (!oleActivated) { clientSite.doVerb(OLE.OLEIVERB_SHOW); oleActivated = true; String progId = clientSite.getProgramID(); if (progId != null && progId.startsWith("Word.Document")) { //$NON-NLS-1$ handleWord(); } } } /** * Set the file resource that this object is displaying * @param file */ protected void setResource(IFile file) { resource = file; source = new File(file.getLocation().toOSString()); } /** * See if it is one of the known types that use OLE Storage. * @param progID the type to test * @return true if it is one of the known types */ private static boolean usesStorageFiles(String progID) { return (progID != null && (progID.startsWith("Word.", 0) //$NON-NLS-1$ || progID.startsWith("MSGraph", 0) //$NON-NLS-1$ || progID.startsWith("PowerPoint", 0) //$NON-NLS-1$ || progID.startsWith("Excel", 0))); //$NON-NLS-1$ } /** * The source has changed to the newFile. Update * editors and set any required flags * @param newFile The file to get the new contents from. */ private void sourceChanged(IFile newFile) { FileEditorInput newInput = new FileEditorInput(newFile); setInput(newInput); setResource(newFile); sourceChanged = true; setPartName(newInput.getName()); } /* * See IEditorPart.isSaveOnCloseNeeded() */ public boolean isSaveOnCloseNeeded() { return !sourceDeleted && super.isSaveOnCloseNeeded(); } /** * Posts the update code "behind" the running operation. * * @param runnable the update code */ private void update(Runnable runnable) { IWorkbench workbench = PlatformUI.getWorkbench(); IWorkbenchWindow[] windows = workbench.getWorkbenchWindows(); if (windows != null && windows.length > 0) { Display display = windows[0].getShell().getDisplay(); display.asyncExec(runnable); } else runnable.run(); } /** * See if we can get a better title by looking for information from the Docex navigator or some other AWB * busy-body. */ private void decorateFromInput() { IEditorInput input = getEditorInput(); AWBEditorInput aei = null; if(input instanceof AWBEditorInput) aei = (AWBEditorInput)input; else { IAWBEditorInputProvider p = AWBCorePlugin.getDefault().getAwbEditorInputProvider(); if(p != null) aei = p.adaptInput(input); } if(aei == null) { AWBCorePlugin.getDefault().setEditorWaitingForDocexData(this); setPartName(input.getName()); } else setPartName(aei.getTitle()); } /* (non-Javadoc) * @see com.basistech.awb.IAWBEditorBelatedInput#noteAWBEditorInput() */ public void noteAWBEditorInput() { decorateFromInput(); } }