org.eclipse.ui.internal.views.log.LogView.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ui.internal.views.log.LogView.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2014 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
 *     Jacek Pospychala <jacek.pospychala@pl.ibm.com> - bugs 202583, 202584, 207344
 *                                                      bugs 207323, 207931, 207101
 *                                                      bugs 172658, 216341, 216657
 *     Benjamin Cabe <benjamin.cabe@anyware-tech.com> - bug 218648
 *     Tuukka Lehtonen <tuukka.lehtonen@semantum.fi>  - bug 247907
 *     Eike Stepper <stepper@esc-net.de>              - bug 429372
 *******************************************************************************/

package org.eclipse.ui.internal.views.log;

import com.ibm.icu.text.DateFormat;
import com.ibm.icu.text.SimpleDateFormat;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.*;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.Window;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.dnd.*;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.*;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.dialogs.FilteredTree;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.part.ViewPart;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

public class LogView extends ViewPart implements ILogListener {
    public static final String P_LOG_WARNING = "warning"; //$NON-NLS-1$
    public static final String P_LOG_ERROR = "error"; //$NON-NLS-1$
    public static final String P_LOG_INFO = "info"; //$NON-NLS-1$
    public static final String P_LOG_OK = "ok"; //$NON-NLS-1$

    /** 
     * Maximum tail size of the log file in Mega Bytes (1024 * 1024 Bytes) considers the last XYZ MB of the log file to create log entries.
     * This value should be increased if the size of the sub elements of the last (most recent) log entry in the log file exceeds the maximum tail size. 
     **/
    public static final String P_LOG_MAX_TAIL_SIZE = "maxLogTailSize"; //$NON-NLS-1$
    public static final String P_LOG_LIMIT = "limit"; //$NON-NLS-1$
    public static final String P_USE_LIMIT = "useLimit"; //$NON-NLS-1$
    public static final String P_SHOW_ALL_SESSIONS = "allSessions"; //$NON-NLS-1$
    protected static final String P_COLUMN_1 = "column2"; //$NON-NLS-1$
    protected static final String P_COLUMN_2 = "column3"; //$NON-NLS-1$
    protected static final String P_COLUMN_3 = "column4"; //$NON-NLS-1$
    public static final String P_ACTIVATE = "activate"; //$NON-NLS-1$
    public static final String P_SHOW_FILTER_TEXT = "show_filter_text"; //$NON-NLS-1$
    public static final String P_ORDER_TYPE = "orderType"; //$NON-NLS-1$
    public static final String P_ORDER_VALUE = "orderValue"; //$NON-NLS-1$
    public static final String P_IMPORT_LOG = "importLog"; //$NON-NLS-1$
    public static final String P_GROUP_BY = "groupBy"; //$NON-NLS-1$

    /** default values **/
    private static final int DEFAULT_LOG_MAX_TAIL_SIZE = 1; // 1 Mega Byte

    private int MESSAGE_ORDER;
    private int PLUGIN_ORDER;
    private int DATE_ORDER;

    public final static byte MESSAGE = 0x0;
    public final static byte PLUGIN = 0x1;
    public final static byte DATE = 0x2;
    public static int ASCENDING = 1;
    public static int DESCENDING = -1;

    public static final int GROUP_BY_NONE = 0;
    public static final int GROUP_BY_SESSION = 1;
    public static final int GROUP_BY_PLUGIN = 2;

    private List elements;
    private Map groups;
    private LogSession currentSession;

    private List batchedEntries;
    private boolean batchEntries;

    private Clipboard fClipboard;

    private IMemento fMemento;
    private File fInputFile;
    private String fDirectory;

    private Comparator fComparator;

    // hover text
    private boolean fCanOpenTextShell;
    private Text fTextLabel;
    private Shell fTextShell;

    private boolean fFirstEvent = true;

    private TreeColumn fColumn1;
    private TreeColumn fColumn2;
    private TreeColumn fColumn3;

    private Tree fTree;
    private FilteredTree fFilteredTree;
    private LogViewLabelProvider fLabelProvider;

    private Action fPropertiesAction;
    private Action fDeleteLogAction;
    private Action fReadLogAction;
    private Action fCopyAction;
    private Action fActivateViewAction;
    private Action fOpenLogAction;
    private Action fExportLogAction;
    private Action fExportLogEntryAction;

    /**
     * Action called when user selects "Group by -> ..." from menu.
     */
    class GroupByAction extends Action {
        private int groupBy;

        public GroupByAction(String text, int groupBy) {
            super(text, IAction.AS_RADIO_BUTTON);

            this.groupBy = groupBy;

            if (fMemento.getInteger(LogView.P_GROUP_BY).intValue() == groupBy) {
                setChecked(true);
            }
        }

        public void run() {
            if (fMemento.getInteger(LogView.P_GROUP_BY).intValue() != groupBy) {
                fMemento.putInteger(LogView.P_GROUP_BY, groupBy);
                reloadLog();
            }
        }
    }

    /**
     * Constructor
     */
    public LogView() {
        elements = new ArrayList();
        groups = new HashMap();
        batchedEntries = new ArrayList();
        fInputFile = Platform.getLogFileLocation().toFile();
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.part.WorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    public void createPartControl(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.horizontalSpacing = 0;
        layout.verticalSpacing = 0;
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        composite.setLayout(layout);
        composite.setLayoutData(new GridData(GridData.FILL_BOTH));

        readLogFile();
        createViewer(composite);
        getSite().setSelectionProvider(fFilteredTree.getViewer());
        createActions();
        fClipboard = new Clipboard(fTree.getDisplay());
        fTree.setToolTipText(""); //$NON-NLS-1$
        initializeViewerSorter();

        makeHoverShell();

        Platform.addLogListener(this);
        PlatformUI.getWorkbench().getHelpSystem().setHelp(fFilteredTree, IHelpContextIds.LOG_VIEW);
        getSite().getWorkbenchWindow().addPerspectiveListener(new IPerspectiveListener2() {

            public void perspectiveChanged(IWorkbenchPage page, IPerspectiveDescriptor perspective,
                    IWorkbenchPartReference partRef, String changeId) {
                if (!(partRef instanceof IViewReference))
                    return;

                IWorkbenchPart part = partRef.getPart(false);
                if (part == null) {
                    return;
                }

                if (part.equals(LogView.this)) {
                    if (changeId.equals(IWorkbenchPage.CHANGE_VIEW_SHOW)) {
                        if (!batchedEntries.isEmpty()) {
                            pushBatchedEntries();
                        }

                        batchEntries = false;
                    } else if (changeId.equals(IWorkbenchPage.CHANGE_VIEW_HIDE)) {
                        batchEntries = true;
                    }
                }
            }

            public void perspectiveActivated(IWorkbenchPage page, IPerspectiveDescriptor perspective) {
                // empty
            }

            public void perspectiveChanged(IWorkbenchPage page, IPerspectiveDescriptor perspective,
                    String changeId) {
                // empty
            }

        });
    }

    /**
     * Creates the actions for the viewsite action bars
     */
    private void createActions() {
        IActionBars bars = getViewSite().getActionBars();

        fCopyAction = createCopyAction();
        bars.setGlobalActionHandler(ActionFactory.COPY.getId(), fCopyAction);

        IToolBarManager toolBarManager = bars.getToolBarManager();

        fExportLogAction = createExportLogAction();
        toolBarManager.add(fExportLogAction);

        fExportLogEntryAction = createExportLogEntryAction();

        final Action importLogAction = createImportLogAction();
        toolBarManager.add(importLogAction);

        toolBarManager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));

        final Action clearAction = createClearAction();
        toolBarManager.add(clearAction);

        fDeleteLogAction = createDeleteLogAction();
        toolBarManager.add(fDeleteLogAction);

