org.eclipse.mat.ui.internal.views.NotesView.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.mat.ui.internal.views.NotesView.java

Source

/*******************************************************************************
 * Copyright (c) 2008, 2014 SAP AG and IBM Corporation.
 * 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:
 *    SAP AG - initial API and implementation
 *    Andrew Johnson - improved undo
 *******************************************************************************/
package org.eclipse.mat.ui.internal.views;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.regex.Pattern;
import java.util.zip.CRC32;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.JFacePreferences;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.TextViewerUndoManager;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.DefaultHyperlinkPresenter;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.query.ContextProvider;
import org.eclipse.mat.query.IContextObject;
import org.eclipse.mat.ui.editor.MultiPaneEditor;
import org.eclipse.mat.ui.util.PopupMenu;
import org.eclipse.mat.ui.util.QueryContextMenu;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.ISaveablePart2;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.ActionFactory.IWorkbenchAction;
import org.eclipse.ui.part.ViewPart;

public class NotesView extends ViewPart implements IPartListener, Observer, ISaveablePart, ISaveablePart2 {
    private static final String NOTES_ENCODING = "UTF8"; //$NON-NLS-1$

    private static final int UNDO_LEVEL = 25;

    private File resource;

    private Action undo;
    private Action redo;

    private Menu menu;
    private Font font;
    private Color hyperlinkColor;

    private MultiPaneEditor editor;

    private TextViewer textViewer;
    private TextViewerUndoManager undoManager;
    private Map<String, NotesViewAction> actions = new HashMap<String, NotesViewAction>();
    long hash;
    boolean modified;

