org.carrot2.workbench.vis.aduna.AdunaClusterMapViewPage.java Source code

Java tutorial

Introduction

Here is the source code for org.carrot2.workbench.vis.aduna.AdunaClusterMapViewPage.java

Source

/*
 * Carrot2 project.
 *
 * Copyright (C) 2002-2015, Dawid Weiss, Stanisaw Osiski.
 * All rights reserved.
 *
 * Refer to the full license file "carrot2.LICENSE"
 * in the root folder of the repository checkout or at:
 * http://www.carrot2.org/carrot2.LICENSE
 */

package org.carrot2.workbench.vis.aduna;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.*;
import java.util.Map;

import javax.swing.*;

import org.apache.commons.lang.StringUtils;
import org.carrot2.core.*;
import org.carrot2.core.Cluster;
import org.carrot2.workbench.core.helpers.DisposeBin;
import org.carrot2.workbench.core.helpers.PostponableJob;
import org.carrot2.workbench.core.ui.*;
import org.carrot2.workbench.core.ui.actions.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.SWT;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.events.*;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.part.Page;
import org.eclipse.ui.progress.UIJob;

import biz.aduna.map.cluster.*;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * A single {@link AdunaClusterMapViewPage} page embeds Aduna's Swing component with
 * visualization of clusters.
 */
final class AdunaClusterMapViewPage extends Page {
    /** */
    private final int REFRESH_DELAY = 500;

    /**
     * Classification root.
     */
    private DefaultClassification root;

    /**
     * A map of the most recently shown {@link Cluster}s.
     */
    private Map<Integer, DefaultClassification> clusterMap = Maps.newHashMap();

    /**
     * A map of the most recently shown {@link Document}s.
     */
    private Map<String, DefaultObject> documentMap = Maps.newHashMap();

    /**
     * UI job for applying selection to the cluster map component.
     */
    private PostponableJob selectionJob = new PostponableJob(new UIJob("Aduna ClusterMap (selection)...") {
        private IStructuredSelection currentlyDisplayed = null;

        @Override
        public IStatus runInUIThread(IProgressMonitor monitor) {
            final VisualizationMode mode = VisualizationMode.valueOf(
                    AdunaActivator.plugin.getPreferenceStore().getString(PreferenceConstants.VISUALIZATION_MODE));

            if (root != null) {
                final IStructuredSelection toBeDisplayed;
                final IStructuredSelection currentSelection = getSelected();
                switch (mode) {
                case SHOW_ALL_CLUSTERS:
                    toBeDisplayed = getAll();
                    break;

                case SHOW_FIRST_LEVEL_CLUSTERS:
                    toBeDisplayed = getFirstLevel();
                    break;

                case SHOW_SELECTED_CLUSTERS:
                    toBeDisplayed = currentSelection;
                    break;

                default:
                    throw new RuntimeException("Unhanded case: " + mode);
                }

                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        if (!toBeDisplayed.equals(currentlyDisplayed)) {
                            mapMediator.visualize(selectionToClassification(toBeDisplayed));
                            currentlyDisplayed = toBeDisplayed;
                        }

                        mapMediator.select(selectionToClassification(currentSelection));
                    }
                });
            }

