/*
* Carrot2 project.
*
* Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński.
* 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.core.ui;
import java.util.*;
import org.carrot2.core.Cluster;
import org.carrot2.core.ProcessingResult;
import org.carrot2.workbench.core.helpers.Utils;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.viewers.*;
import org.eclipse.swt.widgets.Display;
import org.carrot2.shaded.guava.common.collect.Lists;
import org.carrot2.shaded.guava.common.collect.Maps;
/**
* Search editor serves as a {@link ISelectionProvider}, providing the selection
* events (a set of selected clusters or the entire search result).
*/
public final class SearchEditorSelectionProvider implements ISelectionProvider
{
/**
* An array of selection listeners.
*/
private final ArrayList<ISelectionChangedListener> listeners = Lists.newArrayList();
/**
* Selected clusters by their ID.
*/
private BitSet selectedClusters = new BitSet();
/**
* Mapping from clusters identifiers to cluster instances.
*/
private final Map<Integer, Cluster> clustersById = Maps.newHashMap();
/**
* Mapping from cluster identifiers to {@link TreePath} with
* {@link Cluster} objects for the tree model.
*/
private final Map<Integer, TreePath> clusterTreePathsById = Maps.newHashMap();
/**
* Last processing result to which this selection applies.
*/
private ProcessingResult processingResult;
/**
* Custom selection for a set of clusters.
*/
@SuppressWarnings("rawtypes")
public final class ClusterSelection implements IStructuredSelection
{
private final ArrayList<Cluster> selected;
public final TreeSelection treeSelection;
ClusterSelection(SearchEditorSelectionProvider provider)
{
selected = Lists.newArrayListWithCapacity(
provider.selectedClusters.cardinality());
for(int i = provider.selectedClusters.nextSetBit(0);
i >= 0; i = provider.selectedClusters.nextSetBit(i + 1))
{
selected.add(provider.clustersById.get(i));
}
treeSelection = createTreeSelection();
}
public Object getFirstElement()
{
return selected.get(0);
}
public Iterator iterator()
{
return selected.iterator();
}
public int size()
{
return selected.size();
}
public Object [] toArray()
{
return selected.toArray(new Cluster [selected.size()]);
}
public List<Cluster> toList()
{
return Lists.newArrayList(selected);
}
public boolean isEmpty()
{
return selected.isEmpty();
}
/**
* Convert a structured selection to a tree selection.
*/
private TreeSelection createTreeSelection()
{
final TreePath [] paths = new TreePath [selected.size()];
for (int i = 0; i < selected.size(); i++)
{
paths[i] = clusterTreePathsById.get(selected.get(i).getId());
}
return new TreeSelection(paths);
}
@Override
public String toString()
{
return treeSelection.toString();
}
}
/* */
SearchEditorSelectionProvider(SearchEditor editor)
{
/* Build the representation of selection for each cluster. */
if (editor.getSearchResult().getProcessingResult() != null)
{
buildRepresentation(editor.getSearchResult().getProcessingResult());
}
else
{
editor.getSearchResult().addListener(new SearchResultListenerAdapter() {
ClusterLabelPaths paths;
@Override
public void beforeProcessingResultUpdated()
{
paths = null;
if (SearchEditorSelectionProvider.this.processingResult != null)
{
paths = ClusterLabelPaths.from(
SearchEditorSelectionProvider.this.processingResult.getClusters(),
getSelection().toList());
}
}
@Override
public void processingResultUpdated(ProcessingResult result)
{
buildRepresentation(result);
}
@Override
public void afterProcessingResultUpdated()
{
if (paths != null)
{
setSelected(paths.filterMatching(
SearchEditorSelectionProvider.this.processingResult.getClusters()));
}
}
});
}
}
/* */
public void addSelectionChangedListener(ISelectionChangedListener listener)
{
assert Display.getCurrent() != null;
listeners.add(listener);
}
/* */
public void removeSelectionChangedListener(ISelectionChangedListener listener)
{
assert Display.getCurrent() != null;
listeners.remove(listener);
}
/* */
public ClusterSelection getSelection()
{
return new ClusterSelection(this);
}
/* */
public void setSelection(ISelection selection, ISelectionChangedListener... skipListeners)
{
if (selection.isEmpty())
{
if (!selectedClusters.isEmpty())
{
selectedClusters.clear();
fireSelectionChanged();
}
return;
}
if (selection instanceof IStructuredSelection)
{
final BitSet newSelection = getClustersFromSelection(
(IStructuredSelection) selection);
// Check if the selection has changed.
final BitSet changes = (BitSet) selectedClusters.clone();
changes.xor(newSelection);
if (changes.cardinality() > 0)
{
this.selectedClusters = newSelection;
fireSelectionChanged(skipListeners);
}
return;
}
// Do nothing (ignore selection we don't understand).
Utils.logError("Unexpected selection passed to search editor: "
+ selection.getClass(), false);
}
/**
* Get clusters contained in the selection.
*/
static BitSet getClustersFromSelection(IStructuredSelection selection)
{
final IStructuredSelection ss = (IStructuredSelection) selection;
final IAdapterManager mgr = Platform.getAdapterManager();
final BitSet newSelection = new BitSet();
for (Object selected : ss.toArray())
{
final Cluster c = (Cluster) mgr.getAdapter(selected, Cluster.class);
if (c == null) continue;
newSelection.set(c.getId());
}
return newSelection;
}
/* */
public void setSelection(ISelection selection)
{
setSelection(selection, new ISelectionChangedListener [0]);
}
/**
* Toggle cluster's <code>groupId</code> selection to <code>selected</code>.
*/
public void toggleSelected(
int groupId, boolean selected, ISelectionChangedListener... skipListeners)
{
assert Display.getCurrent() != null;
if (this.selectedClusters.get(groupId) == selected)
return;
this.selectedClusters.set(groupId, selected);
fireSelectionChanged(skipListeners);
}
/**
* Replace the current selection with the given set of selected groups.
*/
public void setSelected(int [] selectedGroups,
ISelectionChangedListener... skipListeners)
{
assert Display.getCurrent() != null;
this.selectedClusters.clear();
for (int i : selectedGroups)
selectedClusters.set(i);
fireSelectionChanged(skipListeners);
}
/**
* Replace the current selection with the given set of selected groups.
*/
public void setSelected(List<Cluster> clusters,
ISelectionChangedListener... skipListeners)
{
assert Display.getCurrent() != null;
int [] ids = new int [clusters.size()];
for (int i = 0; i < clusters.size(); i++)
ids[i] = clusters.get(i).getId();
setSelected(ids, skipListeners);
}
/**
* Fire selection changed event.
*/
private void fireSelectionChanged(ISelectionChangedListener... skipListeners)
{
assert Display.getCurrent() != null;
final SelectionChangedEvent event =
new SelectionChangedEvent(this, getSelection());
ArrayList<ISelectionChangedListener> notifyList = Lists.newArrayList(listeners);
notifyList.removeAll(Arrays.asList(skipListeners));
for (ISelectionChangedListener listener : notifyList)
{
listener.selectionChanged(event);
}
}
/**
* Build internal data structures required for selections.
*/
private void buildRepresentation(ProcessingResult processingResult)
{
this.selectedClusters.clear();
this.clustersById.clear();
this.clusterTreePathsById.clear();
this.processingResult = processingResult;
// Build mappings.
final ArrayList<Cluster> path = new ArrayList<Cluster>();
descend(processingResult.getClusters(), path);
}
/** Recursive descend on the list of clusters. */
private void descend(List<Cluster> clusters, List<Cluster> path)
{
for (Cluster c : clusters)
{
path.add(c);
this.clusterTreePathsById.put(c.getId(),
new TreePath(path.toArray()));
this.clustersById.put(c.getId(), c);
descend(c.getSubclusters(), path);
path.remove(path.size() - 1);
}
}
}