    @Override
    public void createPartControl(Composite parent) {
        parent.setLayout(new FillLayout());
        // No need for a dispose listener - the SaveablePart will save it

        textViewer = new TextViewer(parent, SWT.MULTI | SWT.V_SCROLL | SWT.LEFT | SWT.H_SCROLL);
        textViewer.setDocument(new Document());
        textViewer.getControl().setEnabled(false);
        textViewer.getTextWidget().setWordWrap(false);
        font = JFaceResources.getFont("org.eclipse.mat.ui.notesfont"); //$NON-NLS-1$
        textViewer.getControl().setFont(font);

        hyperlinkColor = JFaceResources.getColorRegistry().get(JFacePreferences.HYPERLINK_COLOR);

        getSite().getPage().addPartListener(this);

        undoManager = new TextViewerUndoManager(UNDO_LEVEL);
        undoManager.connect(textViewer);
        textViewer.setUndoManager(undoManager);

        textViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent event) {
                updateActions();
            }
        });

        textViewer.addTextListener(new ITextListener() {
            public void textChanged(TextEvent event) {
                modified = true;
                searchForHyperlinks(textViewer.getDocument().get(), 0);
                firePropertyChange(PROP_DIRTY);
            }
        });

        textViewer.setHyperlinkPresenter(new DefaultHyperlinkPresenter(hyperlinkColor));
        textViewer.setHyperlinkDetectors(new IHyperlinkDetector[] { new ObjectAddressHyperlinkDetector() },
                SWT.MOD1);

        makeActions();
        hookContextMenu();
        showBootstrapPart();
        updateActions();
    }

    private void updateActions() {
        for (NotesViewAction a : actions.values())
            a.setEnabled(textViewer.canDoOperation(a.actionId));
    }

    private void hookContextMenu() {
        MenuManager menuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
        menuMgr.setRemoveAllWhenShown(true);
        menuMgr.addMenuListener(new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                textEditorContextMenuAboutToShow(manager);
            }
        });
        Menu menu = menuMgr.createContextMenu(textViewer.getControl());
        textViewer.getControl().setMenu(menu);
    }

    private void textEditorContextMenuAboutToShow(IMenuManager manager) {
        if (textViewer != null) {
            undo.setEnabled(undoManager.undoable());
            redo.setEnabled(undoManager.redoable());
            manager.add(undo);
            manager.add(redo);
            manager.add(new Separator());

            manager.add(getAction(ActionFactory.CUT.getId()));
            manager.add(getAction(ActionFactory.COPY.getId()));
            manager.add(getAction(ActionFactory.PASTE.getId()));
            manager.add(new Separator());
            manager.add(getAction(ActionFactory.DELETE.getId()));
            manager.add(getAction(ActionFactory.SELECT_ALL.getId()));
        }
    }

    private NotesViewAction getAction(String actionID) {
        return actions.get(actionID);
    }

    private Action addAction(ActionFactory actionFactory, int textOperation, String actionDefinitionId) {
        IWorkbenchWindow window = getViewSite().getWorkbenchWindow();
        IWorkbenchAction globalAction = actionFactory.create(window);

        // Create our text action.
        NotesViewAction action = new NotesViewAction(textOperation, actionDefinitionId);
        actions.put(actionFactory.getId(), action);
        // Copy its properties from the global action.
        action.setText(globalAction.getText());
        action.setToolTipText(globalAction.getToolTipText());
        action.setDescription(globalAction.getDescription());
        action.setImageDescriptor(globalAction.getImageDescriptor());

        action.setDisabledImageDescriptor(globalAction.getDisabledImageDescriptor());
        action.setAccelerator(globalAction.getAccelerator());

        // Register our text action with the global action handler.
        IActionBars actionBars = getViewSite().getActionBars();
        actionBars.setGlobalActionHandler(actionFactory.getId(), action);
        return action;
    }

    @Override
    public void setFocus() {
        textViewer.getControl().setFocus();
    }

    public void partActivated(IWorkbenchPart part) {
        if (!supportsNotes(part))
            return;

        textViewer.getControl().setEnabled(true);
        editor = (MultiPaneEditor) part;
        File path = editor.getResourceFile();

        if (path != null && !path.equals(resource)) {
            if (isDirty())
                saveNotes();
            resource = path;
            updateTextViewer();
        }
    }

    public void partBroughtToTop(IWorkbenchPart part) {
        partActivated(part);
    }

    public void partClosed(IWorkbenchPart part) {
        if (!supportsNotes(part)) {
            return;
        }

        MultiPaneEditor editor = (MultiPaneEditor) part;
        File resource = editor.getResourceFile();

        if (resource.equals(this.resource)) {
            // Saving usually done as SaveablePart except when snapshot is closed
            if (isDirty())
                saveNotes();
            this.resource = null;
            this.editor = null;
            this.updateTextViewer();
        }
    }

    public void partDeactivated(IWorkbenchPart part) {
    }

    public void partOpened(IWorkbenchPart part) {
    }

    private void showBootstrapPart() {
        IWorkbenchPage page = getSite().getPage();
        if (page != null)
            partActivated(page.getActiveEditor());
    }

    private void makeActions() {
        // Install the standard text actions.
        addAction(ActionFactory.CUT, ITextOperationTarget.CUT, "org.eclipse.ui.edit.cut");//$NON-NLS-1$
        addAction(ActionFactory.COPY, ITextOperationTarget.COPY, "org.eclipse.ui.edit.copy");//$NON-NLS-1$
        addAction(ActionFactory.PASTE, ITextOperationTarget.PASTE, "org.eclipse.ui.edit.paste");//$NON-NLS-1$
        addAction(ActionFactory.DELETE, ITextOperationTarget.DELETE, "org.eclipse.ui.edit.delete");//$NON-NLS-1$
        addAction(ActionFactory.SELECT_ALL, ITextOperationTarget.SELECT_ALL, "org.eclipse.ui.edit.selectAll");//$NON-NLS-1$
        undo = addAction(ActionFactory.UNDO, ITextOperationTarget.UNDO, "org.eclipse.ui.edit.undo");//$NON-NLS-1$
        redo = addAction(ActionFactory.REDO, ITextOperationTarget.REDO, "org.eclipse.ui.edit.redo");//$NON-NLS-1$
    }

    private long hash() {
        // Used to detect if document has changed.
        CRC32 crc = new CRC32();
        try {
            crc.update(textViewer.getDocument().get().getBytes(NOTES_ENCODING));
        } catch (UnsupportedEncodingException e) {
            // Won't happen
            return textViewer.getDocument().get().hashCode();
        }
        return crc.getValue();
    }

    public void update(Observable o, Object arg) {
        String path = (String) arg;

        if (path == null || new File(path).equals(this.resource)) {
            updateTextViewer();
        }
    }

    private void updateTextViewer() {
        // get notes.txt and if it's not null set is as input
        if (resource != null) {
            String buffer = readNotes(resource);
            if (buffer != null) {
                Document document = new Document(buffer);
                textViewer.setDocument(document);
                revealEndOfDocument();
            } else {
                textViewer.setDocument(new Document(""));//$NON-NLS-1$
            }
        } else {
            textViewer.setDocument(new Document(""));//$NON-NLS-1$
            textViewer.getControl().setEnabled(false);
        }
        hash = hash();
        modified = false;
        firePropertyChange(PROP_DIRTY);
    }

    private void searchForHyperlinks(String allText, int offset) {
        if (resource == null)
            return;
        Pattern addressPattern = Pattern.compile("0x\\p{XDigit}+");//$NON-NLS-1$        
        String[] fields = allText.split("\\W", 0);//$NON-NLS-1$
        List<IdHyperlink> hyperlinks = new ArrayList<IdHyperlink>();

        for (String field : fields) {
            if (addressPattern.matcher(field).matches()) {
                IRegion idRegion = new Region(offset, field.length());
                IdHyperlink hyperlink = new IdHyperlink(field, editor, idRegion);
                hyperlinks.add(hyperlink);
            }
            offset = offset + field.length() + 1; // length of the splitter
        }
        if (!hyperlinks.isEmpty())
            highlightHyperlinks(hyperlinks);
    }

    private void highlightHyperlinks(List<IdHyperlink> hyperlinks) {
        TextPresentation style = new TextPresentation();
        for (IHyperlink hyperlink : hyperlinks) {
            int startIndex = hyperlink.getHyperlinkRegion().getOffset();
            int length = hyperlink.getHyperlinkRegion().getLength();
            StyleRange styleRange = new StyleRange(startIndex, length, hyperlinkColor, null, SWT.ITALIC);
            styleRange.underline = true;
            style.addStyleRange(styleRange);
        }
        textViewer.changeTextPresentation(style, true);
    }

    private void saveNotes() {
        if (resource != null) {
            String text = textViewer.getDocument().get();
            if (text != null)
                saveNotes(resource, text);
            resetUndoManager();
            hash = hash();
            modified = false;
        }
    }

    @Override
    public void dispose() {
        undoManager.disconnect();
        getSite().getPage().removePartListener(this);
        // The parent composite has been disposed, so there is no need to remove the disposeListener.
        if (menu != null)
            menu.dispose();
        super.dispose();
    }

    private boolean supportsNotes(IWorkbenchPart part) {
        if (part instanceof MultiPaneEditor) {
            return true;
        }
        return false;
    }

    protected void revealEndOfDocument() {
        IDocument doc = textViewer.getDocument();
        int docLength = doc.getLength();
        if (docLength > 0) {
            textViewer.revealRange(docLength - 1, 1);
            StyledText widget = textViewer.getTextWidget();
            widget.setCaretOffset(docLength);
        }
    }

    public void resetUndoManager() {
        undoManager.reset();
    }

    private final class IdHyperlink implements IHyperlink {

        String id;
        MultiPaneEditor editor;
        IRegion region;

        public IdHyperlink(String id, MultiPaneEditor editor, IRegion region) {
            this.id = id;
            this.editor = editor;
            this.region = region;
        }

        public IRegion getHyperlinkRegion() {
            return region;
        }

        public String getHyperlinkText() {
            return null;
        }

        public String getTypeLabel() {
            return null;
        }

        public void open() {
            try {
                final int objectId = editor.getQueryContext().mapToObjectId(id);
                if (objectId < 0)
                    return;

                QueryContextMenu contextMenu = new QueryContextMenu(editor, new ContextProvider((String) null) {
                    @Override
                    public IContextObject getContext(final Object row) {
                        return new IContextObject() {

                            public int getObjectId() {
                                return (Integer) row;
                            }

                        };
                    }
                });

                PopupMenu popupMenu = new PopupMenu();
                contextMenu.addContextActions(popupMenu, new StructuredSelection(objectId), null);

                if (menu != null && !menu.isDisposed())
                    menu.dispose();

                menu = popupMenu.createMenu(getViewSite().getActionBars().getStatusLineManager(),
                        PlatformUI.getWorkbench().getDisplay().getActiveShell());
                menu.setVisible(true);
            } catch (NumberFormatException ignore) {
                // $JL-EXC$
                // shouldn't happen and if it does nothing can be done
            } catch (SnapshotException ignore) {
                // $JL-EXC$
                // mapToAddress throws exception on illegal values
            }

        }
    }

    private class ObjectAddressHyperlinkDetector extends AbstractHyperlinkDetector {

        public IHyperlink[] detectHyperlinks(ITextViewer textViewer, IRegion region,
                boolean canShowMultipleHyperlinks) {
            if (region == null || textViewer == null)
                return null;

            IDocument document = textViewer.getDocument();

            int offset = region.getOffset();
            if (document == null)
                return null;

            IRegion lineInfo;
            String text;
            try {
                lineInfo = document.getLineInformationOfOffset(offset);
                text = document.get(lineInfo.getOffset(), lineInfo.getLength());
            } catch (BadLocationException ex) {
                return null;
            }
            int index = offset - lineInfo.getOffset();
            // to the left from offset
            char ch;
            do {
                index--;
                ch = ' ';
                if (index > -1)
                    ch = text.charAt(index);
            } while (Character.isLetterOrDigit(ch));
            int startIndex = index + 1;

            // to the right from offset
            index = offset - lineInfo.getOffset() - 1;
            do {
                index++;
                if (index >= text.length()) {
                    break;
                }
                ch = text.charAt(index);
            } while (Character.isLetterOrDigit(ch));

            Pattern addressPattern = Pattern.compile("0x\\p{XDigit}+");//$NON-NLS-1$
            String address = text.substring(startIndex, index);
            if (address != null && addressPattern.matcher(address).matches()) {
                IRegion idRegion = new Region(startIndex, address.length());
                return new IHyperlink[] { new IdHyperlink(address, editor, idRegion) };
            } else
                return null;
        }
    }

    private class NotesViewAction extends Action {
        private int actionId;

        NotesViewAction(int actionId, String actionDefinitionId) {
            this.actionId = actionId;
            this.setActionDefinitionId(actionDefinitionId);
        }

        @Override
        public boolean isEnabled() {
            return textViewer.canDoOperation(actionId);
        }

        public void run() {
            textViewer.doOperation(actionId);
        }

    }

    // //////////////////////////////////////////////////////////////
    // notes management
    // //////////////////////////////////////////////////////////////

    private static String readNotes(File resourcePath) {
        try {
            if (resourcePath != null) {
                File notesFile = getDefaultNotesFile(resourcePath);
                if (notesFile.exists()) {
                    FileInputStream fileInput = new FileInputStream(getDefaultNotesFile(resourcePath));
                    try {
                        BufferedReader myInput = new BufferedReader(
                                new InputStreamReader(fileInput, NOTES_ENCODING));

                        try {
                            String s;
                            StringBuffer b = new StringBuffer();
                            while ((s = myInput.readLine()) != null) {
                                b.append(s);
                                b.append("\n");//$NON-NLS-1$
                            }
                            return b.toString();
                        } finally {
                            try {
                                myInput.close();
                            } catch (IOException ignore) {
                            }
                        }
                    } finally {
                        fileInput.close();
                    }
                }
            }

            return null;
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void saveNotes(File resource, String notes) {
        OutputStream fout = null;

        try {
            File notesFile = getDefaultNotesFile(resource);

            fout = new FileOutputStream(notesFile);
            OutputStreamWriter out = new OutputStreamWriter(new BufferedOutputStream(fout), NOTES_ENCODING);
            try {
                out.write(notes);
                out.flush();
            } finally {
                out.close();
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (fout != null)
                    fout.close();
            } catch (IOException ignore) {
            }
        }
    }

    private static File getDefaultNotesFile(File resource) {
        String filename = resource.getName();
        int p = filename.lastIndexOf('.');
        if (p >= 0)
            filename = filename.substring(0, p);
        return new File(resource.getParentFile(), filename + ".notes.txt");//$NON-NLS-1$
    }

    public void doSave(IProgressMonitor monitor) {
        saveNotes();
        firePropertyChange(PROP_DIRTY);
    }

    public void doSaveAs() {
    }

    public boolean isDirty() {
        return undoManager.undoable() || modified && hash != hash();
    }

    public boolean isSaveAsAllowed() {
        return false;
    }

    public boolean isSaveOnCloseNeeded() {
        return true;
    }

    public int promptToSaveOnClose() {
        return ISaveablePart2.YES;
    }

}