# HG changeset patch # Parent 258f0b0cada0df2887bdae57bab32bf83e25abdc #200020: Manipulate search results (delete items) diff --git a/api.search/src/org/netbeans/modules/search/MatchingObject.java b/api.search/src/org/netbeans/modules/search/MatchingObject.java --- a/api.search/src/org/netbeans/modules/search/MatchingObject.java +++ b/api.search/src/org/netbeans/modules/search/MatchingObject.java @@ -535,14 +535,15 @@ List detailNodes = new ArrayList(textDetails.size()); for (TextDetail txtDetail : textDetails) { - detailNodes.add(new TextDetail.DetailNode(txtDetail, false)); + detailNodes.add( + new TextDetail.DetailNode(txtDetail, false, resultModel)); } return detailNodes.toArray(new Node[detailNodes.size()]); } public Children getDetailsChildren(boolean replacing) { - return new DetailsChildren(replacing); + return new DetailsChildren(replacing, resultModel); } /** @@ -1032,15 +1033,17 @@ private class DetailsChildren extends Children.Keys { private boolean replacing; + private final ResultModel model; - public DetailsChildren(boolean replacing) { + public DetailsChildren(boolean replacing, ResultModel model) { this.replacing = replacing; setKeys(getTextDetails()); + this.model = model; } @Override protected Node[] createNodes(TextDetail key) { - return new Node[]{new TextDetail.DetailNode(key, replacing)}; + return new Node[]{new TextDetail.DetailNode(key, replacing, model)}; } } diff --git a/api.search/src/org/netbeans/modules/search/ResultModel.java b/api.search/src/org/netbeans/modules/search/ResultModel.java --- a/api.search/src/org/netbeans/modules/search/ResultModel.java +++ b/api.search/src/org/netbeans/modules/search/ResultModel.java @@ -48,12 +48,18 @@ import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.netbeans.modules.search.Constants.Limit; import org.openide.ErrorManager; import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.loaders.DataObject; import org.openide.nodes.Node; @@ -64,7 +70,10 @@ * @author Marian Petras */ public final class ResultModel { - + private static final Logger LOG = + Logger.getLogger(ResultModel.class.getName()); + public static final String PROP_REMOVED = "remove"; //NOI18N + public static final String PROP_REMOVEDDETAIL = "removeDetail"; //NOI18N public static final String PROP_SELECTION = "selection"; //NOI18N public static final String PROP_VALID = "valid"; //NOI18N public static final String PROP_MATCHING_OBJECTS = @@ -115,6 +124,74 @@ isFullText = (basicCriteria != null) && basicCriteria.isFullText(); startTime = -1; } + + /** + * Remove the {@link MatchingObject} from the model and informs the + * listeners. + * + * @param mo Matching object to remove. + */ + public void remove(MatchingObject mo) { + + matchingObjects.remove(mo); + totalDetailsCount -= getDetailsCount(mo); + + mo.cleanup(); + // inform listeners, old object contains removed object + propertyChangeSupport.firePropertyChange(PROP_REMOVED, + Arrays.asList(mo), null); + } + + public void removeDetailMatch(TextDetail txtDetail) { + MatchingObject found = getMatchingObjectFor(txtDetail); + LOG.log(Level.FINE, "Found {0}", found); //NOI18N + if (null != found) { + found.textDetails.remove(txtDetail); + totalDetailsCount--; + + if (found.textDetails.isEmpty()) { + remove(found); + } else { + propertyChangeSupport.firePropertyChange(PROP_REMOVEDDETAIL, + Arrays.asList(), null); + } + // inform listeners, old object contains removed object + } + } + + public void removeFolder(DataObject folder) { + List list = new ArrayList(); + + for (MatchingObject mo : matchingObjects) { + FileObject folderOfFile = mo.getFileObject().getParent(); + FileObject folderToRemove = folder.getPrimaryFile(); + boolean isInFolder = folderOfFile.equals(folderToRemove); + boolean isInSubFolder = FileUtil.isParentOf( + folderToRemove, folderOfFile); + if (isInFolder || isInSubFolder) { + list.add(mo); + } + } + matchingObjects.removeAll(list); + for (MatchingObject mo : list) { + + totalDetailsCount -= getDetailsCount(mo); + mo.cleanup(); + } + // inform listeners, old object contains removed objects + propertyChangeSupport.firePropertyChange(PROP_REMOVED, list, null); + } + + private MatchingObject getMatchingObjectFor(TextDetail txtDetail) { + for (MatchingObject mo : matchingObjects) { + for (TextDetail textDetail : mo.getTextDetails()) { + if (textDetail == txtDetail) { + return mo; + } + } + } + return null; + } /** */ diff --git a/api.search/src/org/netbeans/modules/search/TextDetail.java b/api.search/src/org/netbeans/modules/search/TextDetail.java --- a/api.search/src/org/netbeans/modules/search/TextDetail.java +++ b/api.search/src/org/netbeans/modules/search/TextDetail.java @@ -50,6 +50,7 @@ import java.awt.datatransfer.Transferable; import java.awt.event.ActionEvent; import java.io.CharConversionException; +import java.io.IOException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -451,6 +452,7 @@ /** Cached toString value. */ private String name; private String htmlDisplayName; + private final ResultModel model; /** * Constructs a node representing the specified information about @@ -458,11 +460,13 @@ * * @param txtDetail information to be represented by this node */ - public DetailNode(TextDetail txtDetail, boolean replacing) { + public DetailNode(TextDetail txtDetail, boolean replacing, + ResultModel model) { super(Children.LEAF, Lookups.fixed(txtDetail, new ReplaceCheckableNode(txtDetail, replacing))); this.txtDetail = txtDetail; + this.model = model; setValue(SearchDisplayer.ATTR_OUTPUT_LINE, DetailNode.getFullDesc(txtDetail)); @@ -823,6 +827,16 @@ protected void createPasteTypes(Transferable t, List s) { } + @Override + public boolean canDestroy() { + return true; + } + + @Override + public void destroy() throws IOException { + this.model.removeDetailMatch(txtDetail); +// super.destroy(); + } } // End of DetailNode class. /** diff --git a/api.search/src/org/netbeans/modules/search/ui/AbstractSearchResultsPanel.java b/api.search/src/org/netbeans/modules/search/ui/AbstractSearchResultsPanel.java --- a/api.search/src/org/netbeans/modules/search/ui/AbstractSearchResultsPanel.java +++ b/api.search/src/org/netbeans/modules/search/ui/AbstractSearchResultsPanel.java @@ -126,6 +126,12 @@ this.searchProviderPresenter = searchProviderPresenter; initComponents(); explorerManager = new ExplorerManager(); + + ActionMap map = this.getActionMap(); + // map delete key to delete action + map.put("delete", //NOI18N + ExplorerUtils.actionDelete(explorerManager, false)); + lookup = ExplorerUtils.createLookup(explorerManager, ResultView.getInstance().getActionMap()); initActions(); diff --git a/api.search/src/org/netbeans/modules/search/ui/BasicAbstractResultsPanel.java b/api.search/src/org/netbeans/modules/search/ui/BasicAbstractResultsPanel.java --- a/api.search/src/org/netbeans/modules/search/ui/BasicAbstractResultsPanel.java +++ b/api.search/src/org/netbeans/modules/search/ui/BasicAbstractResultsPanel.java @@ -45,6 +45,8 @@ import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.beans.PropertyVetoException; import java.util.ResourceBundle; import javax.accessibility.AccessibleContext; @@ -74,7 +76,7 @@ * @author jhavlin */ public abstract class BasicAbstractResultsPanel - extends AbstractSearchResultsPanel { + extends AbstractSearchResultsPanel implements PropertyChangeListener { @StaticResource private static final String SHOW_DETAILS_ICON = @@ -120,8 +122,15 @@ setRootDisplayName(NbBundle.getMessage(ResultView.class, "TEXT_SEARCHING___")); //NOI18N initAccessibility(); + this.resultModel.addPropertyChangeListener( + ResultModel.PROP_REMOVED, this); } + @Override + public void propertyChange (PropertyChangeEvent evt) { + // update the root node after change in model + setFinalRootNodeText(); + } public void update() { if (details && btnExpand.isVisible() && !btnExpand.isEnabled()) { diff --git a/api.search/src/org/netbeans/modules/search/ui/MatchingObjectNode.java b/api.search/src/org/netbeans/modules/search/ui/MatchingObjectNode.java --- a/api.search/src/org/netbeans/modules/search/ui/MatchingObjectNode.java +++ b/api.search/src/org/netbeans/modules/search/ui/MatchingObjectNode.java @@ -59,6 +59,8 @@ import org.netbeans.api.annotations.common.StaticResource; import org.netbeans.modules.search.MatchingObject; import org.netbeans.modules.search.MatchingObject.InvalidityStatus; +import org.netbeans.modules.search.ResultModel; +import org.openide.actions.DeleteAction; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; @@ -100,22 +102,24 @@ private boolean valid = true; private PropertyChangeListener validityListener; private PropertyChangeListener selectionListener; + private ResultModel resultModel; PropertySet[] propertySets; public MatchingObjectNode(Node original, - org.openide.nodes.Children children, - MatchingObject matchingObject, final boolean replacing) { + org.openide.nodes.Children children, MatchingObject matchingObject, + final boolean replacing, ResultModel model) { this(original, children, matchingObject, - new ReplaceCheckableNode(matchingObject, replacing)); + new ReplaceCheckableNode(matchingObject, replacing), model); } private MatchingObjectNode(Node original, org.openide.nodes.Children children, final MatchingObject matchingObject, - ReplaceCheckableNode checkableNode) { + ReplaceCheckableNode checkableNode, ResultModel model) { super(children, Lookups.fixed(matchingObject, checkableNode, matchingObject.getFileObject())); Parameters.notNull("original", original); //NOI18N + this.resultModel = model; this.matchingObject = matchingObject; if (matchingObject.isObjectValid()) { this.original = original; @@ -165,7 +169,8 @@ if (!context) { return new Action[]{ SystemAction.get(OpenMatchingObjectsAction.class), - new CopyPathAction() + new CopyPathAction(), + SystemAction.get(DeleteAction.class) }; } else { return new Action[0]; @@ -234,7 +239,7 @@ @Override public boolean canDestroy() { - return false; + return true; } public void clean() { @@ -274,6 +279,12 @@ return propertySets; } + @Override + public void destroy () throws IOException { + // when removing the node, the node's content is removed from model + resultModel.remove(this.matchingObject); + } + /** * Check whether the file object is valid and a valid data object can be * found for it. It should be checked after original node is destroyed. It diff --git a/api.search/src/org/netbeans/modules/search/ui/ResultsOutlineSupport.java b/api.search/src/org/netbeans/modules/search/ui/ResultsOutlineSupport.java --- a/api.search/src/org/netbeans/modules/search/ui/ResultsOutlineSupport.java +++ b/api.search/src/org/netbeans/modules/search/ui/ResultsOutlineSupport.java @@ -53,6 +53,7 @@ import java.beans.PropertyChangeSupport; import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.LinkedList; @@ -73,6 +74,7 @@ import org.netbeans.modules.search.MatchingObject; import org.netbeans.modules.search.ResultModel; import org.netbeans.modules.search.Selectable; +import org.netbeans.modules.search.TextDetail; import org.netbeans.modules.search.ui.AbstractSearchResultsPanel.RootNode; import org.netbeans.swing.etable.ETableColumnModel; import org.netbeans.swing.outline.Outline; @@ -95,7 +97,7 @@ * * @author jhavlin */ -public class ResultsOutlineSupport { +public class ResultsOutlineSupport implements PropertyChangeListener { @StaticResource private static final String ROOT_NODE_ICON = @@ -125,11 +127,27 @@ this.details = details; this.resultModel = resultModel; this.basicComposition = basicComposition; - this.resultsNode = new ResultsNode(); + this.resultsNode = new ResultsNode(resultModel); this.infoNode = infoNode; this.invisibleRoot = new RootNode(resultsNode, infoNode); this.matchingObjectNodes = new LinkedList(); createOutlineView(); + this.resultModel.addPropertyChangeListener( + ResultModel.PROP_REMOVED, this); + } + + @Override + @SuppressWarnings("unchecked") + public void propertyChange(PropertyChangeEvent evt) { + List items = (List) evt.getOldValue(); + + //support folders (multiple matching objects) or single matching objects + for (MatchingObject mo : items) { + removedMatchingObject(mo); + } + + // rebuild the tree from the modified model + this.update(); } private void createOutlineView() { @@ -312,10 +330,11 @@ private FolderTreeChildren folderTreeChildren; private String htmlDisplayName = null; - public ResultsNode() { + public ResultsNode(ResultModel model) { super(new FlatChildren()); this.flatChildren = (FlatChildren) this.getChildren(); - this.folderTreeChildren = new FolderTreeChildren(rootPathItem); + this.folderTreeChildren = + new FolderTreeChildren(rootPathItem, model); } void update() { @@ -412,8 +431,8 @@ } else { children = key.getDetailsChildren(replacing); } - MatchingObjectNode mon = - new MatchingObjectNode(delegate, children, key, replacing); + MatchingObjectNode mon = new MatchingObjectNode( + delegate, children, key, replacing, resultModel); synchronized (this) { if (!closed) { matchingObjectNodes.add(mon); @@ -444,6 +463,21 @@ } return rootFiles; } + public synchronized void removedMatchingObject(MatchingObject mo) { + if (closed) { + return; + } + removeFilesFromTreeView(rootPathItem, mo); + removeEmptyFoldersFromTreeView(rootPathItem); + } + + public synchronized void removedTextDetail(TextDetail detail) { + if (closed) { + return; + } +// removeFilesFromTreeView(rootPathItem, mo); + removeEmptyFoldersFromTreeView(rootPathItem); + } private List getRelativePath(FileObject parent, FileObject fo) { List l = new LinkedList(); @@ -458,6 +492,43 @@ return l; } + private void removeFilesFromTreeView(FolderTreeItem parentItem, + MatchingObject matchingObject) { + List removeableItems = new ArrayList(); + + for (FolderTreeItem pi : parentItem.getChildren()) { + if (!pi.isPathLeaf()) { + //start recursion for folders + removeFilesFromTreeView(pi, matchingObject); + } else { + if (pi.getMatchingObject().equals(matchingObject)) { + // collect all the files which have to be removed + removeableItems.add(pi); + } + } + } + parentItem.removeChildren(removeableItems); + } + + private void removeEmptyFoldersFromTreeView(FolderTreeItem parentItem) { + List emptyFolders = new ArrayList(); + + for (FolderTreeItem pi : parentItem.getChildren()) { + { + // start recursion + removeEmptyFoldersFromTreeView(pi); + + if (pi.getChildren().isEmpty() + && null == pi.getMatchingObject()) { + // collect all the empty folders which have to be removed + emptyFolders.add(pi); + } + } + } + // remove empty folders + parentItem.removeChildren(emptyFolders); + } + private void addToTreeView(FolderTreeItem parentItem, List path, MatchingObject matchingObject) { for (FolderTreeItem pi : parentItem.getChildren()) { @@ -542,6 +613,12 @@ return children; } + public boolean removeChildren (Collection list) { + boolean result = children.removeAll(list); + firePropertyChange(PROP_CHILDREN, null, null); + return result; + } + public MatchingObject getMatchingObject() { return matchingObject; } @@ -596,13 +673,15 @@ } private class FolderTreeNode extends FilterNode { + private ResultModel model; - public FolderTreeNode(FolderTreeItem pathItem) { + public FolderTreeNode(FolderTreeItem pathItem, ResultModel model) { super(pathItem.getFolder().getNodeDelegate(), - new FolderTreeChildren(pathItem), + new FolderTreeChildren(pathItem, model), Lookups.fixed(pathItem, new ReplaceCheckableNode(pathItem, replacing), pathItem.getFolder().getPrimaryFile())); + this.model=model; pathItem.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { @@ -627,6 +706,20 @@ } @Override + public boolean canDestroy () { + return true; + } + + @Override + public void destroy () throws IOException { + //FIXME alter the model + FolderTreeItem folder = + this.getLookup().lookup(FolderTreeItem.class); + this.model.removeFolder(folder.getFolder()); +// this.resultModel.remove(this.getLookup().lookup(FolderTreeItem.class)); + } + + @Override public Transferable drag() throws IOException { return UiUtils.DISABLE_TRANSFER; } @@ -640,8 +733,9 @@ private class FolderTreeChildren extends Children.Keys { private FolderTreeItem item = null; + private final ResultModel model; - public FolderTreeChildren(FolderTreeItem pathItem) { + public FolderTreeChildren(FolderTreeItem pathItem, ResultModel model) { this.item = pathItem; pathItem.addPropertyChangeListener(new PropertyChangeListener() { @Override @@ -652,6 +746,7 @@ } } }); + this.model = model; } @Override @@ -669,7 +764,7 @@ if (key.isPathLeaf()) { n = createNodeForMatchingObject(key.getMatchingObject()); } else { - n = new FolderTreeNode(key); + n = new FolderTreeNode(key, model); } return new Node[]{n}; } diff --git a/api.search/test/unit/src/org/netbeans/modules/search/TextDetailTest.java b/api.search/test/unit/src/org/netbeans/modules/search/TextDetailTest.java --- a/api.search/test/unit/src/org/netbeans/modules/search/TextDetailTest.java +++ b/api.search/test/unit/src/org/netbeans/modules/search/TextDetailTest.java @@ -144,7 +144,7 @@ public String createHtmlDisplayName(String line, String match) { TextDetail td = createMockTextDetail(line, match); - DetailNode detailNode = new TextDetail.DetailNode(td, false); + DetailNode detailNode = new TextDetail.DetailNode(td, false, null); String htmlDisplayName = detailNode.getHtmlDisplayName(); Pattern p = Pattern.compile("(\\s*\\d+: )(.*?)(\\s+\\[.*\\])"); Matcher m = p.matcher(htmlDisplayName); diff --git a/api.search/test/unit/src/org/netbeans/modules/search/ui/MatchingObjectNodeTest.java b/api.search/test/unit/src/org/netbeans/modules/search/ui/MatchingObjectNodeTest.java --- a/api.search/test/unit/src/org/netbeans/modules/search/ui/MatchingObjectNodeTest.java +++ b/api.search/test/unit/src/org/netbeans/modules/search/ui/MatchingObjectNodeTest.java @@ -85,7 +85,7 @@ ResultModel rm = SearchTestUtils.createResultModelWithOneMatch(); MatchingObject mo = rm.getMatchingObjects().get(0); MatchingObjectNode mon = new MatchingObjectNode(n, Children.LEAF, mo, - false); + false, rm); mon.addNodeListener(new DisplayNameChangeListener(s)); mon.getDisplayName(); mo.getFileObject().delete(); @@ -105,7 +105,7 @@ Node original = dob.getNodeDelegate(); fob.delete(); // No exception should be thrown from the constructor. - Node n = new MatchingObjectNode(original, Children.LEAF, mo, false); + Node n = new MatchingObjectNode(original, Children.LEAF, mo, false, rm); assertEquals("test.txt", n.getDisplayName()); }