            return Status.OK_STATUS;
        }

        /*
         * 
         */
        private java.util.List<Classification> selectionToClassification(IStructuredSelection s) {
            final IAdapterManager mgr = Platform.getAdapterManager();

            final java.util.List<Classification> selected = Lists.newArrayList();
            for (Object o : s.toList()) {
                if (o != null && o instanceof Classification) {
                    selected.add((Classification) o);
                } else {
                    final Cluster c = (Cluster) mgr.getAdapter(o, Cluster.class);
                    if (c != null) {
                        final Classification object = clusterMap.get(c.getId());
                        if (object != null)
                            selected.add(object);
                    }
                }
            }

            return selected;
        }

        /**
         * Return the currently selected clusters.
         */
        private IStructuredSelection getSelected() {
            final ISelectionProvider sProvider = editor.getSite().getSelectionProvider();
            final ISelection selection = sProvider.getSelection();
            return (IStructuredSelection) selection;
        }

        /**
         * Return the first level of clusters as the selection.
         */
        private IStructuredSelection getFirstLevel() {
            if (root == null) {
                return StructuredSelection.EMPTY;
            } else {
                return new StructuredSelection(root.getChildren().toArray());
            }
        }

        /**
         * Return All clusters as the selection. 
         */
        @SuppressWarnings("unchecked")
        protected IStructuredSelection getAll() {
            if (root == null) {
                return StructuredSelection.EMPTY;
            } else {
                final java.util.List<Classification> clusters = Lists.newArrayList();
                final java.util.List<Classification> left = Lists.newLinkedList();

                left.add(root);
                while (!left.isEmpty()) {
                    final Classification c = left.remove(0);
                    clusters.add(c);

                    left.addAll(c.getChildren());
                }

                return new StructuredSelection(clusters);
            }
        }
    });

    /**
     * Refresh the entire structure of clusters.
     */
    private PostponableJob refreshJob = new PostponableJob(new UIJob("Aduna ClusterMap (full refresh)...") {
        public IStatus runInUIThread(IProgressMonitor monitor) {
            final ProcessingResult result = editor.getSearchResult().getProcessingResult();

            if (result != null) {
                root = new DefaultClassification("All clusters");
                clusterMap = Maps.newHashMap();
                documentMap = Maps.newHashMap();
                toClassification(root, result.getClusters());
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        mapMediator.setClassificationTree(root);
                    }
                });
                selectionJob.reschedule(0);
            }

            return Status.OK_STATUS;
        }

        private void toClassification(DefaultClassification parent, java.util.List<Cluster> clusters) {
            for (Cluster cluster : clusters) {
                if (clusterMap.containsKey(cluster.getId()))
                    continue;

                final DefaultClassification cc = new DefaultClassification(cluster.getLabel(), parent);
                clusterMap.put(cluster.getId(), cc);

                for (Document d : cluster.getAllDocuments()) {
                    if (!documentMap.containsKey(d.getStringId())) {
                        String dt = (String) (String) d.getField(Document.TITLE);
                        String title = "[" + d.getStringId() + "]";
                        if (!StringUtils.isEmpty(dt)) {
                            title = title + " " + dt;
                        }

                        documentMap.put(d.getStringId(), new DefaultObject(title));
                    }

                    cc.add(documentMap.get(d.getStringId()));
                }

                toClassification(cc, cluster.getSubclusters());
            }
        }
    });

    /*
     * Sync with search result updated event.
     */
    private final SearchResultListenerAdapter editorSyncListener = new SearchResultListenerAdapter() {
        public void processingResultUpdated(ProcessingResult result) {
            refreshJob.reschedule(REFRESH_DELAY);
        }
    };

    /**
     * Editor selection listener.
     */
    private final ISelectionChangedListener selectionListener = new ISelectionChangedListener() {
        /* */
        public void selectionChanged(SelectionChangedEvent event) {
            selectionJob.reschedule(REFRESH_DELAY);
        }
    };

    /*
     * 
     */
    private SearchEditor editor;

    /**
     * SWT's composite inside which Aduna is embedded (AWT/Swing).
     */
    private Composite scrollable;

    /**
     * Resource disposal.
     */
    private DisposeBin disposeBin = new DisposeBin();

    /**
     * Aduna's GUI mediator component.
     */
    private ClusterMapMediator mapMediator;

    /**
     * @see VisualizationMode
     */
    private IPropertyChangeListener viewModeListener = new PropertyChangeListenerAdapter(
            PreferenceConstants.VISUALIZATION_MODE) {
        protected void propertyChangeFiltered(PropertyChangeEvent event) {
            selectionJob.reschedule(REFRESH_DELAY);
        }
    };

    /**
     * A composite with embedded AWT stuff. 
     */
    private Composite embedded;

    /*
     *
     */
    public AdunaClusterMapViewPage(SearchEditor editor) {
        this.editor = editor;
    }

    @Override
    public void init(IPageSite pageSite) {
        super.init(pageSite);

        pageSite.getActionBars().getToolBarManager().add(new ExportImageAction(new IImageStreamProvider() {
            public void save(OutputStream os) throws IOException {
                mapMediator.getClusterMap().exportPngImage(os);
            }
        }));
    }

    /*
     * 
     */
    @Override
    public void createControl(Composite parent) {
        createAdunaControl(parent);
        disposeBin.add(scrollable);

        /*
         * Add listeners.
         */
        disposeBin.registerPropertyChangeListener(AdunaActivator.plugin.getPreferenceStore(), viewModeListener);

        /*
         * Add a listener to the editor to update the view after new clusters are
         * available.
         */
        if (editor.getSearchResult().getProcessingResult() != null) {
            refreshJob.reschedule(REFRESH_DELAY);
        }

        editor.getSearchResult().addListener(editorSyncListener);
        editor.getSite().getSelectionProvider().addSelectionChangedListener(selectionListener);
    }

    /*
     * 
     */
    private void createAdunaControl(Composite parent) {
        /*
         * If <code>true</code>, try some dirty hacks to avoid flicker on Windows.
         */
        final boolean windowsFlickerHack = true;
        if (windowsFlickerHack) {
            System.setProperty("sun.awt.noerasebackground", "true");
        }

        this.scrollable = new Composite(parent, SWT.H_SCROLL | SWT.V_SCROLL);
        scrollable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        final GridLayout layout = new GridLayout();
        layout.marginBottom = 0;
        layout.marginLeft = 0;
        layout.marginRight = 0;
        layout.marginTop = 0;
        layout.horizontalSpacing = 0;
        layout.verticalSpacing = 0;
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        scrollable.setLayout(layout);

        embedded = new Composite(scrollable, SWT.NO_BACKGROUND | SWT.EMBEDDED);
        embedded.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        final Frame frame = SWT_AWT.new_Frame(embedded);
        frame.setLayout(new java.awt.BorderLayout());

        // LINGO-446: flicker fix; see "Creating a Root Pane Container" in http://www.eclipse.org/articles/article.php?file=Article-Swing-SWT-Integration/index.html
        final JApplet applet = new JApplet();
        frame.add(applet);
        applet.setLayout(new java.awt.BorderLayout());

        final JScrollPane scrollPanel = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_NEVER,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        scrollPanel.setDoubleBuffered(true);

        scrollPanel.setBorder(BorderFactory.createEmptyBorder());
        applet.getContentPane().add(scrollPanel, java.awt.BorderLayout.CENTER);

        final ClusterMapFactory factory = ClusterMapFactory.createFactory();
        final ClusterMap clusterMap = factory.createClusterMap();
        final ClusterMapMediator mapMediator = factory.createMediator(clusterMap);
        this.mapMediator = mapMediator;

        final ClusterGraphPanel graphPanel = mapMediator.getGraphPanel();
        graphPanel.setDoubleBuffered(true);
        scrollPanel.setViewportView(graphPanel);

        scrollable.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                updateScrollBars();
            }
        });

        final SelectionAdapter adapter = new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                ScrollBar hbar = scrollable.getHorizontalBar();
                ScrollBar vbar = scrollable.getVerticalBar();
                final java.awt.Rectangle viewport = new java.awt.Rectangle(hbar.getSelection(), vbar.getSelection(),
                        hbar.getThumb(), vbar.getThumb());
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        graphPanel.scrollRectToVisible(viewport);
                    }
                });
            }
        };
        scrollable.getVerticalBar().addSelectionListener(adapter);
        scrollable.getHorizontalBar().addSelectionListener(adapter);

        final Runnable updateScrollBarsAsync = new Runnable() {
            public void run() {
                updateScrollBars();
            }
        };

        graphPanel.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentShown(ComponentEvent e) {
                graphPanelSize = graphPanel.getPreferredSize();
                Display.getDefault().asyncExec(updateScrollBarsAsync);
            }

            @Override
            public void componentResized(ComponentEvent e) {
                graphPanelSize = graphPanel.getPreferredSize();
                Display.getDefault().asyncExec(updateScrollBarsAsync);
            }
        });
    }

    /**
     * The latest size of Aduna's graph panel. Passed between Swing and SWT, so volatile.
     */
    private volatile Dimension graphPanelSize;

    /*
     * 
     */
    protected void updateScrollBars() {
        if (Display.findDisplay(Thread.currentThread()) == null)
            throw new IllegalStateException("Not an SWT thread: " + Thread.currentThread());

        if (graphPanelSize == null)
            return;

        org.eclipse.swt.graphics.Rectangle swtScrollableArea = scrollable.getClientArea();

        int width = Math.max(graphPanelSize.width, 0);
        int viewportWidth = Math.max(swtScrollableArea.width, 0);
        updateScrollBar(scrollable.getHorizontalBar(), width, viewportWidth);

        int height = Math.max(graphPanelSize.height, 0);
        int viewportHeight = Math.max(swtScrollableArea.height, 0);
        updateScrollBar(scrollable.getVerticalBar(), height, viewportHeight);
    }

    private static void updateScrollBar(ScrollBar sbar, int value, int viewportValue) {
        int selection = sbar.getSelection();
        int minimum = 0;
        int maximum = value;
        int thumb = Math.min(viewportValue, value);
        int increment = /* SharedScrolledComposite.V_SCROLL_INCREMENT */ 64;
        int pageIncrement = Math.max(thumb - 5 * thumb / 100, 5);

        sbar.setValues(selection, minimum, maximum, thumb, increment, pageIncrement);
    }

    /*
     * 
     */
    @Override
    public Control getControl() {
        return scrollable;
    }

    /*
     * 
     */
    @Override
    public void dispose() {
        editor.getSearchResult().removeListener(editorSyncListener);
        editor.getSite().getSelectionProvider().removeSelectionChangedListener(selectionListener);

        disposeBin.dispose();

        super.dispose();
    }

    /*
     * 
     */
    @Override
    public void setFocus() {
        // Ignore.
    }
}