        fOpenLogAction = createOpenLogAction();
        toolBarManager.add(fOpenLogAction);

        fReadLogAction = createReadLogAction();
        toolBarManager.add(fReadLogAction);

        toolBarManager.add(new Separator());

        IMenuManager mgr = bars.getMenuManager();

        mgr.add(createGroupByAction());
        mgr.add(new Separator());
        mgr.add(createFilterAction());
        mgr.add(new Separator());

        fActivateViewAction = createActivateViewAction();
        mgr.add(fActivateViewAction);
        if (fFilteredTree.getFilterControl() != null)
            mgr.add(createShowTextFilter());

        fPropertiesAction = createPropertiesAction();

        MenuManager popupMenuManager = new MenuManager("#PopupMenu"); //$NON-NLS-1$
        IMenuListener listener = new IMenuListener() {
            public void menuAboutToShow(IMenuManager manager) {
                manager.add(fCopyAction);
                manager.add(new Separator());
                manager.add(clearAction);
                manager.add(fDeleteLogAction);
                manager.add(fOpenLogAction);
                manager.add(fReadLogAction);
                manager.add(new Separator());
                manager.add(fExportLogAction);
                manager.add(createImportLogAction());
                manager.add(new Separator());
                manager.add(fExportLogEntryAction);
                manager.add(new Separator());

                ((EventDetailsDialogAction) fPropertiesAction).setComparator(fComparator);
                TreeItem[] selection = fTree.getSelection();
                if ((selection.length > 0) && (selection[0].getData() instanceof LogEntry)) {
                    manager.add(fPropertiesAction);
                }

                manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
            }
        };
        popupMenuManager.addMenuListener(listener);
        popupMenuManager.setRemoveAllWhenShown(true);
        getSite().registerContextMenu(popupMenuManager, getSite().getSelectionProvider());
        Menu menu = popupMenuManager.createContextMenu(fTree);
        fTree.setMenu(menu);
    }

    private Action createActivateViewAction() {
        Action action = new Action(Messages.LogView_activate) { //
            public void run() {
                fMemento.putString(P_ACTIVATE, isChecked() ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
            }
        };
        action.setChecked(fMemento.getString(P_ACTIVATE).equals("true")); //$NON-NLS-1$
        return action;
    }

    private Action createClearAction() {
        Action action = new Action(Messages.LogView_clear) {
            public void run() {
                handleClear();
            }
        };
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_CLEAR));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_CLEAR_DISABLED));
        action.setToolTipText(Messages.LogView_clear_tooltip);
        action.setText(Messages.LogView_clear);
        return action;
    }

    private Action createCopyAction() {
        Action action = new Action(Messages.LogView_copy) {
            public void run() {
                copyToClipboard(fFilteredTree.getViewer().getSelection());
            }
        };
        action.setImageDescriptor(
                PlatformUI.getWorkbench().getSharedImages().getImageDescriptor(ISharedImages.IMG_TOOL_COPY));
        return action;
    }

    private Action createDeleteLogAction() {
        Action action = new Action(Messages.LogView_delete) {
            public void run() {
                doDeleteLog();
            }
        };
        action.setToolTipText(Messages.LogView_delete_tooltip);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_REMOVE_LOG));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_REMOVE_LOG_DISABLED));
        action.setEnabled(fInputFile.exists() && fInputFile.equals(Platform.getLogFileLocation().toFile()));
        return action;
    }

    private Action createExportLogAction() {
        Action action = new Action(Messages.LogView_export) {
            public void run() {
                handleExport(true);
            }
        };
        action.setToolTipText(Messages.LogView_export_tooltip);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_EXPORT));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_EXPORT_DISABLED));
        action.setEnabled(fInputFile.exists());
        return action;
    }

    private Action createExportLogEntryAction() {
        Action action = new Action(Messages.LogView_exportEntry) {
            public void run() {
                handleExport(false);
            }
        };
        action.setToolTipText(Messages.LogView_exportEntry_tooltip);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_EXPORT));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_EXPORT_DISABLED));
        action.setEnabled(!fFilteredTree.getViewer().getSelection().isEmpty());
        return action;
    }

    private Action createFilterAction() {
        Action action = new Action(Messages.LogView_filter) {
            public void run() {
                handleFilter();
            }
        };
        action.setToolTipText(Messages.LogView_filter);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_FILTER));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_FILTER_DISABLED));
        return action;
    }

    private Action createImportLogAction() {
        Action action = new ImportLogAction(this, Messages.LogView_import, fMemento);
        action.setToolTipText(Messages.LogView_import_tooltip);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_IMPORT));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_IMPORT_DISABLED));
        return action;
    }

    private Action createOpenLogAction() {
        Action action = null;
        try {
            // TODO this isn't the best way to check... we should be smarter and use package admin
            // check to see if org.eclipse.ui.ide is available
            Class.forName("org.eclipse.ui.ide.IDE"); //$NON-NLS-1$
            // check to see if org.eclipse.core.filesystem is available
            Class.forName("org.eclipse.core.filesystem.IFileStore"); //$NON-NLS-1$
            action = new OpenIDELogFileAction(this);
        } catch (ClassNotFoundException e) {
            action = new Action() {
                public void run() {
                    if (fInputFile.exists()) {
                        Job job = getOpenLogFileJob();
                        job.setUser(false);
                        job.setPriority(Job.SHORT);
                        job.schedule();
                    }
                }
            };
        }
        action.setText(Messages.LogView_view_currentLog);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_OPEN_LOG));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_OPEN_LOG_DISABLED));
        action.setEnabled(fInputFile.exists());
        action.setToolTipText(Messages.LogView_view_currentLog_tooltip);
        return action;
    }

    private Action createPropertiesAction() {
        Action action = new EventDetailsDialogAction(fTree, fFilteredTree.getViewer(), fMemento);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_PROPERTIES));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_PROPERTIES_DISABLED));
        action.setToolTipText(Messages.LogView_properties_tooltip);
        action.setEnabled(false);
        return action;
    }

    private Action createReadLogAction() {
        Action action = new Action(Messages.LogView_readLog_restore) {
            public void run() {
                fInputFile = Platform.getLogFileLocation().toFile();
                reloadLog();
            }
        };
        action.setToolTipText(Messages.LogView_readLog_restore_tooltip);
        action.setImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_READ_LOG));
        action.setDisabledImageDescriptor(SharedImages.getImageDescriptor(SharedImages.DESC_READ_LOG_DISABLED));
        return action;
    }

    /**
     * Creates the Show Text Filter view menu action
     * @return the new action for the Show Text Filter
     */
    private Action createShowTextFilter() {
        Action action = new Action(Messages.LogView_show_filter_text) {
            public void run() {
                showFilterText(isChecked());
            }
        };
        boolean visible = fMemento.getBoolean(P_SHOW_FILTER_TEXT).booleanValue();
        action.setChecked(visible);
        showFilterText(visible);
        return action;
    }

    /**
     * Shows/hides the filter text control from the filtered tree. This method also sets the
     * P_SHOW_FILTER_TEXT preference to the visible state
     *
     * @param visible if the filter text control should be shown or not
     */
    private void showFilterText(boolean visible) {
        fMemento.putBoolean(P_SHOW_FILTER_TEXT, visible);

        Text filterControl = fFilteredTree.getFilterControl();
        Composite filterComposite = filterControl.getParent(); // FilteredTree new look lays filter Text on additional composite

        GridData gd = (GridData) filterComposite.getLayoutData();
        gd.exclude = !visible;
        filterComposite.setVisible(visible);

        // reset control if we aren't visible and if we get visible again
        filterControl.setText(Messages.LogView_show_filter_initialText);

        if (visible) {
            filterControl.selectAll();
            setFocus();
        }

        fFilteredTree.layout(false);
    }

    private IContributionItem createGroupByAction() {
        IMenuManager manager = new MenuManager(Messages.LogView_GroupBy);
        manager.add(new GroupByAction(Messages.LogView_GroupBySession, LogView.GROUP_BY_SESSION));
        manager.add(new GroupByAction(Messages.LogView_GroupByPlugin, LogView.GROUP_BY_PLUGIN));
        manager.add(new GroupByAction(Messages.LogView_GroupByNone, LogView.GROUP_BY_NONE));
        return manager;
    }

    private void createViewer(Composite parent) {
        PatternFilter filter = new PatternFilter() {
            protected boolean isLeafMatch(Viewer viewer, Object element) {
                if (element instanceof LogEntry) {
                    LogEntry logEntry = (LogEntry) element;
                    String message = logEntry.getMessage();
                    String plugin = logEntry.getPluginId();
                    DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
                    String date = dateFormat.format(logEntry.getDate());
                    return wordMatches(message) || wordMatches(plugin) || wordMatches(date);
                }
                return false;
            }
        };
        filter.setIncludeLeadingWildcard(true);
        fFilteredTree = new FilteredTree(parent, SWT.FULL_SELECTION, filter, true);
        // need to give filter Textbox some space from the border
        if (fFilteredTree.getFilterControl() != null) {
            Composite filterComposite = fFilteredTree.getFilterControl().getParent(); // FilteredTree new look lays filter Text on additional composite
            GridData gd = (GridData) filterComposite.getLayoutData();
            gd.verticalIndent = 2;
            gd.horizontalIndent = 1;
        }
        fFilteredTree.setLayoutData(new GridData(GridData.FILL_BOTH));
        fFilteredTree.setInitialText(Messages.LogView_show_filter_initialText);
        fTree = fFilteredTree.getViewer().getTree();
        fTree.setLinesVisible(true);
        createColumns(fTree);
        fFilteredTree.getViewer().setAutoExpandLevel(2);
        fFilteredTree.getViewer().setContentProvider(new LogViewContentProvider(this));
        fFilteredTree.getViewer().setLabelProvider(fLabelProvider = new LogViewLabelProvider(this));
        fLabelProvider.connect(this);
        fFilteredTree.getViewer().addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(SelectionChangedEvent e) {
                handleSelectionChanged(e.getSelection());
                if (fPropertiesAction.isEnabled())
                    ((EventDetailsDialogAction) fPropertiesAction).resetSelection();
            }
        });
        fFilteredTree.getViewer().addDoubleClickListener(new IDoubleClickListener() {
            public void doubleClick(DoubleClickEvent event) {
                ((EventDetailsDialogAction) fPropertiesAction).setComparator(fComparator);
                fPropertiesAction.run();
            }
        });
        fFilteredTree.getViewer().setInput(this);
        addMouseListeners();
        addDragSource();
    }

    private void createColumns(Tree tree) {
        fColumn1 = new TreeColumn(tree, SWT.LEFT);
        fColumn1.setText(Messages.LogView_column_message);
        fColumn1.setWidth(fMemento.getInteger(P_COLUMN_1).intValue());
        fColumn1.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                MESSAGE_ORDER *= -1;
                ViewerComparator comparator = getViewerComparator(MESSAGE);
                fFilteredTree.getViewer().setComparator(comparator);
                boolean isComparatorSet = ((EventDetailsDialogAction) fPropertiesAction).resetSelection(MESSAGE,
                        MESSAGE_ORDER);
                setComparator(MESSAGE);
                if (!isComparatorSet)
                    ((EventDetailsDialogAction) fPropertiesAction).setComparator(fComparator);
                fMemento.putInteger(P_ORDER_VALUE, MESSAGE_ORDER);
                fMemento.putInteger(P_ORDER_TYPE, MESSAGE);
                setColumnSorting(fColumn1, MESSAGE_ORDER);
            }
        });

        fColumn2 = new TreeColumn(tree, SWT.LEFT);
        fColumn2.setText(Messages.LogView_column_plugin);
        fColumn2.setWidth(fMemento.getInteger(P_COLUMN_2).intValue());
        fColumn2.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                PLUGIN_ORDER *= -1;
                ViewerComparator comparator = getViewerComparator(PLUGIN);
                fFilteredTree.getViewer().setComparator(comparator);
                boolean isComparatorSet = ((EventDetailsDialogAction) fPropertiesAction).resetSelection(PLUGIN,
                        PLUGIN_ORDER);
                setComparator(PLUGIN);
                if (!isComparatorSet)
                    ((EventDetailsDialogAction) fPropertiesAction).setComparator(fComparator);
                fMemento.putInteger(P_ORDER_VALUE, PLUGIN_ORDER);
                fMemento.putInteger(P_ORDER_TYPE, PLUGIN);
                setColumnSorting(fColumn2, PLUGIN_ORDER);
            }
        });

        fColumn3 = new TreeColumn(tree, SWT.LEFT);
        fColumn3.setText(Messages.LogView_column_date);
        fColumn3.setWidth(fMemento.getInteger(P_COLUMN_3).intValue());
        fColumn3.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                DATE_ORDER *= -1;
                ViewerComparator comparator = getViewerComparator(DATE);
                fFilteredTree.getViewer().setComparator(comparator);
                setComparator(DATE);
                ((EventDetailsDialogAction) fPropertiesAction).setComparator(fComparator);
                fMemento.putInteger(P_ORDER_VALUE, DATE_ORDER);
                fMemento.putInteger(P_ORDER_TYPE, DATE);
                setColumnSorting(fColumn3, DATE_ORDER);
            }
        });

        tree.setHeaderVisible(true);
    }

    private void initializeViewerSorter() {
        byte orderType = fMemento.getInteger(P_ORDER_TYPE).byteValue();
        ViewerComparator comparator = getViewerComparator(orderType);
        fFilteredTree.getViewer().setComparator(comparator);
        if (orderType == MESSAGE)
            setColumnSorting(fColumn1, MESSAGE_ORDER);
        else if (orderType == PLUGIN)
            setColumnSorting(fColumn2, PLUGIN_ORDER);
        else if (orderType == DATE)
            setColumnSorting(fColumn3, DATE_ORDER);
    }

    private void setColumnSorting(TreeColumn column, int order) {
        fTree.setSortColumn(column);
        fTree.setSortDirection(order == ASCENDING ? SWT.UP : SWT.DOWN);
    }

    public void dispose() {
        writeSettings();
        Platform.removeLogListener(this);
        fClipboard.dispose();
        if (fTextShell != null)
            fTextShell.dispose();
        fLabelProvider.disconnect(this);
        fFilteredTree.dispose();
        super.dispose();
    }

    /**
     * Import log from file selected in FileDialog.
     */
    void handleImport() {
        FileDialog dialog = new FileDialog(getViewSite().getShell());
        dialog.setFilterExtensions(new String[] { "*.log" }); //$NON-NLS-1$
        if (fDirectory != null)
            dialog.setFilterPath(fDirectory);
        String path = dialog.open();
        if (path == null) { // cancel
            return;
        }

        File file = new Path(path).toFile();
        if (file.exists()) {
            handleImportPath(path);
        } else {
            String msg = NLS.bind(Messages.LogView_FileCouldNotBeFound, file.getName());
            MessageDialog.openError(getViewSite().getShell(), Messages.LogView_OpenFile, msg);
        }
    }

    /**
     * Import log from given file path. Do nothing if file not exists.
     * @param path path to log file.
     */
    public void handleImportPath(String path) {
        File file = new File(path);
        if (path != null && file.exists()) {
            setLogFile(file);
        }
    }

    /**
     * Import log from given file path.
     * @param path path to log file.
     */
    protected void setLogFile(File path) {
        fInputFile = path;
        fDirectory = fInputFile.getParent();
        IRunnableWithProgress op = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) {
                monitor.beginTask(Messages.LogView_operation_importing, IProgressMonitor.UNKNOWN);
                readLogFile();
            }
        };
        ProgressMonitorDialog pmd = new ProgressMonitorDialog(getViewSite().getShell());
        try {
            pmd.run(true, true, op);
        } catch (InvocationTargetException e) { // do nothing
        } catch (InterruptedException e) { // do nothing
        } finally {
            fReadLogAction.setText(Messages.LogView_readLog_reload);
            fReadLogAction.setToolTipText(Messages.LogView_readLog_reload);
            asyncRefresh(false);
            resetDialogButtons();
        }
    }

    private void handleExport(boolean exportWholeLog) {
        FileDialog dialog = new FileDialog(getViewSite().getShell(), SWT.SAVE);
        dialog.setFilterExtensions(new String[] { "*.log" }); //$NON-NLS-1$
        if (fDirectory != null)
            dialog.setFilterPath(fDirectory);
        String path = dialog.open();
        if (path != null) {
            if (path.indexOf('.') == -1 && !path.endsWith(".log")) //$NON-NLS-1$
                path += ".log"; //$NON-NLS-1$
            File outputFile = new Path(path).toFile();
            fDirectory = outputFile.getParent();
            if (outputFile.exists()) {
                String message = NLS.bind(Messages.LogView_confirmOverwrite_message, outputFile.toString());
                if (!MessageDialog.openQuestion(getViewSite().getShell(),
                        (exportWholeLog ? Messages.LogView_exportLog : Messages.LogView_exportLogEntry), message))
                    return;
            }

            BufferedReader in = null;
            BufferedWriter out = null;
            try {
                out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8")); //$NON-NLS-1$
                if (exportWholeLog)
                    in = new BufferedReader(new InputStreamReader(new FileInputStream(fInputFile), "UTF-8")); //$NON-NLS-1$
                else {
                    String selectedEntryAsString = selectionToString(fFilteredTree.getViewer().getSelection());
                    in = new BufferedReader(new StringReader(selectedEntryAsString));
                }
                copy(in, out);
            } catch (IOException ex) {
                // do nothing
            } finally {
                try {
                    if (in != null)
                        in.close();
                } catch (IOException e) {
                    // do nothing
                }
                try {
                    if (out != null)
                        out.close();
                } catch (IOException e) {
                    // do nothing
                }
            }
        }
    }

    private void copy(BufferedReader reader, BufferedWriter writer) throws IOException {
        String line;
        while (reader.ready() && ((line = reader.readLine()) != null)) {
            writer.write(line);
            writer.newLine();
        }
    }

    private void handleFilter() {
        FilterDialog dialog = new FilterDialog(getSite().getShell(), fMemento);
        dialog.create();
        dialog.getShell().setText(Messages.LogView_FilterDialog_title);
        if (dialog.open() == Window.OK)
            reloadLog();
    }

    private void doDeleteLog() {
        String title = Messages.LogView_confirmDelete_title;
        String message = Messages.LogView_confirmDelete_message;
        if (!MessageDialog.openConfirm(fTree.getShell(), title, message))
            return;
        if (fInputFile.delete() || elements.size() > 0) {
            handleClear();
        }
    }

    public void fillContextMenu(IMenuManager manager) { // nothing
    }

    public AbstractEntry[] getElements() {
        return (AbstractEntry[]) elements.toArray(new AbstractEntry[elements.size()]);
    }

    protected void handleClear() {
        BusyIndicator.showWhile(fTree.getDisplay(), new Runnable() {
            public void run() {
                elements.clear();
                groups.clear();
                if (currentSession != null) {
                    currentSession.removeAllChildren();
                }
                asyncRefresh(false);
                resetDialogButtons();
            }
        });
    }

    /**
     * Reloads the log
     */
    protected void reloadLog() {
        IRunnableWithProgress op = new IRunnableWithProgress() {
            public void run(IProgressMonitor monitor) {
                monitor.beginTask(Messages.LogView_operation_reloading, IProgressMonitor.UNKNOWN);
                readLogFile();
            }
        };
        ProgressMonitorDialog pmd = new ProgressMonitorDialog(getViewSite().getShell());
        try {
            pmd.run(true, true, op);
        } catch (InvocationTargetException e) { // do nothing
        } catch (InterruptedException e) { // do nothing
        } finally {
            fReadLogAction.setText(Messages.LogView_readLog_restore);
            fReadLogAction.setToolTipText(Messages.LogView_readLog_restore);
            asyncRefresh(false);
            resetDialogButtons();
        }
    }

    /**
     * Reads the chosen backing log file
     */
    void readLogFile() {
        elements.clear();
        groups.clear();

        List result = new ArrayList();
        LogSession lastLogSession = LogReader.parseLogFile(this.fInputFile, getLogMaxTailSize(), result,
                this.fMemento);
        if (lastLogSession != null
                && (lastLogSession.getDate() == null || isEclipseStartTime(lastLogSession.getDate()))) {
            currentSession = lastLogSession;
        } else {
            currentSession = null;
        }

        group(result);
        limitEntriesCount();

        getSite().getShell().getDisplay().asyncExec(new Runnable() {
            public void run() {
                setContentDescription(getTitleSummary());
            }
        });

    }

    private boolean isEclipseStartTime(Date date) {
        String ts = System.getProperty("eclipse.startTime"); //$NON-NLS-1$
        try {
            return (ts != null && date.getTime() == Long.parseLong(ts));
        } catch (NumberFormatException e) {
            // empty
        }
        return false;
    }

    private String getTitleSummary() {
        String path = ""; //$NON-NLS-1$
        try {
            path = fInputFile.getCanonicalPath();
        } catch (IOException e) { // log nothing
        }

        if (isPlatformLogOpen()) {
            return Messages.LogView_WorkspaceLogFile;
        }

        Map sources = LogFilesManager.getLogSources();
        if (sources.containsValue(path)) {
            for (Iterator i = sources.keySet().iterator(); i.hasNext();) {
                String key = (String) i.next();
                if (sources.get(key).equals(path)) {
                    return NLS.bind(Messages.LogView_LogFileTitle, new String[] { key, path });
                }
            }
        }

        return path;
    }

    /**
     * Add new entries to correct groups in the view.
     * @param entries new entries to show up in groups in the view.
     */
    private void group(List entries) {
        if (fMemento.getInteger(P_GROUP_BY).intValue() == GROUP_BY_NONE) {
            elements.addAll(entries);
        } else {
            for (Iterator i = entries.iterator(); i.hasNext();) {
                LogEntry entry = (LogEntry) i.next();
                Group group = getGroup(entry);
                group.addChild(entry);
            }
        }
    }

    /**
     * Limits the number of entries according to the max entries limit set in
     * memento.
     */
    private void limitEntriesCount() {
        int limit = Integer.MAX_VALUE;
        if (fMemento.getString(LogView.P_USE_LIMIT).equals("true")) {//$NON-NLS-1$
            limit = fMemento.getInteger(LogView.P_LOG_LIMIT).intValue();
        }

        int entriesCount = getEntriesCount();

        if (entriesCount <= limit) {
            return;
        }
        Comparator dateComparator = new Comparator() {
            public int compare(Object o1, Object o2) {
                Date l1 = ((LogEntry) o1).getDate();
                Date l2 = ((LogEntry) o2).getDate();
                if ((l1 != null) && (l2 != null)) {
                    return l1.before(l2) ? -1 : 1;
                } else if ((l1 == null) && (l2 == null)) {
                    return 0;
                } else
                    return (l1 == null) ? -1 : 1;
            }
        };

        if (fMemento.getInteger(P_GROUP_BY).intValue() == GROUP_BY_NONE) {
            elements.subList(0, elements.size() - limit).clear();
        } else {
            List copy = new ArrayList(entriesCount);
            for (Iterator i = elements.iterator(); i.hasNext();) {
                AbstractEntry group = (AbstractEntry) i.next();
                copy.addAll(Arrays.asList(group.getChildren(group)));
            }

            Collections.sort(copy, dateComparator);
            List toRemove = copy.subList(0, copy.size() - limit);

            for (Iterator i = elements.iterator(); i.hasNext();) {
                AbstractEntry group = (AbstractEntry) i.next();
                group.removeChildren(toRemove);
            }
        }

    }

    private int getEntriesCount() {
        if (fMemento.getInteger(P_GROUP_BY).intValue() == GROUP_BY_NONE) {
            return elements.size();
        }
        int size = 0;
        for (Iterator i = elements.iterator(); i.hasNext();) {
            AbstractEntry group = (AbstractEntry) i.next();
            size += group.size();
        }
        return size;
    }

    /**
     * Returns group appropriate for the entry. Group depends on P_GROUP_BY
     * preference, or is null if grouping is disabled (GROUP_BY_NONE), or group
     * could not be determined. May create group if it haven't existed before.
     *
     * @param entry entry to be grouped
     * @return group or null if grouping is disabled
     */
    protected Group getGroup(LogEntry entry) {
        int groupBy = fMemento.getInteger(P_GROUP_BY).intValue();
        Object elementGroupId = null;
        String groupName = null;

        switch (groupBy) {
        case GROUP_BY_PLUGIN:
            groupName = entry.getPluginId();
            elementGroupId = groupName;
            break;

        case GROUP_BY_SESSION:
            elementGroupId = entry.getSession();
            break;

        default: // grouping is disabled
            return null;
        }

        if (elementGroupId == null) { // could not determine group
            return null;
        }

        Group group = (Group) groups.get(elementGroupId);
        if (group == null) {
            if (groupBy == GROUP_BY_SESSION) {
                group = entry.getSession();
            } else {
                group = new Group(groupName);
            }
            groups.put(elementGroupId, group);
            elements.add(group);
        }

        return group;
    }

    public void logging(IStatus status, String plugin) {
        if (!isPlatformLogOpen())
            return;

        if (batchEntries) {
            // create LogEntry immediately to don't loose IStatus creation date.
            LogEntry entry = createLogEntry(status);
            batchedEntries.add(entry);
            return;
        }

        if (fFirstEvent || (currentSession == null)) {
            readLogFile();
            asyncRefresh(true);
            fFirstEvent = false;
        } else {
            LogEntry entry = createLogEntry(status);

            if (!batchedEntries.isEmpty()) {
                // batch new entry as well, to have only one asyncRefresh()
                batchedEntries.add(entry);
                pushBatchedEntries();
            } else {
                pushEntry(entry);
                asyncRefresh(true);
            }
        }
    }

    /**
     * Push batched entries to log view.
     */
    private void pushBatchedEntries() {
        Job job = new Job(Messages.LogView_AddingBatchedEvents) {
            protected IStatus run(IProgressMonitor monitor) {
                for (int i = 0; i < batchedEntries.size(); i++) {
                    if (!monitor.isCanceled()) {
                        LogEntry entry = (LogEntry) batchedEntries.get(i);
                        pushEntry(entry);
                        batchedEntries.remove(i);
                    }
                }
                asyncRefresh(true);
                return Status.OK_STATUS;
            }
        };
        job.schedule();
    }

    private LogEntry createLogEntry(IStatus status) {
        LogEntry entry = new LogEntry(status, currentSession);

        if (status.getException() instanceof CoreException) {
            IStatus coreStatus = ((CoreException) status.getException()).getStatus();
            if (coreStatus != null) {
                LogEntry childEntry = createLogEntry(coreStatus);
                entry.addChild(childEntry);
            }
        }

        return entry;
    }

    private synchronized void pushEntry(LogEntry entry) {
        if (LogReader.isLogged(entry, fMemento)) {
            group(Collections.singletonList(entry));
            limitEntriesCount();
        }
        asyncRefresh(true);
    }

    private void asyncRefresh(final boolean activate) {
        if (fTree.isDisposed())
            return;
        Display display = fTree.getDisplay();
        final ViewPart view = this;
        if (display != null) {
            display.asyncExec(new Runnable() {
                public void run() {
                    if (!fTree.isDisposed()) {
                        TreeViewer viewer = fFilteredTree.getViewer();
                        viewer.refresh();
                        viewer.expandToLevel(2);
                        fDeleteLogAction.setEnabled(
                                fInputFile.exists() && fInputFile.equals(Platform.getLogFileLocation().toFile()));
                        fOpenLogAction.setEnabled(fInputFile.exists());
                        fExportLogAction.setEnabled(fInputFile.exists());
                        fExportLogEntryAction.setEnabled(!viewer.getSelection().isEmpty());
                        if (activate && fActivateViewAction.isChecked()) {
                            IWorkbenchWindow window = Activator.getDefault().getWorkbench()
                                    .getActiveWorkbenchWindow();
                            if (window != null) {
                                IWorkbenchPage page = window.getActivePage();
                                if (page != null) {
                                    page.bringToTop(view);
                                }
                            }
                        }
                    }
                }
            });
        }
    }

    public void setFocus() {
        if (fFilteredTree != null) {
            if (fMemento.getBoolean(P_SHOW_FILTER_TEXT).booleanValue()) {
                Text filterControl = fFilteredTree.getFilterControl();
                if (filterControl != null && !filterControl.isDisposed()) {
                    filterControl.setFocus();
                }
            } else if (!fFilteredTree.isDisposed()) {
                fFilteredTree.setFocus();
            }
        }
    }

    private void handleSelectionChanged(ISelection selection) {
        updateStatus(selection);
        fCopyAction
                .setEnabled((!selection.isEmpty()) && ((IStructuredSelection) selection).getFirstElement() != null);
        fPropertiesAction.setEnabled(!selection.isEmpty());
        fExportLogEntryAction.setEnabled(!selection.isEmpty());
    }

    private void updateStatus(ISelection selection) {
        IStatusLineManager status = getViewSite().getActionBars().getStatusLineManager();
        if (selection.isEmpty())
            status.setMessage(null);
        else {
            Object element = ((IStructuredSelection) selection).getFirstElement();
            status.setMessage(((LogViewLabelProvider) fFilteredTree.getViewer().getLabelProvider())
                    .getColumnText(element, 0));
        }
    }

    /**
     * Converts selected log view element to string.
     * @return textual log entry representation or null if selection doesn't contain log entry
     */
    private static String selectionToString(ISelection selection) {
        StringWriter writer = new StringWriter();
        PrintWriter pwriter = new PrintWriter(writer);
        if (selection.isEmpty())
            return null;
        AbstractEntry entry = (AbstractEntry) ((IStructuredSelection) selection).getFirstElement();
        entry.write(pwriter);
        pwriter.flush();
        String textVersion = writer.toString();
        pwriter.close();
        try {
            writer.close();
        } catch (IOException e) {
            // empty
        }

        return textVersion;
    }

    /**
     * Copies selected element to clipboard.
     */
    private void copyToClipboard(ISelection selection) {
        String textVersion = selectionToString(selection);
        if ((textVersion != null) && (textVersion.trim().length() > 0)) {
            // set the clipboard contents
            fClipboard.setContents(new Object[] { textVersion }, new Transfer[] { TextTransfer.getInstance() });
        }
    }

    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        if (memento == null)
            this.fMemento = XMLMemento.createWriteRoot("LOGVIEW"); //$NON-NLS-1$
        else
            this.fMemento = memento;
        readSettings();

        // initialize column ordering
        final byte type = this.fMemento.getInteger(P_ORDER_TYPE).byteValue();
        switch (type) {
        case DATE:
            DATE_ORDER = this.fMemento.getInteger(P_ORDER_VALUE).intValue();
            MESSAGE_ORDER = DESCENDING;
            PLUGIN_ORDER = DESCENDING;
            break;
        case MESSAGE:
            MESSAGE_ORDER = this.fMemento.getInteger(P_ORDER_VALUE).intValue();
            DATE_ORDER = DESCENDING;
            PLUGIN_ORDER = DESCENDING;
            break;
        case PLUGIN:
            PLUGIN_ORDER = this.fMemento.getInteger(P_ORDER_VALUE).intValue();
            MESSAGE_ORDER = DESCENDING;
            DATE_ORDER = DESCENDING;
            break;
        default:
            DATE_ORDER = DESCENDING;
            MESSAGE_ORDER = DESCENDING;
            PLUGIN_ORDER = DESCENDING;
        }
        setComparator(fMemento.getInteger(P_ORDER_TYPE).byteValue());
    }

    private void initializeMemento() {
        if (fMemento.getString(P_USE_LIMIT) == null) {
            fMemento.putString(P_USE_LIMIT, "true"); //$NON-NLS-1$
        }
        if (fMemento.getInteger(P_LOG_LIMIT) == null) {
            fMemento.putInteger(P_LOG_LIMIT, 50);
        }
        if (fMemento.getString(P_LOG_INFO) == null) {
            fMemento.putString(P_LOG_INFO, "true"); //$NON-NLS-1$
        }
        if (fMemento.getString(P_LOG_OK) == null) {
            fMemento.putString(P_LOG_OK, "true"); //$NON-NLS-1$
        }
        if (fMemento.getString(P_LOG_WARNING) == null) {
            fMemento.putString(P_LOG_WARNING, "true"); //$NON-NLS-1$
        }
        if (fMemento.getString(P_LOG_ERROR) == null) {
            fMemento.putString(P_LOG_ERROR, "true"); //$NON-NLS-1$
        }
        if (fMemento.getString(P_SHOW_ALL_SESSIONS) == null) {
            fMemento.putString(P_SHOW_ALL_SESSIONS, "true"); //$NON-NLS-1$
        }
    }

    public void saveState(IMemento memento) {
        if (this.fMemento == null || memento == null)
            return;
        //store some sane values to prevent the view from being broken
        this.fMemento.putInteger(P_COLUMN_1, getColumnWidth(fColumn1, 300));
        this.fMemento.putInteger(P_COLUMN_2, getColumnWidth(fColumn2, 150));
        this.fMemento.putInteger(P_COLUMN_3, getColumnWidth(fColumn3, 150));
        this.fMemento.putString(P_ACTIVATE, fActivateViewAction.isChecked() ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
        memento.putMemento(this.fMemento);
        writeSettings();
    }

    /**
     * Returns the width of the column or the default value if the column has been resized to be not visible
     * @param column the column to get the width from
     * @param defaultwidth the width to return if the column has been resized to not be visible
     * @return the width of the column or the default value
     *
     * @since 3.6
     */
    int getColumnWidth(TreeColumn column, int defaultwidth) {
        int width = column.getWidth();
        return width < 1 ? defaultwidth : width;
    }

    private void addMouseListeners() {
        Listener tableListener = new Listener() {
            public void handleEvent(Event e) {
                switch (e.type) {
                case SWT.MouseExit:
                case SWT.MouseMove:
                    onMouseMove(e);
                    break;
                case SWT.MouseHover:
                    onMouseHover(e);
                    break;
                case SWT.MouseDown:
                    onMouseDown(e);
                    break;
                }
            }
        };
        int[] tableEvents = new int[] { SWT.MouseDown, SWT.MouseMove, SWT.MouseHover, SWT.MouseExit };
        for (int i = 0; i < tableEvents.length; i++) {
            fTree.addListener(tableEvents[i], tableListener);
        }
    }

    /**
     * Adds drag source support to error log tree.
     */
    private void addDragSource() {
        DragSource source = new DragSource(fTree, DND.DROP_COPY);
        Transfer[] types = new Transfer[] { TextTransfer.getInstance() };
        source.setTransfer(types);

        source.addDragListener(new DragSourceAdapter() {

            public void dragStart(DragSourceEvent event) {
                ISelection selection = fFilteredTree.getViewer().getSelection();
                if (selection.isEmpty()) {
                    event.doit = false;
                    return;
                }

                AbstractEntry entry = (AbstractEntry) ((TreeSelection) selection).getFirstElement();
                if (!(entry instanceof LogEntry)) {
                    event.doit = false;
                    return;
                }
            }

            public void dragSetData(DragSourceEvent event) {
                if (!TextTransfer.getInstance().isSupportedType(event.dataType)) {
                    return;
                }

                ISelection selection = fFilteredTree.getViewer().getSelection();
                String textVersion = selectionToString(selection);
                event.data = textVersion;
            }
        });
    }

    private void makeHoverShell() {
        // parent it off the workbench window's shell so it will be valid regardless of whether the view is a detached window or not
        fTextShell = new Shell(getSite().getWorkbenchWindow().getShell(), SWT.NO_FOCUS | SWT.ON_TOP | SWT.TOOL);
        Display display = fTextShell.getDisplay();
        fTextShell.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));
        GridLayout layout = new GridLayout(1, false);
        int border = ((fTree.getShell().getStyle() & SWT.NO_TRIM) == 0) ? 0 : 1;
        layout.marginHeight = border;
        layout.marginWidth = border;
        fTextShell.setLayout(layout);
        fTextShell.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
        Composite shellComposite = new Composite(fTextShell, SWT.NONE);
        layout = new GridLayout();
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        shellComposite.setLayout(layout);
        shellComposite.setLayoutData(new GridData(GridData.FILL_BOTH | GridData.VERTICAL_ALIGN_BEGINNING));
        fTextLabel = new Text(shellComposite, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY);
        GridData gd = new GridData(GridData.FILL_BOTH);
        gd.widthHint = 100;
        gd.grabExcessHorizontalSpace = true;
        fTextLabel.setLayoutData(gd);
        Color c = fTree.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND);
        fTextLabel.setBackground(c);
        c = fTree.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND);
        fTextLabel.setForeground(c);
        fTextLabel.setEditable(false);
        fTextShell.addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                onTextShellDispose(e);
            }
        });
    }

    void onTextShellDispose(DisposeEvent e) {
        fCanOpenTextShell = true;
        setFocus();
    }

    void onMouseDown(Event e) {
        if (fTextShell != null && !fTextShell.isDisposed() && !fTextShell.isFocusControl()) {
            fTextShell.setVisible(false);
            fCanOpenTextShell = true;
        }
    }

    void onMouseHover(Event e) {
        if (!fCanOpenTextShell || fTextShell == null || fTextShell.isDisposed())
            return;
        fCanOpenTextShell = false;
        Point point = new Point(e.x, e.y);
        TreeItem item = fTree.getItem(point);
        if (item == null)
            return;

        String message = null;
        if (item.getData() instanceof LogEntry) {
            message = ((LogEntry) item.getData()).getStack();
        } else if (item.getData() instanceof LogSession) {
            LogSession session = ((LogSession) item.getData());
            message = Messages.LogView_SessionStarted;
            if (session.getDate() != null) {
                DateFormat formatter = new SimpleDateFormat(LogEntry.F_DATE_FORMAT);
                message += formatter.format(session.getDate());
            }
        }

        if (message == null)
            return;

        fTextLabel.setText(message);
        Rectangle bounds = fTree.getDisplay().getBounds();
        Point cursorPoint = fTree.getDisplay().getCursorLocation();
        int x = point.x;
        int y = point.y + 25;
        int width = fTree.getColumn(0).getWidth();
        int height = 125;
        if (cursorPoint.x + width > bounds.width)
            x -= width;
        if (cursorPoint.y + height + 25 > bounds.height)
            y -= height + 27;

        fTextShell.setLocation(fTree.toDisplay(x, y));
        fTextShell.setSize(width, height);
        fTextShell.setVisible(true);
    }

    void onMouseMove(Event e) {
        if (fTextShell != null && !fTextShell.isDisposed() && fTextShell.isVisible())
            fTextShell.setVisible(false);

        Point point = new Point(e.x, e.y);
        TreeItem item = fTree.getItem(point);
        if (item == null)
            return;
        Image image = item.getImage();
        Object data = item.getData();
        if (data instanceof LogEntry) {
            LogEntry entry = (LogEntry) data;
            int parentCount = getNumberOfParents(entry);
            int startRange = 20 + Math.max(image.getBounds().width + 2, 7 + 2) * parentCount;
            int endRange = startRange + 16;
            fCanOpenTextShell = e.x >= startRange && e.x <= endRange;
        }
    }

    private int getNumberOfParents(AbstractEntry entry) {
        AbstractEntry parent = (AbstractEntry) entry.getParent(entry);
        if (parent == null)
            return 0;
        return 1 + getNumberOfParents(parent);
    }

    public Comparator getComparator() {
        return fComparator;
    }

    private void setComparator(byte sortType) {
        if (sortType == DATE) {
            fComparator = new Comparator() {
                public int compare(Object e1, Object e2) {
                    long date1 = 0;
                    long date2 = 0;
                    if ((e1 instanceof LogEntry) && (e2 instanceof LogEntry)) {
                        date1 = ((LogEntry) e1).getDate().getTime();
                        date2 = ((LogEntry) e2).getDate().getTime();
                    } else if ((e1 instanceof LogSession) && (e2 instanceof LogSession)) {
                        date1 = ((LogSession) e1).getDate() == null ? 0 : ((LogSession) e1).getDate().getTime();
                        date2 = ((LogSession) e2).getDate() == null ? 0 : ((LogSession) e2).getDate().getTime();
                    }
                    if (date1 == date2) {
                        int result = elements.indexOf(e2) - elements.indexOf(e1);
                        if (DATE_ORDER == DESCENDING)
                            result *= DESCENDING;
                        return result;
                    }
                    if (DATE_ORDER == DESCENDING)
                        return date1 > date2 ? DESCENDING : ASCENDING;
                    return date1 < date2 ? DESCENDING : ASCENDING;
                }
            };
        } else if (sortType == PLUGIN) {
            fComparator = new Comparator() {
                public int compare(Object e1, Object e2) {
                    if ((e1 instanceof LogEntry) && (e2 instanceof LogEntry)) {
                        LogEntry entry1 = (LogEntry) e1;
                        LogEntry entry2 = (LogEntry) e2;
                        return getDefaultComparator().compare(entry1.getPluginId(), entry2.getPluginId())
                                * PLUGIN_ORDER;
                    }
                    return 0;
                }
            };
        } else {
            fComparator = new Comparator() {
                public int compare(Object e1, Object e2) {
                    if ((e1 instanceof LogEntry) && (e2 instanceof LogEntry)) {
                        LogEntry entry1 = (LogEntry) e1;
                        LogEntry entry2 = (LogEntry) e2;
                        return getDefaultComparator().compare(entry1.getMessage(), entry2.getMessage())
                                * MESSAGE_ORDER;
                    }
                    return 0;
                }
            };
        }
    }

    private Comparator getDefaultComparator() {
        return Policy.getComparator();
    }

    private ViewerComparator getViewerComparator(byte sortType) {
        if (sortType == PLUGIN) {
            return new ViewerComparator() {
                public int compare(Viewer viewer, Object e1, Object e2) {
                    if ((e1 instanceof LogEntry) && (e2 instanceof LogEntry)) {
                        LogEntry entry1 = (LogEntry) e1;
                        LogEntry entry2 = (LogEntry) e2;
                        return getComparator().compare(entry1.getPluginId(), entry2.getPluginId()) * PLUGIN_ORDER;
                    }
                    return 0;
                }
            };
        } else if (sortType == MESSAGE) {
            return new ViewerComparator() {
                public int compare(Viewer viewer, Object e1, Object e2) {
                    if ((e1 instanceof LogEntry) && (e2 instanceof LogEntry)) {
                        LogEntry entry1 = (LogEntry) e1;
                        LogEntry entry2 = (LogEntry) e2;
                        return getComparator().compare(entry1.getMessage(), entry2.getMessage()) * MESSAGE_ORDER;
                    }
                    return 0;
                }
            };
        } else {
            return new ViewerComparator() {
                private int indexOf(Object[] array, Object o) {
                    if (o == null)
                        return -1;
                    for (int i = 0; i < array.length; ++i)
                        if (o.equals(array[i]))
                            return i;
                    return -1;
                }

                public int compare(Viewer viewer, Object e1, Object e2) {
                    long date1 = 0;
                    long date2 = 0;
                    if ((e1 instanceof LogEntry) && (e2 instanceof LogEntry)) {
                        date1 = ((LogEntry) e1).getDate().getTime();
                        date2 = ((LogEntry) e2).getDate().getTime();
                    } else if ((e1 instanceof LogSession) && (e2 instanceof LogSession)) {
                        date1 = ((LogSession) e1).getDate() == null ? 0 : ((LogSession) e1).getDate().getTime();
                        date2 = ((LogSession) e2).getDate() == null ? 0 : ((LogSession) e2).getDate().getTime();
                    }

                    if (date1 == date2) {
                        // Everything that appears in LogView should be an AbstractEntry.
                        AbstractEntry parent = (AbstractEntry) ((AbstractEntry) e1).getParent(null);
                        Object[] children = null;
                        if (parent != null)
                            children = parent.getChildren(parent);

                        int result = 0;
                        if (children != null) {
                            // The elements in children seem to be in reverse order,
                            // i.e. latest log message first, therefore index(e2)-index(e1)
                            result = indexOf(children, e2) - indexOf(children, e1);
                        } else {
                            result = elements.indexOf(e1) - elements.indexOf(e2);
                        }
                        if (DATE_ORDER == DESCENDING)
                            result *= DESCENDING;
                        return result;
                    }
                    if (DATE_ORDER == DESCENDING)
                        return date1 > date2 ? DESCENDING : ASCENDING;
                    return date1 < date2 ? DESCENDING : ASCENDING;
                }
            };
        }
    }

    private void resetDialogButtons() {
        ((EventDetailsDialogAction) fPropertiesAction).resetDialogButtons();
    }

    /**
     * Returns the filter dialog settings object used to maintain
     * state between filter dialogs
     * @return the dialog settings to be used
     */
    private IDialogSettings getLogSettings() {
        IDialogSettings settings = Activator.getDefault().getDialogSettings();
        return settings.getSection(getClass().getName());
    }

    /**
     * Loads any saved {@link IDialogSettings} into the backing view memento
     */
    private void readSettings() {
        IDialogSettings s = getLogSettings();
        if (s == null) {
            initializeMemento();
        } else {
            fMemento.putString(P_USE_LIMIT, s.getBoolean(P_USE_LIMIT) ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
            fMemento.putString(P_LOG_INFO, s.getBoolean(P_LOG_INFO) ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
            fMemento.putString(P_LOG_OK, s.getBoolean(P_LOG_OK) ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
            fMemento.putString(P_LOG_WARNING, s.getBoolean(P_LOG_WARNING) ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
            fMemento.putString(P_LOG_ERROR, s.getBoolean(P_LOG_ERROR) ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
            fMemento.putString(P_SHOW_ALL_SESSIONS, s.getBoolean(P_SHOW_ALL_SESSIONS) ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
            try {
                fMemento.putInteger(P_LOG_LIMIT, s.getInt(P_LOG_LIMIT));
            } catch (NumberFormatException e) {
                fMemento.putInteger(P_LOG_LIMIT, 50);
            }
        }

        Preferences instancePrefs = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
        Preferences defaultPrefs = DefaultScope.INSTANCE.getNode(Activator.PLUGIN_ID);
        fMemento.putInteger(P_COLUMN_1, getColumnWidthPreference(instancePrefs, defaultPrefs, P_COLUMN_1, 300));
        fMemento.putInteger(P_COLUMN_2, getColumnWidthPreference(instancePrefs, defaultPrefs, P_COLUMN_2, 150));
        fMemento.putInteger(P_COLUMN_3, getColumnWidthPreference(instancePrefs, defaultPrefs, P_COLUMN_3, 150));
        fMemento.putBoolean(P_ACTIVATE,
                instancePrefs.getBoolean(P_ACTIVATE, defaultPrefs.getBoolean(P_ACTIVATE, true)));
        fMemento.putInteger(P_ORDER_VALUE,
                instancePrefs.getInt(P_ORDER_VALUE, defaultPrefs.getInt(P_ORDER_VALUE, DESCENDING)));
        fMemento.putInteger(P_ORDER_TYPE,
                instancePrefs.getInt(P_ORDER_TYPE, defaultPrefs.getInt(P_ORDER_TYPE, LogView.DATE)));
        fMemento.putBoolean(P_SHOW_FILTER_TEXT,
                instancePrefs.getBoolean(P_SHOW_FILTER_TEXT, defaultPrefs.getBoolean(P_SHOW_FILTER_TEXT, true)));
        fMemento.putInteger(P_GROUP_BY,
                instancePrefs.getInt(P_GROUP_BY, defaultPrefs.getInt(P_GROUP_BY, LogView.GROUP_BY_NONE)));
        fMemento.putString(P_LOG_MAX_TAIL_SIZE, String
                .valueOf(getLogMaxTailSizePreference(instancePrefs, defaultPrefs, DEFAULT_LOG_MAX_TAIL_SIZE)));
    }

    private long getLogMaxTailSizePreference(Preferences instancePrefs, Preferences defaultPrefs,
            long defaultMaxLogTailSize) {
        try {
            return instancePrefs.getLong(P_LOG_MAX_TAIL_SIZE,
                    defaultPrefs.getLong(P_LOG_MAX_TAIL_SIZE, defaultMaxLogTailSize));
        } catch (IllegalStateException ex) {
            return defaultMaxLogTailSize;
        }
    }

    private long getLogMaxTailSize() {
        return Long.valueOf(this.fMemento.getString(P_LOG_MAX_TAIL_SIZE)).longValue();
    }

    /**
     * Returns the width to use for the column represented by the given key. The default width
     * is returned iff:
     * <ul>
     * <li>There is no preference for the given key</li>
     * <li>The returned preference value is too small, making the columns invisible by width.</li>
     * </ul>
     * @param instancePrefs
     * @param defaultPrefs
     * @param key
     * @param defaultwidth
     * @return the stored width for the a column described by the given key or the default width
     *
     * @since 3.6
     */
    int getColumnWidthPreference(Preferences instancePrefs, Preferences defaultPrefs, String key,
            int defaultwidth) {
        int width = instancePrefs.getInt(key, defaultPrefs.getInt(key, defaultwidth));
        return width < 1 ? defaultwidth : width;
    }

    private void writeSettings() {
        writeViewSettings();
        writeFilterSettings();
    }

    private void writeFilterSettings() {
        IDialogSettings settings = getLogSettings();
        if (settings == null)
            settings = Activator.getDefault().getDialogSettings().addNewSection(getClass().getName());
        settings.put(P_USE_LIMIT, fMemento.getString(P_USE_LIMIT).equals("true")); //$NON-NLS-1$
        settings.put(P_LOG_LIMIT, fMemento.getInteger(P_LOG_LIMIT).intValue());
        settings.put(P_LOG_INFO, fMemento.getString(P_LOG_INFO).equals("true")); //$NON-NLS-1$
        settings.put(P_LOG_OK, fMemento.getString(P_LOG_OK).equals("true")); //$NON-NLS-1$
        settings.put(P_LOG_WARNING, fMemento.getString(P_LOG_WARNING).equals("true")); //$NON-NLS-1$
        settings.put(P_LOG_ERROR, fMemento.getString(P_LOG_ERROR).equals("true")); //$NON-NLS-1$
        settings.put(P_SHOW_ALL_SESSIONS, fMemento.getString(P_SHOW_ALL_SESSIONS).equals("true")); //$NON-NLS-1$
    }

    private void writeViewSettings() {
        Preferences instancePrefs = (InstanceScope.INSTANCE).getNode(Activator.PLUGIN_ID);
        instancePrefs.putInt(P_COLUMN_1, fMemento.getInteger(P_COLUMN_1).intValue());
        instancePrefs.putInt(P_COLUMN_2, fMemento.getInteger(P_COLUMN_2).intValue());
        instancePrefs.putInt(P_COLUMN_3, fMemento.getInteger(P_COLUMN_3).intValue());
        instancePrefs.putBoolean(P_ACTIVATE, fMemento.getBoolean(P_ACTIVATE).booleanValue());
        instancePrefs.putInt(P_ORDER_VALUE, fMemento.getInteger(P_ORDER_VALUE).intValue());
        instancePrefs.putInt(P_ORDER_TYPE, fMemento.getInteger(P_ORDER_TYPE).intValue());
        instancePrefs.putBoolean(P_SHOW_FILTER_TEXT, fMemento.getBoolean(P_SHOW_FILTER_TEXT).booleanValue());
        instancePrefs.putInt(P_GROUP_BY, fMemento.getInteger(P_GROUP_BY).intValue());
        instancePrefs.putLong(P_LOG_MAX_TAIL_SIZE, getLogMaxTailSize());
        try {
            instancePrefs.flush();
        } catch (BackingStoreException e) {
            // empty
        }
    }

    public void sortByDateDescending() {
        setColumnSorting(fColumn3, DESCENDING);
    }

    protected Job getOpenLogFileJob() {
        final Shell shell = getViewSite().getShell();
        return new Job(Messages.OpenLogDialog_message) {
            protected IStatus run(IProgressMonitor monitor) {
                boolean failed = false;
                if (fInputFile.length() <= LogReader.MAX_FILE_LENGTH) {
                    failed = !Program.launch(fInputFile.getAbsolutePath());
                    if (failed) {
                        Program p = Program.findProgram(".txt"); //$NON-NLS-1$
                        if (p != null) {
                            p.execute(fInputFile.getAbsolutePath());
                            return Status.OK_STATUS;
                        }
                    }
                }
                if (failed) {
                    final OpenLogDialog openDialog = new OpenLogDialog(shell, fInputFile);
                    Display.getDefault().asyncExec(new Runnable() {
                        public void run() {
                            openDialog.create();
                            openDialog.open();
                        }
                    });
                }
                return Status.OK_STATUS;
            }
        };
    }

    protected File getLogFile() {
        return fInputFile;
    }

    /**
     * Returns whether given session equals to currently displayed in LogView.
     * @param session LogSession
     * @return true if given session equals to currently displayed in LogView
     */
    public boolean isCurrentLogSession(LogSession session) {
        return isPlatformLogOpen() && (currentSession != null) && (currentSession.equals(session));
    }

    /**
     * Returns whether currently open log is platform log or imported file.
     * @return true if currently open log is platform log, false otherwise
     */
    public boolean isPlatformLogOpen() {
        return (fInputFile.equals(Platform.getLogFileLocation().toFile()));
    }

    /**
     *
     */
    public void setPlatformLog() {
        setLogFile(Platform.getLogFileLocation().toFile());
    }
}