| /******************************************************************************* |
| * Copyright (c) 2008, 2011 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.compare.internal.win32; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.MissingResourceException; |
| import java.util.ResourceBundle; |
| |
| import org.eclipse.compare.CompareConfiguration; |
| import org.eclipse.compare.CompareEditorInput; |
| import org.eclipse.compare.CompareUI; |
| import org.eclipse.compare.CompareViewerPane; |
| import org.eclipse.compare.IEditableContent; |
| import org.eclipse.compare.IPropertyChangeNotifier; |
| import org.eclipse.compare.ITypedElement; |
| import org.eclipse.compare.contentmergeviewer.IFlushable; |
| import org.eclipse.compare.structuremergeviewer.Differencer; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.FileLocator; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.action.IToolBarManager; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.dialogs.ErrorDialog; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.jface.util.SafeRunnable; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.FileDialog; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.forms.widgets.FormToolkit; |
| import org.eclipse.ui.part.PageBook; |
| |
| public class WordMergeViewer extends AbstractMergeViewer implements IFlushable, IPropertyChangeNotifier { |
| |
| private static final String RESOURCE_BUNDLE_NAME = "org.eclipse.compare.internal.win32.WordMergeViewer"; //$NON-NLS-1$ |
| |
| private FormToolkit formToolkit; |
| private PageBook composite; |
| private Composite docArea; |
| private Composite textArea; |
| private Label description; |
| private WordComparison wordArea; |
| private boolean isDirty; |
| private ListenerList listeners = new ListenerList(ListenerList.IDENTITY); |
| private Action saveAction; |
| private ResourceBundle resourceBundle; |
| private Action inplaceAction; |
| private long resultFileTimestamp = -1; |
| |
| public WordMergeViewer(Composite parent, CompareConfiguration configuration) { |
| super(configuration); |
| createContentArea(parent); |
| getControl().addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| handleDispose(); |
| } |
| }); |
| getControl().setData(CompareUI.COMPARE_VIEWER_TITLE, CompareWin32Messages.WordMergeViewer_1); |
| IToolBarManager toolBarManager = CompareViewerPane.getToolBarManager(parent); |
| if (toolBarManager != null) { |
| toolBarManager.removeAll(); |
| initializeToolbar(toolBarManager); |
| } |
| updateEnablements(); |
| } |
| |
| private void createContentArea(Composite parent) { |
| formToolkit = new FormToolkit(parent.getDisplay()); |
| composite = new PageBook(parent, SWT.NONE); |
| createDocArea(composite); |
| createTextArea(composite); |
| } |
| |
| private void initializeToolbar(IToolBarManager toolBarManager) { |
| toolBarManager.add(new Separator("modes")); //$NON-NLS-1$ |
| toolBarManager.add(new Separator("file")); //$NON-NLS-1$ |
| CompareConfiguration configuration = getConfiguration(); |
| // For now, only support saving if one side is editable |
| if (configuration.isRightEditable() || configuration.isLeftEditable() |
| && (configuration.isRightEditable() != configuration.isLeftEditable())) { |
| saveAction = new Action() { |
| public void run() { |
| saveDocument(); |
| } |
| }; |
| initAction(saveAction, getResourceBundle(), "action.save."); //$NON-NLS-1$ |
| toolBarManager.appendToGroup("file", saveAction); //$NON-NLS-1$ |
| } |
| |
| inplaceAction = new Action(CompareWin32Messages.WordMergeViewer_2, Action.AS_CHECK_BOX) { |
| public void run() { |
| toggleInplaceExternalState(); |
| } |
| }; |
| initAction(inplaceAction, getResourceBundle(), "action.inplace."); //$NON-NLS-1$ |
| toolBarManager.appendToGroup("modes", inplaceAction); //$NON-NLS-1$ |
| |
| toolBarManager.update(true); |
| } |
| |
| /* |
| * Initialize the given Action from a ResourceBundle. |
| */ |
| private static void initAction(IAction a, ResourceBundle bundle, String prefix) { |
| |
| String labelKey= "label"; //$NON-NLS-1$ |
| String tooltipKey= "tooltip"; //$NON-NLS-1$ |
| String imageKey= "image"; //$NON-NLS-1$ |
| String descriptionKey= "description"; //$NON-NLS-1$ |
| |
| if (prefix != null && prefix.length() > 0) { |
| labelKey= prefix + labelKey; |
| tooltipKey= prefix + tooltipKey; |
| imageKey= prefix + imageKey; |
| descriptionKey= prefix + descriptionKey; |
| } |
| |
| a.setText(getString(bundle, labelKey, labelKey)); |
| a.setToolTipText(getString(bundle, tooltipKey, null)); |
| a.setDescription(getString(bundle, descriptionKey, null)); |
| |
| String relPath= getString(bundle, imageKey, null); |
| if (relPath != null && relPath.trim().length() > 0) { |
| |
| String dPath; |
| String ePath; |
| |
| if (relPath.contains("/")) { //$NON-NLS-1$ |
| String path= relPath.substring(1); |
| dPath= 'd' + path; |
| ePath= 'e' + path; |
| } else { |
| dPath= "dlcl16/" + relPath; //$NON-NLS-1$ |
| ePath= "elcl16/" + relPath; //$NON-NLS-1$ |
| } |
| |
| ImageDescriptor id= getImageDescriptor(dPath); // we set the disabled image first (see PR 1GDDE87) |
| if (id != null) |
| a.setDisabledImageDescriptor(id); |
| id= getImageDescriptor(ePath); |
| if (id != null) { |
| a.setImageDescriptor(id); |
| a.setHoverImageDescriptor(id); |
| } |
| } |
| } |
| |
| private static ImageDescriptor getImageDescriptor(String relativePath) { |
| IPath path= new Path("$nl$/icons/full/").append(relativePath); |
| URL url= FileLocator.find(Activator.getDefault().getBundle(), path, null); |
| if (url == null) |
| return null; |
| return ImageDescriptor.createFromURL(url); |
| } |
| |
| private static String getString(ResourceBundle bundle, String key, String dfltValue) { |
| |
| if (bundle != null) { |
| try { |
| return bundle.getString(key); |
| } catch (MissingResourceException x) { |
| // fall through |
| } |
| } |
| return dfltValue; |
| } |
| |
| private ResourceBundle getResourceBundle() { |
| if (resourceBundle == null) { |
| resourceBundle = ResourceBundle.getBundle(RESOURCE_BUNDLE_NAME); |
| } |
| return resourceBundle; |
| } |
| |
| private Composite createComposite(Composite parent) { |
| return formToolkit.createComposite(parent); |
| } |
| |
| private void updateEnablements() { |
| if (saveAction != null) |
| saveAction.setEnabled(isDirty()); |
| inplaceAction.setChecked(wordArea.isInplace()); |
| inplaceAction.setEnabled(wordArea.isOpen()); |
| } |
| |
| |
| private void createDocArea(PageBook book) { |
| docArea = createComposite(book); |
| docArea.setLayout(GridLayoutFactory.fillDefaults().create()); |
| wordArea = new WordComparison(docArea); |
| IWorkbenchPart workbenchPart = getConfiguration().getContainer().getWorkbenchPart(); |
| if (workbenchPart != null) { |
| wordArea.initializeWorkbenchMenus(workbenchPart.getSite().getWorkbenchWindow()); |
| } |
| wordArea.getFrame().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| wordArea.getFrame().setBackground(formToolkit.getColors().getBackground()); |
| updateDirtyFlag(); |
| } |
| |
| private void createTextArea(PageBook book) { |
| textArea = createComposite(book); |
| textArea.setLayout(GridLayoutFactory.fillDefaults().extendedMargins(10, 10, 10, 10).create()); |
| description = formToolkit.createLabel(textArea, getTextDescription(), SWT.WRAP); |
| description.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| } |
| |
| protected void saveDocument() { |
| try { |
| File result = getResultFile(); |
| wordArea.saveAsDocument(result.getAbsolutePath()); |
| // Forward the saved content to the save target |
| IEditableContent saveTarget = getSaveTarget(); |
| if (saveTarget == null) { |
| if (MessageDialog.openQuestion(WordMergeViewer.this.composite.getShell(), "Save to File?", "The compare editor is not saveable. Would you like to save your changes to another file?")) { |
| FileDialog dialog = new FileDialog(WordMergeViewer.this.composite.getShell(), SWT.SAVE); |
| String filename = dialog.open(); |
| if (filename != null) { |
| wordArea.saveAsDocument(filename); |
| } |
| } |
| } else { |
| synchronized (result) { |
| if (result.exists()) { |
| saveTarget.setContent(asBytes(result)); |
| resultFileTimestamp = result.lastModified(); |
| } |
| } |
| } |
| updateEnablements(); |
| } catch (IOException e) { |
| ErrorDialog.openError(WordMergeViewer.this.composite.getShell(), null, null, new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, e.getMessage(), e)); |
| } catch (SWTException e) { |
| ErrorDialog.openError(WordMergeViewer.this.composite.getShell(), null, null, new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, e.getMessage(), e)); |
| } |
| } |
| |
| public void flush(IProgressMonitor monitor) { |
| Display.getDefault().syncExec(new Runnable() { |
| public void run() { |
| if (isReallyDirty()) |
| saveDocument(); |
| } |
| }); |
| } |
| |
| |
| protected void toggleInplaceExternalState() { |
| BusyIndicator.showWhile(Display.getDefault(), new Runnable() { |
| public void run() { |
| try { |
| if (isReallyDirty()) { |
| // If the file is dirty, save the result file before switching so our changes are not lost |
| try { |
| File result = getResultFile(); |
| wordArea.saveAsDocument(result.getAbsolutePath()); |
| } catch (IOException e) { |
| throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, e.getMessage(), e)); |
| } |
| } |
| openComparison(!wordArea.isInplace()); |
| } catch (CoreException e) { |
| ErrorDialog.openError(WordMergeViewer.this.composite.getShell(), null, null, e.getStatus()); |
| Activator.log(e); |
| } |
| } |
| }); |
| } |
| |
| private boolean isReallyDirty() { |
| return isDirty() || wordArea.isDirty(); |
| } |
| |
| private void openComparison(boolean inplace) throws CoreException { |
| try { |
| if (isOneSided()) { |
| File file = getFileForSingleSide(); |
| if (file != null) { |
| try { |
| wordArea.openDocument(file.getAbsolutePath(), inplace); |
| } catch (SWTException e) { |
| throw new CoreException(new Status(IStatus.ERROR, |
| Activator.PLUGIN_ID, NLS.bind( |
| CompareWin32Messages.WordComparison_16, |
| file.getAbsolutePath()), e)); |
| } |
| } |
| } else { |
| File left = getFileForLeft(); |
| File right = getFileForRight(); |
| if (left != null && right != null) { |
| File result = getResultFile(); |
| int direction = getCompareInput().getKind() & Differencer.DIRECTION_MASK; |
| File base, revised; |
| if (direction == Differencer.RIGHT) { |
| base = left; |
| revised = right; |
| } else { |
| base = right; |
| revised = left; |
| } |
| synchronized (result) { |
| if (!result.exists()) { |
| wordArea.createWorkingCopy(base.getAbsolutePath(), revised.getAbsolutePath(), result.getAbsolutePath()); |
| resultFileTimestamp = result.lastModified(); |
| description.setText(getTextDescription()); |
| } |
| try { |
| wordArea.openDocument(result.getAbsolutePath(), inplace); |
| } catch (SWTException e) { |
| throw new CoreException(new Status(IStatus.ERROR, |
| Activator.PLUGIN_ID, NLS.bind( |
| CompareWin32Messages.WordComparison_16, |
| result.getAbsolutePath()), e)); |
| } |
| } |
| } |
| } |
| if (wordArea.isInplace()) { |
| composite.showPage(docArea); |
| } else { |
| composite.showPage(textArea); |
| } |
| } catch (SWTException e) { |
| throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, e.getMessage(), e)); |
| } catch (IOException e) { |
| throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, e.getMessage(), e)); |
| } |
| updateEnablements(); |
| } |
| |
| private String getTextDescription() { |
| if (saveAction != null &&hasResultFile()) { |
| IEditableContent saveTarget = getSaveTarget(); |
| String name = CompareWin32Messages.WordMergeViewer_3; |
| if (saveTarget instanceof ITypedElement) { |
| ITypedElement te = (ITypedElement) saveTarget; |
| name = te.getName(); |
| } |
| try { |
| return NLS.bind(CompareWin32Messages.WordMergeViewer_4, getResultFile().getName(), name); |
| } catch (IOException e) { |
| // This shouldn't happen since the result file has already been created |
| } |
| } |
| return CompareWin32Messages.WordMergeViewer_5; |
| } |
| |
| public Control getControl() { |
| return composite; |
| } |
| |
| public void setInput(Object input) { |
| super.setInput(input); |
| try { |
| openComparison(true); |
| } catch (CoreException e) { |
| ErrorDialog.openError(WordMergeViewer.this.composite.getShell(), null, null, e.getStatus()); |
| Activator.log(e); |
| } |
| } |
| |
| private void updateDirtyFlag() { |
| final Runnable dirtyFlagUpdater = new Runnable() { |
| public void run() { |
| if (wordArea.getFrame().isDisposed()) |
| return; |
| boolean dirty = wordArea.isDirty(); |
| if (hasResultFile()) { |
| try { |
| File resultFile = getResultFile(); |
| synchronized (resultFile) { |
| if (resultFile.exists()) { |
| long lastModified = resultFile.lastModified(); |
| if (lastModified != resultFileTimestamp) { |
| dirty = true; |
| } |
| } |
| } |
| } catch (IOException e) { |
| // Shouldn't happen since we only get the result file if it has already been created |
| } |
| } |
| if (isDirty() != dirty) { |
| setDirty(dirty); |
| } |
| composite.getDisplay().timerExec(1000, this); |
| } |
| }; |
| dirtyFlagUpdater.run(); |
| } |
| |
| protected boolean isDirty() { |
| return isDirty; |
| } |
| |
| protected void setDirty(boolean dirty) { |
| if (isDirty != dirty) { |
| isDirty = dirty; |
| updateEnablements(); |
| firePropertyChange(CompareEditorInput.DIRTY_STATE, Boolean.valueOf(!isDirty), Boolean.valueOf(isDirty)); |
| } |
| } |
| |
| private void firePropertyChange(String property, Object oldValue, Object newValue) { |
| Object[] allListeners = listeners.getListeners(); |
| final PropertyChangeEvent event = new PropertyChangeEvent(this, property, oldValue, newValue); |
| for (int i = 0; i < allListeners.length; i++) { |
| final IPropertyChangeListener listener = (IPropertyChangeListener)allListeners[i]; |
| SafeRunner.run(new SafeRunnable() { |
| public void run() throws Exception { |
| listener.propertyChange(event); |
| } |
| }); |
| } |
| } |
| |
| public void addPropertyChangeListener(IPropertyChangeListener listener) { |
| listeners.add(listener); |
| } |
| |
| public void removePropertyChangeListener(IPropertyChangeListener listener) { |
| listeners.remove(listener); |
| } |
| |
| private void handleDispose() { |
| BusyIndicator.showWhile(Display.getDefault(), new Runnable() { |
| public void run() { |
| wordArea.dispose(); |
| formToolkit.dispose(); |
| reset(); |
| } |
| }); |
| } |
| |
| protected void reset() { |
| if (wordArea.isOpen()) { |
| wordArea.close(); |
| } |
| super.reset(); |
| } |
| } |