Update of /cvsroot/jcommander/plugins/org.jcommander.eclipsepatch.compare/compare/org/eclipse/compare In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv5127/compare/org/eclipse/compare Added Files: ResourceNode.java CompareUI.java IPropertyChangeNotifier.java CompareViewerSwitchingPane.java ICompareNavigator.java IEncodedStreamContentAccessor.java CompareViewerPane.java IEditableContent.java CompareConfiguration.java IViewerCreator.java CompareEditorInput.java IContentChangeNotifier.java IModificationDate.java Splitter.java IStreamContentAccessor.java BufferedContent.java ITypedElement.java package.html ZipFileStructureCreator.java EditionSelectionDialog.java NavigationAction.java IContentChangeListener.java IStreamMerger.java HistoryItem.java IResourceProvider.java Log Message: org.eclipse.compare, extracted from the Eclipse CVS. Now independent of org.eclipse.ui.ide etc. --- NEW FILE: IContentChangeListener.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; /** * An <code>IContentChangeListener</code> is informed about content changes of a * <code>IContentChangeNotifier</code>. * <p> * Clients may implement this interface. * </p> * * @see IContentChangeNotifier */ public interface IContentChangeListener { /** * Called whenever the content of the given source has changed. * * @param source the source whose contents has changed */ void contentChanged(IContentChangeNotifier source); } --- NEW FILE: Splitter.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import org.eclipse.swt.widgets.*; import org.eclipse.swt.custom.SashForm; /** * The Splitter adds support for nesting to a SashForm. * <P> * If Splitters are nested directly: * <UL> * <LI>changing the visibility of a child may propagate upward to the parent Splitter if the child * is the last child to become invisible or the first to become visible.</LI> * <LI>maximizing a child makes it as large as the topmost enclosing Splitter</LI> * </UL> * * @since 2.1 */ public class Splitter extends SashForm { private static final String VISIBILITY= "org.eclipse.compare.internal.visibility"; //$NON-NLS-1$ /** * Constructs a new instance of this class given its parent * and a style value describing its behavior and appearance. * <p> * The style value is either one of the style constants defined in * class <code>SWT</code> which is applicable to instances of this * class, or must be built by <em>bitwise OR</em>'ing together * (that is, using the <code>int</code> "|" operator) two or more * of those <code>SWT</code> style constants. The class description * lists the style constants that are applicable to the class. * Style bits are also inherited from superclasses. * </p> * * @param parent a widget which will be the parent of the new instance (cannot be null) * @param style the style of widget to construct * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * </ul> * @exception org.eclipse.swt.SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> * </ul> */ public Splitter(Composite parent, int style) { super(parent, style); } /** * Sets the visibility of the given child in this Splitter. If this change * affects the visibility state of the whole Splitter, and if the Splitter * is directly nested in one or more Splitters, this method recursively * propagates the new state upward. * * @param child the child control for which the visibility is changed * @param visible the new visibility state */ public void setVisible(Control child, boolean visible) { boolean wasEmpty= isEmpty(); child.setVisible(visible); child.setData(VISIBILITY, new Boolean(visible)); if (wasEmpty != isEmpty()) { // recursively walk up Composite parent= getParent(); if (parent instanceof Splitter) { Splitter sp= (Splitter) parent; sp.setVisible(this, visible); sp.layout(); } } else { layout(); } } /* (non-Javadoc) * Recursively calls setMaximizedControl for all direct parents that are * itself Splitters. */ public void setMaximizedControl(Control control) { if (control == null || control == getMaximizedControl()) super.setMaximizedControl(null); else super.setMaximizedControl(control); // recursively walk upward Composite parent= getParent(); if (parent instanceof Splitter) ((Splitter) parent).setMaximizedControl(this); else layout(true); } /* (non-Javadoc) * Returns true if Splitter has no children or if all children are invisible. */ private boolean isEmpty() { Control[] controls= getChildren(); for (int i= 0; i < controls.length; i++) if (isVisible(controls[i])) return false; return true; } /* (non-Javadoc) * Returns the visibility state of the given child control. If the * control is a Sash, this method always returns false. */ private boolean isVisible(Control child) { if (child instanceof Sash) return false; Object data= child.getData(VISIBILITY); if (data instanceof Boolean) return ((Boolean)data).booleanValue(); return true; } } --- NEW FILE: ITypedElement.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import org.eclipse.swt.graphics.Image; /** * Interface for getting the name, image, and type for an object. * <p> * These methods are typically used to present an input object in the compare UI * (<code>getName</code> and <code>getImage</code>) * and for finding a viewer for a given input type (<code>getType</code>). * <p> * Clients may implement this interface. */ public interface ITypedElement { /** * Type for a folder input (value <code>"FOLDER"</code>). * Folders are comparison elements that have no contents, only a name and children. */ public static final String FOLDER_TYPE= "FOLDER"; //$NON-NLS-1$ /** * Type for an element whose actual type is text (value <code>"txt"</code>). */ public static final String TEXT_TYPE= "txt"; //$NON-NLS-1$ /** * Type for an element whose actual type could not * be determined. (value <code>"???"</code>). */ public static final String UNKNOWN_TYPE= "???"; //$NON-NLS-1$ /** * Returns the name of this object. * The name is used when displaying this object in the UI. * * @return the name of this object */ String getName(); /** * Returns an image for this object. * This image is used when displaying this object in the UI. * * @return the image of this object or <code>null</code> if this type of input has no image */ Image getImage(); /** * Returns the type of this object. For objects with a file name * this is typically the file extension. For folders its the constant * <code>FOLDER_TYPE</code>. * The type is used for determining a suitable viewer for this object. * * @return the type of this object */ String getType(); } --- NEW FILE: IModificationDate.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; /** * Common interface for objects with a modification date. The modification date * can be used in the UI to give the user a general idea of how old an object is. * <p> * Clients may implement this interface. * </p> */ public interface IModificationDate { /** * Returns the modification time of this object. * <p> * Note that this value should only be used to give the user a general idea of how * old the object is. * * @return the time of last modification, in milliseconds since January 1, 1970, 00:00:00 GMT */ long getModificationDate(); } --- NEW FILE: CompareViewerPane.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.*; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.*; import org.eclipse.jface.action.ToolBarManager; /** * A <code>CompareViewerPane</code> is a convenience class which installs a * <code>CLabel</code> and a <code>Toolbar</code> in a <code>ViewForm</code>. * <P> * Double clicking onto the <code>CompareViewerPane</code>'s title bar maximizes * the <code>CompareViewerPane</code> to the size of an enclosing <code>Splitter</code> * (if there is one). * If more <code>Splitters</code> are nested maximizing walks up and * maximizes to the outermost <code>Splitter</code>. * * @since 2.0 */ public class CompareViewerPane extends ViewForm { private ToolBarManager fToolBarManager; /** * Constructs a new instance of this class given its parent * and a style value describing its behavior and appearance. * * @param container a widget which will be the container of the new instance (cannot be null) * @param style the style of widget to construct * * @exception IllegalArgumentException <ul> * <li>ERROR_NULL_ARGUMENT - if the parent is null</li> * </ul> * @exception org.eclipse.swt.SWTException <ul> * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li> * </ul> */ public CompareViewerPane(Composite container, int style) { super(container, style); marginWidth= 0; marginHeight= 0; CLabel label= new CLabel(this, SWT.NONE) { public Point computeSize(int wHint, int hHint, boolean changed) { return super.computeSize(wHint, Math.max(24, hHint), changed); } }; setTopLeft(label); MouseAdapter ml= new MouseAdapter() { public void mouseDoubleClick(MouseEvent e) { Control content= getContent(); if (content != null && content.getBounds().contains(e.x, e.y)) return; Control parent= getParent(); if (parent instanceof Splitter) ((Splitter)parent).setMaximizedControl(CompareViewerPane.this); } }; addMouseListener(ml); label.addMouseListener(ml); addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { if (fToolBarManager != null) { fToolBarManager.removeAll(); fToolBarManager.dispose(); fToolBarManager= null; } } }); } /** * Set the pane's title text. * The value <code>null</code> clears it. * * @param label the text to be displayed in the pane or null */ public void setText(String label) { CLabel cl= (CLabel) getTopLeft(); if (cl != null) cl.setText(label); } /** * Set the pane's title Image. * The value <code>null</code> clears it. * * @param image the image to be displayed in the pane or null */ public void setImage(Image image) { CLabel cl= (CLabel) getTopLeft(); if (cl != null) cl.setImage(image); } /** * Returns a <code>ToolBarManager</code> if the given parent is a * <code>CompareViewerPane</code> or <code>null</code> otherwise. * * @param parent a <code>Composite</code> or <code>null</code> * @return a <code>ToolBarManager</code> if the given parent is a <code>CompareViewerPane</code> otherwise <code>null</code> */ public static ToolBarManager getToolBarManager(Composite parent) { if (parent instanceof CompareViewerPane) { CompareViewerPane pane= (CompareViewerPane) parent; return pane.getToolBarManager(); } return null; } /** * Clears tool items in the <code>CompareViewerPane</code>'s control bar. * * @param parent a <code>Composite</code> or <code>null</code> */ public static void clearToolBar(Composite parent) { ToolBarManager tbm= getToolBarManager(parent); if (tbm != null) { tbm.removeAll(); tbm.update(true); } } //---- private stuff private ToolBarManager getToolBarManager() { if (fToolBarManager == null) { ToolBar tb= new ToolBar(this, SWT.FLAT); setTopCenter(tb); fToolBarManager= new ToolBarManager(tb); } return fToolBarManager; } } --- NEW FILE: HistoryItem.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import java.io.InputStream; import java.io.BufferedInputStream; import org.eclipse.swt.graphics.Image; import org.eclipse.compare.IResourceProvider; import org.eclipse.core.resources.IEncodedStorage; import org.eclipse.core.resources.IFileState; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; /** * A combination <code>IFileState</code> and <code>ITypedElement</code> that can be used as * an input to a compare viewer or other places where an <code>IStreamContentAccessor</code> * is needed. * <p> * <p> * Clients may instantiate this class; it is not intended to be subclassed. * </p> */ public class HistoryItem implements IEncodedStreamContentAccessor, ITypedElement, IModificationDate, IResourceProvider { private ITypedElement fBase; private IFileState fFileState; /** * Creates a <code>HistoryItem</code> object which combines the given <code>IFileState</code> * and <code>ITypedElement</code> into an object * which is suitable as input for a compare viewer or <code>ReplaceWithEditionDialog</code>. * * @param base the implementation of the <code>ITypedElement</code> interface delegates to this base <code>ITypedElement</code> * @param fileState the <code>IFileState</code> from which the streamable contents and the modification time is derived from */ public HistoryItem(ITypedElement base, IFileState fileState) { fBase= base; fFileState= fileState; } /* (non-Javadoc) * see ITypedElement.getName */ public String getName() { return fBase.getName(); } /* (non-Javadoc) * see ITypedElement.getImage */ public Image getImage() { return fBase.getImage(); } /* (non-Javadoc) * see ITypedElement.getType */ public String getType() { return fBase.getType(); } /* (non-Javadoc) * see IModificationDate.getModificationDate */ public long getModificationDate() { return fFileState.getModificationTime(); } /* (non-Javadoc) * see IStreamContentAccessor.getContents */ public InputStream getContents() throws CoreException { return new BufferedInputStream(fFileState.getContents()); } /* (non-Javadoc) * @see org.eclipse.compare.IEncodedStreamContentAccessor#getCharset() */ public String getCharset() throws CoreException { String charset= fFileState.getCharset(); if (charset == null) { IResource resource= getResource(); if (resource instanceof IEncodedStorage) charset= ((IEncodedStorage)resource).getCharset(); } return charset; } /* (non-Javadoc) * @see org.eclipse.compare.internal.IResourceProvider#getResource() */ public IResource getResource() { IPath fullPath= fFileState.getFullPath(); return ResourcesPlugin.getWorkspace().getRoot().findMember(fullPath); } } --- NEW FILE: IContentChangeNotifier.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; /** * Interface common to all objects that provide a means for registering * for content change notification. * <p> * Clients may implement this interface. * </p> * * @see IContentChangeListener */ public interface IContentChangeNotifier { /** * Adds a content change listener to this notifier. * Has no effect if an identical listener is already registered. * * @param listener a content changed listener */ void addContentChangeListener(IContentChangeListener listener); /** * Removes the given content changed listener from this notifier. * Has no effect if the listener is not registered. * * @param listener a content changed listener */ void removeContentChangeListener(IContentChangeListener listener); } --- NEW FILE: CompareUI.java --- /******************************************************************************* * 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 *******************************************************************************/ package org.eclipse.compare; import java.util.ResourceBundle; import org.eclipse.compare.internal.CompareUIPlugin; import org.eclipse.compare.internal.DocumentManager; import org.eclipse.compare.structuremergeviewer.ICompareInput; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.content.IContentType; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IReusableEditor; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.plugin.AbstractUIPlugin; /** * The class <code>CompareUI</code> defines the entry point to initiate a configurable * compare operation on arbitrary resources. The result of the compare * is opened into a compare editor where the details can be browsed and * edited in dynamically selected structure and content viewers. * <p> * The Compare UI provides a registry for content and structure compare viewers, * which is initialized from extensions contributed to extension points * declared by this plug-in. */ public final class CompareUI { /** * Compare Plug-in ID (value <code>"org.eclipse.compare"</code>). * @since 2.0 */ public static final String PLUGIN_ID= "org.eclipse.compare"; //$NON-NLS-1$ /** * The id of the Compare Preference Page * (value <code>"org.eclipse.compare.internal.ComparePreferencePage"</code>). * * @since 3.1 */ public static final String PREFERENCE_PAGE_ID= "org.eclipse.compare.internal.ComparePreferencePage"; //$NON-NLS-1$ /** * Image descriptor for the disabled icon of the 'Next' tool bar button. * @since 2.0 */ public static final ImageDescriptor DESC_DTOOL_NEXT= CompareUIPlugin.getImageDescriptor(CompareUIPlugin.DTOOL_NEXT); /** * Image descriptor for the normal icon of the 'Next' tool bar button. * @since 2.0 */ public static final ImageDescriptor DESC_CTOOL_NEXT= CompareUIPlugin.getImageDescriptor(CompareUIPlugin.CTOOL_NEXT); /** * Image descriptor for the roll-over icon of the 'Next' tool bar button. * @since 2.0 */ public static final ImageDescriptor DESC_ETOOL_NEXT= CompareUIPlugin.getImageDescriptor(CompareUIPlugin.ETOOL_NEXT); /** * Image descriptor for the disabled icon of the 'Previous' tool bar button. * @since 2.0 */ public static final ImageDescriptor DESC_DTOOL_PREV= CompareUIPlugin.getImageDescriptor(CompareUIPlugin.DTOOL_PREV); /** * Image descriptor for the normal icon of the 'Previous' tool bar button. * @since 2.0 */ public static final ImageDescriptor DESC_CTOOL_PREV= CompareUIPlugin.getImageDescriptor(CompareUIPlugin.CTOOL_PREV); /** * Image descriptor for the roll-over icon of the 'Previous' tool bar button. * @since 2.0 */ public static final ImageDescriptor DESC_ETOOL_PREV= CompareUIPlugin.getImageDescriptor(CompareUIPlugin.ETOOL_PREV); /** * Name of the title property of a compare viewer. * If a property with this name is set * on the top level SWT control of a viewer, it is used as a title in the pane's * title bar. */ public static final String COMPARE_VIEWER_TITLE= "org.eclipse.compare.CompareUI.CompareViewerTitle"; //$NON-NLS-1$ private CompareUI() { // empty implementation } public static AbstractUIPlugin getPlugin() { return CompareUIPlugin.getDefault(); } /** * Returns this plug-in's resource bundle. * * @return the plugin's resource bundle */ public static ResourceBundle getResourceBundle() { return CompareUIPlugin.getDefault().getResourceBundle(); } /** * Performs the comparison described by the given input and opens a * compare editor on the result in the currently active workbench page. * * @param input the input on which to open the compare editor */ public static void openCompareEditor(CompareEditorInput input) { openCompareEditorOnPage(input, null); } /** * Performs the comparison described by the given input and opens a * compare editor on the result in the given workbench page. * * @param input the input on which to open the compare editor * @param page the workbench page in which to open the compare editor * @since 2.1 */ public static void openCompareEditorOnPage(CompareEditorInput input, IWorkbenchPage page) { CompareUIPlugin plugin= CompareUIPlugin.getDefault(); if (plugin != null) plugin.openCompareEditor(input, page, null); } /** * Performs the comparison described by the given input and * shows the result in the given editor. * * @param input the input on which to open the compare editor * @param editor the compare editor to reuse or null to create a new one * @since 3.0 */ public static void reuseCompareEditor(CompareEditorInput input, IReusableEditor editor) { CompareUIPlugin plugin= CompareUIPlugin.getDefault(); if (plugin != null) plugin.openCompareEditor(input, null, editor); } /** * Performs the comparison described by the given input and opens a * modal compare dialog on the result. * * @param input the input on which to open the compare dialog */ public static void openCompareDialog(CompareEditorInput input) { CompareUIPlugin plugin= CompareUIPlugin.getDefault(); if (plugin != null) plugin.openCompareDialog(input); } /** * Registers an image descriptor for the given type. * * @param type the type * @param descriptor the image descriptor */ public static void registerImageDescriptor(String type, ImageDescriptor descriptor) { CompareUIPlugin.registerImageDescriptor(type, descriptor); } /** * Returns a shared image for the given type, or a generic image if none * has been registered for the given type. * <p> * Note: Images returned from this method will be automatically disposed * of when this plug-in shuts down. Callers must not dispose of these * images themselves. * </p> * * @param type the type * @return the image */ public static Image getImage(String type) { return CompareUIPlugin.getImage(type); } /** * Registers the given image for being disposed when this plug-in is shutdown. * * @param image the image to register for disposal */ public static void disposeOnShutdown(Image image) { CompareUIPlugin.disposeOnShutdown(image); } /** * Returns a shared image for the given adaptable. * This convenience method queries the given adaptable * for its <code>IWorkbenchAdapter.getImageDescriptor</code>, which it * uses to create an image if it does not already have one. * <p> * Note: Images returned from this method will be automatically disposed * of when this plug-in shuts down. Callers must not dispose of these * images themselves. * </p> * * @param adaptable the adaptable for which to find an image * @return an image */ public static Image getImage(IAdaptable adaptable) { return CompareUIPlugin.getImage(adaptable); } /** * Creates a stream merger for the given content type. * If no stream merger is registered for the given content type <code>null</code> is returned. * * @param type the type for which to find a stream merger * @return a stream merger for the given type, or <code>null</code> if no * stream merger has been registered */ public static IStreamMerger createStreamMerger(IContentType type) { return CompareUIPlugin.getDefault().createStreamMerger(type); } /** * Creates a stream merger for the given file extension. * If no stream merger is registered for the file extension <code>null</code> is returned. * * @param type the type for which to find a stream merger * @return a stream merger for the given type, or <code>null</code> if no * stream merger has been registered */ public static IStreamMerger createStreamMerger(String type) { return CompareUIPlugin.getDefault().createStreamMerger(type); } /** * Returns a structure compare viewer based on an old viewer and an input object. * If the old viewer is suitable for showing the input, the old viewer * is returned. Otherwise, the input's type is used to find a viewer descriptor in the registry * which in turn is used to create a structure compare viewer under the given parent composite. * If no viewer descriptor can be found <code>null</code> is returned. * * @param oldViewer a new viewer is only created if this old viewer cannot show the given input * @param input the input object for which to find a structure viewer * @param parent the SWT parent composite under which the new viewer is created * @param configuration a configuration which is passed to a newly created viewer * @return the compare viewer which is suitable for the given input object or <code>null</code> */ public static Viewer findStructureViewer(Viewer oldViewer, ICompareInput input, Composite parent, CompareConfiguration configuration) { return CompareUIPlugin.getDefault().findStructureViewer(oldViewer, input, parent, configuration); } /** * Returns a content compare viewer based on an old viewer and an input object. * If the old viewer is suitable for showing the input the old viewer * is returned. Otherwise the input's type is used to find a viewer descriptor in the registry * which in turn is used to create a content compare viewer under the given parent composite. * If no viewer descriptor can be found <code>null</code> is returned. * * @param oldViewer a new viewer is only created if this old viewer cannot show the given input * @param input the input object for which to find a content viewer * @param parent the SWT parent composite under which the new viewer is created * @param configuration a configuration which is passed to a newly created viewer * @return the compare viewer which is suitable for the given input object or <code>null</code> */ public static Viewer findContentViewer(Viewer oldViewer, ICompareInput input, Composite parent, CompareConfiguration configuration) { return CompareUIPlugin.getDefault().findContentViewer(oldViewer, input, parent, configuration); } /** * Returns a content compare viewer based on an old viewer and an input * object. If the old viewer is suitable for showing the input the old * viewer is returned. Otherwise the input's type is used to find a viewer * descriptor in the registry which in turn is used to create a content * compare viewer under the given parent composite. In order to determine * the input's type, the input must either implement IStreamContentAccessor * and ITypedElement or ICompareInput. If no viewer descriptor can be found * <code>null</code> is returned. * * @param oldViewer a new viewer is only created if this old viewer cannot show the given input * @param input the input object for which to find a content viewer. Must * implement either <code>IStreamContentAccessor</code> and<code> * ITypedElement</code> or <code>ICompareInput</code>. * @param parent the SWT parent composite under which the new viewer is created * @param configuration a configuration which is passed to a newly created viewer * @return the compare viewer which is suitable for the given input object or <code>null</code> */ public static Viewer findContentViewer(Viewer oldViewer, Object input, Composite parent, CompareConfiguration configuration) { return CompareUIPlugin.getDefault().findContentViewer(oldViewer, input, parent, configuration); } /** * Adds an alias for the given type. * Subsequent calls to <code>findStructureViewer</code> * treat alias as a synonym for type and return the same viewer. * <p> * Note: this method is for internal use only. Clients should not call this method. * @param type a type name for which a viewer has been registered * @param alias a type name which should be treated as a synonym of type * @since 2.0 */ public static void addStructureViewerAlias(String type, String alias) { CompareUIPlugin.getDefault().addStructureViewerAlias(type, alias); } /** * Remove all aliases for the given type. This method does not affect * the initial binding between type and viewer. If no aliases exist for the * given type this method does nothing. * <p> * Note: this method is for internal use only. Clients should not call this method. * @param type the type name for which all synonyms are removed. * @since 2.0 */ public static void removeAllStructureViewerAliases(String type) { CompareUIPlugin.getDefault().removeAllStructureViewerAliases(type); } /** * Retrieve a document for the given input or return <code>null</code> if * no document has been registered for the input. * @param input the object for which to retrieve a document * @return a document or <code>null</code> if no document was registered for the input * @since 3.1 */ public static IDocument getDocument(Object input) { return DocumentManager.get(input); } /** * Register a document for the given input. * @param input the object for which to register a document * @param document the document to register * @since 3.1 */ public static void registerDocument(Object input, IDocument document) { DocumentManager.put(input, document); } /** * Unregister the given document. * @param document the document to unregister * @since 3.1 */ public static void unregisterDocument(IDocument document) { DocumentManager.remove(document); } } --- NEW FILE: IStreamContentAccessor.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import java.io.InputStream; import org.eclipse.core.runtime.CoreException; /** * An <code>IStreamContentAccessor</code> object represents a set of bytes which can be * accessed by means of a stream. * <p> * Clients may implement this interface, or use the standard implementation, * <code>BufferedContent</code>. * * @see BufferedContent */ public interface IStreamContentAccessor { /** * Returns an open <code>InputStream</code> for this object which can be used to retrieve the object's content. * The client is responsible for closing the stream when finished. * Returns <code>null</code> if this object has no streamable contents. * * @return an input stream containing the contents of this object * @exception CoreException if the contents of this object could not be accessed */ InputStream getContents() throws CoreException; } --- NEW FILE: ResourceNode.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import java.io.*; import java.util.ArrayList; import org.eclipse.swt.graphics.Image; import org.eclipse.jface.util.Assert; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.compare.IResourceProvider; import org.eclipse.compare.internal.Utilities; import org.eclipse.compare.structuremergeviewer.IStructureComparator; /** * A <code>ResourceNode</code> wrappers an <code>IResources</code> so that it can be used * as input for the differencing engine (interfaces <code>IStructureComparator</code> and <code>ITypedElement</code>) * and the <code>ReplaceWithEditionDialog</code> (interfaces <code>ITypedElement</code> and <code>IModificationDate</code>). * <p> * Clients may instantiate this class; it is not intended to be subclassed. * </p> * * @see EditionSelectionDialog */ public class ResourceNode extends BufferedContent implements IEncodedStreamContentAccessor, IStructureComparator, ITypedElement, IEditableContent, IModificationDate, IResourceProvider { private IResource fResource; private ArrayList fChildren; /** * Creates a <code>ResourceNode</code> for the given resource. * * @param resource the resource */ public ResourceNode(IResource resource) { fResource= resource; Assert.isNotNull(resource); } /** * Returns the corresponding resource for this object. * * @return the corresponding resource */ public IResource getResource() { return fResource; } /* (non Javadoc) * see IStreamContentAccessor.getContents */ public InputStream getContents() throws CoreException { if (fResource instanceof IStorage) return super.getContents(); return null; } /* (non Javadoc) * see IModificationDate.getModificationDate */ public long getModificationDate() { IPath path= fResource.getLocation(); File file= path.toFile(); return file.lastModified(); } /* (non Javadoc) * see ITypedElement.getName */ public String getName() { if (fResource != null) return fResource.getName(); return null; } /* (non Javadoc) * see ITypedElement.getType */ public String getType() { if (fResource instanceof IContainer) return ITypedElement.FOLDER_TYPE; if (fResource != null) { String s= fResource.getFileExtension(); if (s != null) return s; } return ITypedElement.UNKNOWN_TYPE; } /* (non Javadoc) * see ITypedElement.getImage */ public Image getImage() { return CompareUI.getImage(fResource); } /* * Returns <code>true</code> if the other object is of type <code>ITypedElement</code> * and their names are identical. The content is not considered. */ public boolean equals(Object other) { if (other instanceof ITypedElement) { String otherName= ((ITypedElement)other).getName(); return getName().equals(otherName); } return super.equals(other); } /** * Returns the hash code of the name. * @return a hash code value for this object. */ public int hashCode() { return getName().hashCode(); } /* (non Javadoc) * see IStructureComparator.getChildren */ public Object[] getChildren() { if (fChildren == null) { fChildren= new ArrayList(); if (fResource instanceof IContainer) { try { IResource members[]= ((IContainer)fResource).members(); for (int i= 0; i < members.length; i++) { IStructureComparator child= createChild(members[i]); if (child != null) fChildren.add(child); } } catch (CoreException ex) { // NeedWork } } } return fChildren.toArray(); } /** * This hook method is called from <code>getChildren</code> once for every * member of a container resource. This implementation * creates a new <code>ResourceNode</code> for the given child resource. * Clients may override this method to create a different type of * <code>IStructureComparator</code> or to filter children by returning <code>null</code>. * * @param child the child resource for which a <code>IStructureComparator</code> must be returned * @return a <code>ResourceNode</code> for the given child or <code>null</code> */ protected IStructureComparator createChild(IResource child) { return new ResourceNode(child); } /** * Returns an open stream if the corresponding resource implements the * <code>IStorage</code> interface. Otherwise the value <code>null</code> is returned. * * @return a buffered input stream containing the contents of this storage * @exception CoreException if the contents of this storage could not be accessed */ protected InputStream createStream() throws CoreException { if (fResource instanceof IStorage) { InputStream is= null; IStorage storage= (IStorage) fResource; try { is= storage.getContents(); } catch (CoreException e) { if (e.getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) { fResource.refreshLocal(IResource.DEPTH_INFINITE, null); is= storage.getContents(); } else throw e; } if (is != null) return new BufferedInputStream(is); } return null; } /* (non Javadoc) * see IEditableContent.isEditable */ public boolean isEditable() { return true; } /* (non Javadoc) * see IEditableContent.replace */ public ITypedElement replace(ITypedElement child, ITypedElement other) { return child; } /* (non-Javadoc) * @see org.eclipse.compare.IEncodedStreamContentAccessor#getCharset() */ public String getCharset() { return Utilities.getCharset(fResource); } } --- NEW FILE: package.html --- <!doctype html public "-//w3c//dtd html 4.0 transitional//en"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> <meta name="Author" content="IBM"> <meta name="GENERATOR" content="Mozilla/4.75 [en] (WinNT; U) [Netscape]"> <title>Package-level Javadoc</title> </head> <body> Provides support for performing structural and textual compare operations on arbitrary data and displaying the results. <h2> Package Specification</h2> The class <b>CompareUI</b> defines the entry point to initiate a configurable compare operation on arbitrary resources. The result of the compare is opened into a compare editor where the details can be browsed and edited in dynamically selected structure and content viewers. <p> A compare operation must be implemented as a subclass of <b>CompareEditorInput</b>. A <b>CompareEditorInput</b> runs a (potentially lengthy) compare operation under progress monitor control, creates a UI for drilling-down into the compare results, tracks the dirty state of the result in case of merge, and saves any changes that occured during a merge. <p> The <b>NavigationAction</b> is used to navigate (step) through the individual differences of a <b>CompareEditorInput</b>. <p> An instance of <b>CompareConfiguration</b> configures various UI aspects of compare/merge viewers like title labels and images, or whether a side of a merge viewer is editable. It is passed to the <b>CompareEditorInput</b> on creation. <p> When implementing a hierarchical compare operation as a subclass of <b>CompareEditorInput</b> clients have to provide a tree of objects where each node implements the interface <b>org.eclipse.compare.structuremergeviewer.IStructureComparator</b>. This interface is used by the hierarchical differencing engine (<b>org.eclipse.compare.structuremergeviewer.Differencer</b>) to walk the tree. <br> In addition every leaf of the tree must implement the <b>IStreamContentAccessor</b> or <b>IEncodedStreamContentAccessor</b> interfaces in order to give the differencing engine access to its stream content and to its encoding (with IEncodedStreamContentAccessor). <p> The abstract class <b>BufferedContent</b> provides a default implementation for the <b>IStreamContentAccessor</b> and <b>IContentChangeNotifier</b> interfaces. Its subclass <b>ResourceNode</b> adds an implementation for the <b>IStructureComparator</b> and <b>ITypedElement</b> interfaces based on Eclipse workbench resources (org.eclipse.core.resources.IResource). It can be used without modification as the input to the differencing engine. <p> The <b>ZipFileStructureCreator</b> is an implementation of the <b>org.eclipse.compare.structuremergeviewer.IStructureCreator</b> interface and makes the contents of a zip archive available as a hierarchical structure of <b>IStructureComparator</b>s which can be easily compared by the differencing engine (<b>org.eclipse.compare.structuremergeviewer.Differencer</b>). It is a good example for how to make structured files available to the hierarchical compare functionality of the Compare plugin. <p> The <b>EditionSelectionDialog</b> is a simple selection dialog where one input element can be compared against a list of historic variants (<i>editions</i>) of the same input element. The dialog can be used to implement functions like <i>"Replace with Version"</i> or <i>"Replace with Edition"</i> on workbench resources. <p> In addition it is possible to specify a subsection of the input element (e.g. a method in a Java source file) by means of a <i>path</i>. In this case the dialog compares only the subsection (as specified by the path) with the corresponding subsection in the list of editions. This functionality can be used to implement <i>"Replace with Method Edition"</i> for the Java language. <p> The <b>EditionSelectionDialog</b> requires that the editions implement the <b>IStreamContentAccessor</b> and <b>IModificationDate</b> interfaces. The <b>HistoryItem</b> is a convenience class that implements these interfaces for <b>IFileState</b> objects. <p> The <b>CompareViewerPane</b> is a convenience class which provides a label and local toolbar for a compare viewer (or any other subclass of a JFace <b>Viewer</b>). <br> Its abstract subclass <b>CompareViewerSwitchingPane</b> supports <i>dynamic viewer switching</i>, that is the viewer installed in the pane is dynamically determined by the pane's input object. Both classes are useful if you want to use compare viewers outside the context of a compare editor, for example in a dialog or wizard. <p> A <b>Splitter</b> is an extension of a SashForm that supports nesting, maximizing of panes, and propagating the visibility state of panes. <p> The interface <b>IStreamMerger</b> defines a single operation for performing a three-way merge on three input streams. The merged result is written to an output stream. <br> Clients must implement this interface when contributing new mergers to the <code>org.eclipse.compare.streamMergers</code> extension point. New <b>IStreamMerger</b>s can be created for registered types with the createStreamMerger methods of CompareUI. </body> </html> --- NEW FILE: IViewerCreator.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import org.eclipse.swt.widgets.Composite; import org.eclipse.jface.viewers.Viewer; /** * A factory object for <code>Viewer</code>. * <p> * This interface is only required when creating a <code>Viewer</code> from a plugin.xml file. * Since <code>Viewer</code>s have no default constructor they cannot be * instantiated directly with <code>Class.forName</code>. */ public interface IViewerCreator { /** * Creates a new viewer under the given SWT parent control. * * @param parent the SWT parent control under which to create the viewer's SWT control * @param config a compare configuration the newly created viewer might want to use * @return a new viewer */ Viewer createViewer(Composite parent, CompareConfiguration config); } --- NEW FILE: IEncodedStreamContentAccessor.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import org.eclipse.core.runtime.CoreException; /** * Extension for <code>IStreamContentAccessor</code>. Extends the original * concept of a <code>IStreamContentAccessor</code> to answer the Charset (encoding) used for the stream. * * @since 3.0 */ public interface IEncodedStreamContentAccessor extends IStreamContentAccessor { /** * Returns the name of a charset encoding to be used when decoding this * stream accessor's contents into characters. Returns <code>null</code> if a proper * encoding cannot be determined. * <p> * <b>Note</b>: this method does not check whether the result is a supported * charset name. Callers should be prepared to handle * <code>UnsupportedEncodingException</code> where this charset is used. * </p> * @return the name of a charset, or <code>null</code> * @exception CoreException if an error happens while determining * the charset. See any refinements for more information. * @see IStreamContentAccessor#getContents * @since 3.0 */ String getCharset() throws CoreException; } --- NEW FILE: BufferedContent.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import java.io.*; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.CoreException; import org.eclipse.compare.internal.Utilities; /** * Abstract implementation for a buffered <code>IStreamContentAccessor</code>. * <p> * Subclasses must implement the <code>createStream</code> method * to connect the buffered content with a streamable source (e.g., a file). * <p> * As long as the contents of <code>BufferedContent</code> is only retrieved as an input stream * (by means of <code>getContents</code>) and the <code>BufferedContent</code> is not modified (with * <code>setContent</code>) no buffering takes place. * Buffering starts when either method <code>getContent</code> or <code>setContent</code> is called. * * @see IContentChangeNotifier * @see IStreamContentAccessor */ public abstract class BufferedContent implements IContentChangeNotifier, IStreamContentAccessor { byte[] fContent; private ListenerList fListenerList; /** * Creates a buffered stream content accessor. */ protected BufferedContent() { // empty implementation } /* (non-Javadoc) * see IStreamContentAccessor.getContents */ public InputStream getContents() throws CoreException { if (fContent != null) return new ByteArrayInputStream(fContent); return createStream(); } /** * Creates and returns a stream for reading the contents. * <p> * Subclasses must implement this method. * </p> * * @return the stream from which the content is read * @exception CoreException if the contents could not be accessed */ protected abstract InputStream createStream() throws CoreException; /** * Sets the contents. Registered content change listeners are notified. * * @param contents the new contents */ public void setContent(byte[] contents) { fContent= contents; fireContentChanged(); } /** * Returns the contents as an array of bytes. * * @return the contents as an array of bytes, or <code>null</code> if * the contents could not be accessed */ public byte[] getContent() { if (fContent == null) { try { InputStream is= createStream(); fContent= Utilities.readBytes(is); } catch(CoreException ex) { // NeedWork } } return fContent; } /** * Discards the buffered content. */ public void discardBuffer() { fContent= null; } /* (non-Javadoc) * see IContentChangeNotifier.addChangeListener */ public void addContentChangeListener(IContentChangeListener listener) { if (fListenerList == null) fListenerList= new ListenerList(); fListenerList.add(listener); } /* (non-Javadoc) * see IContentChangeNotifier.removeChangeListener */ public void removeContentChangeListener(IContentChangeListener listener) { if (fListenerList != null) { fListenerList.remove(listener); if (fListenerList.isEmpty()) fListenerList= null; } } /** * Notifies all registered <code>IContentChangeListener</code>s of a content change. */ protected void fireContentChanged() { if (fListenerList != null) { Object[] listeners= fListenerList.getListeners(); for (int i= 0; i < listeners.length; i++) ((IContentChangeListener)listeners[i]).contentChanged(this); } } } --- NEW FILE: IPropertyChangeNotifier.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import org.eclipse.jface.util.IPropertyChangeListener; /** * Interface common to all objects that provide a means for registering * for property change notification. * <p> * Clients may implement this interface. * </p> * * @see org.eclipse.jface.util.IPropertyChangeListener */ public interface IPropertyChangeNotifier { /** * Adds a listener for property changes to this notifier. * Has no effect if an identical listener is already registered. * * @param listener a property change listener */ void addPropertyChangeListener(IPropertyChangeListener listener); /** * Removes the given content change listener from this notifier. * Has no effect if the identical listener is not registered. * * @param listener a property change listener */ void removePropertyChangeListener(IPropertyChangeListener listener); } --- NEW FILE: NavigationAction.java --- /******************************************************************************* * Copyright (c) 2000, 2004 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 *******************************************************************************/ package org.eclipse.compare; import java.util.ResourceBundle; import org.eclipse.jface.action.Action; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.compare.internal.CompareMessages; import org.eclipse.compare.internal.CompareUIPlugin; import org.eclipse.compare.internal.Utilities; /** * A <code>NavigationAction</code> is used to navigate through the individual * differences of a <code>CompareEditorInput</code>. * <p> * Clients may instantiate this class; it is not intended to be subclassed. * </p> * @since 2.0 */ public class NavigationAction extends Action { private boolean fNext; private CompareEditorInput fCompareEditorInput; /** * Creates a <code>NavigationAction</code>. * * @param next if <code>true</code> action goes to the next difference; otherwise to the previous difference. */ public NavigationAction(boolean next) { this(CompareUI.getResourceBundle(), next); } /** * Creates a <code>NavigationAction</code> that initializes its attributes * from the given <code>ResourceBundle</code>. * * @param bundle is used to initialize the action * @param next if <code>true</code> action goes to the next difference; otherwise to the previous difference. */ public NavigationAction(ResourceBundle bundle, boolean next) { Utilities.initAction(this, bundle, next ? "action.Next." : "action.Previous."); //$NON-NLS-2$ //$NON-NLS-1$ fNext= next; } public void run() { if (fCompareEditorInput != null) { Object adapter= fCompareEditorInput.getAdapter(ICompareNavigator.class); if (adapter instanceof ICompareNavigator) { boolean atEnd= ((ICompareNavigator)adapter).selectChange(fNext); Shell shell= CompareUIPlugin.getShell(); if (atEnd && shell != null) { Display display= shell.getDisplay(); if (display != null) display.beep(); String title; String message; if (fNext) { title= CompareMessages.CompareNavigator_atEnd_title; message= CompareMessages.CompareNavigator_atEnd_message; } else { title= CompareMessages.CompareNavigator_atBeginning_title; message= CompareMessages.CompareNavigator_atBeginning_message; } MessageDialog.openInformation(shell, title, message); } } } } /** * Sets the <code>CompareEditorInput</code> on which this action operates. * * @param input the <code>CompareEditorInput</code> on which this action operates; if <code>null</code> action does nothing */ public void setCompareEditorInput(CompareEditorInput input) { fCompareEditorInput= input; } } --- NEW FILE: EditionSelectionDialog.java --- /******************************************************************************* * 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 *******************************************************************************/ package org.eclipse.compare; import java.util.Arrays; import java.util.HashMap; import java.util.ResourceBundle; import java.util.Date; import java.util.List; import java.util.ArrayList; import java.util.Iterator; [...1099 lines suppressed...] } if (fCommitButton != null) { if (fMultiSelect) fCommitButton.setEnabled(isOK && fSelectedItem != null && fArrayList.size() > 0); else fCommitButton.setEnabled(isOK && fSelectedItem != null && fTargetPair.getItem() != fSelectedItem); } } /* * Feeds selection from structure viewer to content viewer. */ private void feedInput2(ISelection sel) { if (sel instanceof IStructuredSelection) { IStructuredSelection ss= (IStructuredSelection) sel; if (ss.size() == 1) fContentPane.setInput(ss.getFirstElement()); } } } --- NEW FILE: CompareConfiguration.java --- /******************************************************************************* * 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 *******************************************************************************/ package org.eclipse.compare; import java.util.HashMap; import org.eclipse.swt.graphics.Image; import org.eclipse.core.runtime.ListenerList; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.compare.internal.*; import org.eclipse.compare.structuremergeviewer.Differencer; /** * A <code>CompareConfiguration</code> object * controls various UI aspects of compare/merge viewers like * title labels and images, or whether a side of a merge viewer is editable. * In addition to these fixed properties <code>ICompareConfiguration</code> provides * API for an open ended set of properties. Different viewers which share the same * configuration can communicate via this mechanism. E.g. if a compare editor * has a button for controlling whether compare viewers ignore white space, * the button would trigger... [truncated message content] |