/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.repository.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import com.rapidminer.RepositoryProcessLocation;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.actions.OpenAction;
import com.rapidminer.gui.actions.ToggleAction;
import com.rapidminer.gui.dnd.AbstractPatchedTransferHandler;
import com.rapidminer.gui.dnd.DragListener;
import com.rapidminer.gui.dnd.RepositoryLocationList;
import com.rapidminer.gui.dnd.TransferableOperator;
import com.rapidminer.gui.tools.ProgressThread;
import com.rapidminer.gui.tools.ProgressThreadDialog;
import com.rapidminer.gui.tools.RepositoryGuiTools;
import com.rapidminer.gui.tools.ResourceActionAdapter;
import com.rapidminer.gui.tools.SwingTools;
import com.rapidminer.gui.tools.SwingTools.ResultRunnable;
import com.rapidminer.gui.tools.components.ToolTipWindow;
import com.rapidminer.gui.tools.components.ToolTipWindow.TipProvider;
import com.rapidminer.gui.tools.components.ToolTipWindow.TooltipLocation;
import com.rapidminer.gui.tools.dialogs.ConfirmDialog;
import com.rapidminer.gui.tools.dialogs.SelectionDialog;
import com.rapidminer.repository.DataEntry;
import com.rapidminer.repository.Entry;
import com.rapidminer.repository.Folder;
import com.rapidminer.repository.ProcessEntry;
import com.rapidminer.repository.Repository;
import com.rapidminer.repository.RepositoryActionCondition;
import com.rapidminer.repository.RepositoryActionConditionImplConfigRepository;
import com.rapidminer.repository.RepositoryActionConditionImplStandard;
import com.rapidminer.repository.RepositoryActionConditionImplStandardNoRepository;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.RepositoryLocation;
import com.rapidminer.repository.RepositoryManager;
import com.rapidminer.repository.RepositorySortingMethod;
import com.rapidminer.repository.RepositorySortingMethodListener;
import com.rapidminer.repository.gui.actions.AbstractRepositoryAction;
import com.rapidminer.repository.gui.actions.ConfigureRepositoryAction;
import com.rapidminer.repository.gui.actions.CopyEntryRepositoryAction;
import com.rapidminer.repository.gui.actions.CopyLocationAction;
import com.rapidminer.repository.gui.actions.CreateFolderAction;
import com.rapidminer.repository.gui.actions.CutEntryRepositoryAction;
import com.rapidminer.repository.gui.actions.DeleteRepositoryEntryAction;
import com.rapidminer.repository.gui.actions.OpenEntryAction;
import com.rapidminer.repository.gui.actions.OpenInFileBrowserAction;
import com.rapidminer.repository.gui.actions.PasteEntryRepositoryAction;
import com.rapidminer.repository.gui.actions.RefreshRepositoryEntryAction;
import com.rapidminer.repository.gui.actions.RenameRepositoryEntryAction;
import com.rapidminer.repository.gui.actions.ShowProcessInRepositoryAction;
import com.rapidminer.repository.gui.actions.SortByLastModifiedAction;
import com.rapidminer.repository.gui.actions.SortByNameAction;
import com.rapidminer.repository.gui.actions.StoreProcessAction;
import com.rapidminer.repository.local.LocalRepository;
import com.rapidminer.studio.io.gui.internal.DataImportWizardBuilder;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.PasswordInputCanceledException;
import com.rapidminer.tools.ProgressListener;
/**
* A tree displaying repository contents.
* <p>
* To add new actions to the popup menu, call
* {@link #addRepositoryAction(Class, RepositoryActionCondition, Class, boolean, boolean)} or
* {@link #addRepositoryAction(Class, RepositoryActionCondition, boolean, boolean)}. Be sure to
* follow its instructions carefully.
*
* @author Simon Fischer, Tobias Malbrecht, Marco Boeck, Adrian Wilke
*/
public class RepositoryTree extends JTree {
/**
* @author Nils Woehler, Adrian Wilke
*
*/
private final class RepositoryTreeTransferhandler extends AbstractPatchedTransferHandler {
private static final long serialVersionUID = 1L;
// Remember whether the last cut/copy action was a MOVE
// A move will result in the entry being deleted upon drop / paste
// Unfortunately there is no easy way to know this from the TransferSupport
// passed to importData(). It is not even kno wn in createTransferable(), so we
// cannot even attach it to the Transferable
// This implementation implies that we can only transfer from one repository tree
// to the same instance since this state is not passed to other instances.
// REASON THIS IS HERE:
// ctrl+x followed by ctrl+v would copy the entry instead of moving it due to
// the way the transfer system is implemented. See RM-10 for further details.
int latestAction = 0;
public RepositoryTreeTransferhandler() {
addDragListener(new DragListener() {
@Override
public void dragStarted(Transferable t) {
// reset latestAction because a new drag makes the last action irrelevant
latestAction = 0;
}
@Override
public void dragEnded() {}
});
}
@Override
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
List<DataFlavor> flavors = Arrays.asList(transferFlavors);
boolean contains = flavors.contains(DataFlavor.javaFileListFlavor);
contains |= flavors.contains(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR);
contains |= flavors.contains(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR);
return contains;
}
@Override
public boolean importData(final TransferSupport ts) {
// Determine where to insert
final Entry droppedOnEntry;
if (ts.isDrop()) {
Point dropPoint = ts.getDropLocation().getDropPoint();
TreePath path = getPathForLocation((int) dropPoint.getX(), (int) dropPoint.getY());
if (path == null) {
return false;
}
droppedOnEntry = (Entry) path.getLastPathComponent();
} else {
droppedOnEntry = getSelectedEntry();
}
if (droppedOnEntry == null) {
return false;
}
// Execute operation chosen by flavor
try {
List<DataFlavor> flavors = Arrays.asList(ts.getDataFlavors());
if (flavors.contains(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR)) {
// Single repository entry
final RepositoryLocation location = (RepositoryLocation) ts.getTransferable()
.getTransferData(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR);
List<RepositoryLocation> singleLocationList = new LinkedList<>();
singleLocationList.add(location);
return copyOrMoveRepositoryEntries(droppedOnEntry, singleLocationList, ts);
} else if (flavors.contains(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR)) {
// Multiple repository entries
RepositoryLocationList locationList = (RepositoryLocationList) ts.getTransferable()
.getTransferData(TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR);
List<RepositoryLocation> locations = locationList.getAll();
return copyOrMoveRepositoryEntries(droppedOnEntry, locations, ts);
} else if (flavors.contains(DataFlavor.javaFileListFlavor)) {
// Import file via wizard
@SuppressWarnings("unchecked")
List<File> files = (List<File>) ts.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
File file = files.get(0);
DataImportWizardBuilder builder = new DataImportWizardBuilder();
builder.forFile(file.toPath()).build(owner).getDialog().setVisible(true);
return true;
} else {
// Flavor not supported
return false;
}
} catch (UnsupportedFlavorException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.repository.RepositoryTree.accepting_flavor_error", e), e);
return false;
} catch (IOException | RepositoryException | RuntimeException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.repository.RepositoryTree.error_during_drop", e), e);
return false;
}
}
/**
* Copies / moves multiple repository locations.
*
* Additionally, at first it is checked, if the operations are allowed.
*/
private boolean copyOrMoveRepositoryEntries(final Entry droppedOnEntry, final List<RepositoryLocation> locations,
final TransferSupport ts) throws RepositoryException {
// First check, if operation is allowed for all locations
for (RepositoryLocation location : locations) {
if (!copyOrMoveCheck(droppedOnEntry, location, ts)) {
return false;
}
}
// Execute operations in new thread
new ProgressThread("copy_repository_entry", true) {
final class UserDecisions {
boolean repeatDecision = false;
boolean overwriteIfExists = false;
int lastDecision = 0;
}
private static final int INSERT = 1;
private static final int OVERWRITE = 2;
private static final int SKIP = 3;
/** Total progress of progress listener bar */
private int progressListenerCompleted = 0;
/** Step size for single entry operation */
private final int PROGRESS_LISTENER_SINGLE_STEP_SIZE = 100;
/**
* Iteratively perform copy / move
*/
@Override
public void run() {
// Check, if repository is in location.
// Repositories can not be moved.
// This results in a copy operation.
boolean isRepositoryInLocations = false;
for (RepositoryLocation location : locations) {
try {
if (location.locateEntry() instanceof Repository) {
isRepositoryInLocations = true;
}
} catch (RepositoryException e) {
SwingTools.showSimpleErrorMessage("error_in_copy_repository_entry", e, location.toString(),
e.getMessage());
return;
}
}
boolean isSingleEntryOperation = true;
if (locations.size() > 1) {
isSingleEntryOperation = false;
}
TreePath droppedOnPath = RepositoryTreeModel.getPathTo(droppedOnEntry,
RepositoryManager.getInstance(null));
// Initialize progress listener
getProgressListener().setTotal(locations.size() * PROGRESS_LISTENER_SINGLE_STEP_SIZE);
getProgressListener().setCompleted(progressListenerCompleted);
final UserDecisions userDecisions = new UserDecisions();
locationLoop: for (RepositoryLocation location : locations) {
// Single entry check
try {
// Entry already exists, overwrite?
final String effectiveNewName = location.locateEntry().getName();
if (((Folder) droppedOnEntry).containsEntry(effectiveNewName)) {
// Do not confuse user with incorrect selected paths
RepositoryTree.this.setSelectionPath(droppedOnPath);
if (!userDecisions.repeatDecision) {
final List<String> optionsToSelect = new LinkedList<>();
optionsToSelect.add("existing_entry.insert");
optionsToSelect.add("existing_entry.overwrite");
optionsToSelect.add("existing_entry.skip");
final List<String> optionsToCheck = new LinkedList<>();
if (!isSingleEntryOperation) {
optionsToCheck.add("existing_entry.repeat");
}
int result = SwingTools.invokeAndWaitWithResult(new ResultRunnable<Integer>() {
@Override
public Integer run() {
SelectionDialog selectionDialog = new SelectionDialog(
ProgressThreadDialog.getInstance(), "existing_entry",
SelectionDialog.OK_CANCEL_OPTION, new String[] { effectiveNewName },
optionsToSelect, optionsToCheck).showDialog();
if (selectionDialog.isOptionSelected("existing_entry.insert")) {
userDecisions.lastDecision = INSERT;
} else if (selectionDialog.isOptionSelected("existing_entry.overwrite")) {
userDecisions.lastDecision = OVERWRITE;
} else {
userDecisions.lastDecision = SKIP;
}
if (selectionDialog.isOptionChecked("existing_entry.repeat")) {
userDecisions.repeatDecision = true;
}
return selectionDialog.getResult();
}
});
if (result != SelectionDialog.OK_OPTION) {
return;
}
}
switch (userDecisions.lastDecision) {
case INSERT:
userDecisions.overwriteIfExists = false;
break;
case OVERWRITE:
userDecisions.overwriteIfExists = true;
break;
case SKIP:
default:
continue locationLoop;
}
}
} catch (RepositoryException e) {
SwingTools.showSimpleErrorMessage("error_in_copy_repository_entry", e, location.toString(),
e.getMessage());
continue;
}
// Do copy or move operation
// Extracted to own method for lock handling and retry calls
boolean done = executeCopyOrMoveOperation(location, isRepositoryInLocations, isSingleEntryOperation,
userDecisions.overwriteIfExists);
if (!done) {
break;
}
progressListenerCompleted += PROGRESS_LISTENER_SINGLE_STEP_SIZE;
}
// On multi-operations, select the target folder after finishing copy/move
// operation
if (droppedOnEntry != null && !isSingleEntryOperation) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TreePath droppedOnPath = RepositoryTreeModel.getPathTo(droppedOnEntry,
RepositoryManager.getInstance(null));
RepositoryTree.this.setSelectionPath(droppedOnPath);
RepositoryTree.this.scrollPathToVisible(droppedOnPath);
}
});
}
getProgressListener().complete();
}
/**
* Runs the copy or move operation using {@link RepositoryManager}
*
* @return <code>false</code> if the user chose to cancel the operation;
* <code>true</code> otherwise
*/
private boolean executeCopyOrMoveOperation(RepositoryLocation location, boolean isRepositoryInLocations,
boolean isSingleEntryOperation, boolean overwriteIfExists) {
try {
ProgressListener progressListener = null;
progressListener = new RescalingProgressListener(getProgressListener(), progressListenerCompleted,
progressListenerCompleted + PROGRESS_LISTENER_SINGLE_STEP_SIZE);
if (isMoveOperation(ts, isRepositoryInLocations)) {
RepositoryManager.getInstance(null).move(location, (Folder) droppedOnEntry, null,
overwriteIfExists, progressListener);
// On drag and drop move operation with overwrite, two delete operations
// are performed. This results in a selection of a wrong tree element.
// For this case, select the element, which is the target of the drop
// operation.
if (overwriteIfExists && droppedOnEntry != null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
RepositoryTree.this.setSelectionPath(RepositoryTreeModel.getPathTo(droppedOnEntry,
RepositoryManager.getInstance(null)));
}
});
}
} else {
RepositoryManager.getInstance(null).copy(location, (Folder) droppedOnEntry, null,
overwriteIfExists, progressListener);
}
} catch (RepositoryException e) {
if (e.getCause() != null && e.getCause() instanceof PasswordInputCanceledException) {
// no extra dialog if login dialog was canceled
return false;
}
// Do not show "cancel" option, if is is an single entry operation
int dialogMode = ConfirmDialog.YES_NO_CANCEL_OPTION;
if (isSingleEntryOperation) {
dialogMode = ConfirmDialog.YES_NO_OPTION;
}
String locationName = location.getName();
if (locationName == null) {
locationName = "";
}
ConfirmDialog dialog = null;
if (e.getMessage() != null && !e.getMessage().isEmpty()) {
dialog = new ConfirmDialog(ProgressThreadDialog.getInstance(), "error_in_copy_entry_with_cause",
dialogMode, false, locationName, e.getMessage());
} else {
dialog = new ConfirmDialog(ProgressThreadDialog.getInstance(), "error_in_copy_entry", dialogMode,
false, locationName);
}
dialog.setVisible(true);
int retry = dialog.getReturnOption();
if (retry == ConfirmDialog.YES_OPTION) {
return executeCopyOrMoveOperation(location, isRepositoryInLocations, isSingleEntryOperation,
overwriteIfExists);
} else if (retry == ConfirmDialog.CANCEL_OPTION) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.repository.RepositoryTree.error_during_copying", e);
return false;
} else {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.repository.RepositoryTree.error_during_copying", e);
// No retry and no cancel, just go on
}
}
return true;
}
}.start();
// No failures in initial check
return true;
}
/**
* Checks, if desired copy or move operation is allowed.
*
* If not, false is returned and an additional error message is shown.
*
* @throws RepositoryException
* If repository can not be found or entry can not be located.
* @throws IllegalArgumentException
* If an argument is null
*/
private boolean copyOrMoveCheck(final Entry droppedOnEntry, final RepositoryLocation location,
final TransferSupport ts) throws RepositoryException {
if (droppedOnEntry == null) {
throw new IllegalArgumentException("Entry must not be null.");
} else if (location == null) {
throw new IllegalArgumentException("RepositoryLocation must not be null.");
} else if (ts == null) {
throw new IllegalArgumentException("TransferSupport must not be null.");
} else if (!(droppedOnEntry instanceof Folder)) {
// Copy / move only to folder
return false;
} else {
// Check for unknown parameters
RepositoryLocation targetLocation = ((Folder) droppedOnEntry).getLocation();
if (targetLocation == null) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.repository.RepositoryTree.parameter_missing.target_location");
return false;
}
String targetAbsolutePath = targetLocation.getAbsoluteLocation();
if (targetAbsolutePath == null || targetAbsolutePath.isEmpty()) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.repository.RepositoryTree.parameter_missing.target_path");
return false;
}
String sourceAbsolutePath = location.getAbsoluteLocation();
if (sourceAbsolutePath == null || sourceAbsolutePath.isEmpty()) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.repository.RepositoryTree.parameter_missing.source_path");
return false;
}
Entry locationEntry = location.locateEntry();
if (locationEntry == null) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.repository.RepositoryTree.parameter_missing.repository_location");
return false;
}
String effectiveNewName = locationEntry.getName();
if (effectiveNewName == null || effectiveNewName.isEmpty()) {
LogService.getRoot().log(Level.WARNING,
"com.rapidminer.repository.RepositoryTree.parameter_missing.name");
return false;
}
if (isMoveOperation(ts, false)) {
// Check for MOVE
// Make sure same folder moves are forbidden
if (sourceAbsolutePath.equals(targetAbsolutePath)) {
SwingTools.showVerySimpleErrorMessage("repository_move_same_folder");
return false;
}
// Make sure moving parent folder into subfolder is forbidden
if (RepositoryGuiTools.isSuccessor(sourceAbsolutePath, targetAbsolutePath)) {
SwingTools.showVerySimpleErrorMessage("repository_move_into_subfolder");
return false;
}
// Entry should be moved into its own parent folder, invalid
if (!(location.locateEntry() instanceof Repository)) {
String sourceParentLocation = location.locateEntry().getContainingFolder().getLocation()
.getAbsoluteLocation();
if (sourceParentLocation.equals(targetAbsolutePath)) {
SwingTools.showVerySimpleErrorMessage("repository_move_same_folder");
return false;
}
}
} else {
// Check for COPY
// Make sure same folder moves are forbidden
if (sourceAbsolutePath.equals(targetAbsolutePath)) {
SwingTools.showVerySimpleErrorMessage("repository_copy_same_folder");
return false;
}
// Make sure moving parent folder into subfolder is forbidden
if (RepositoryGuiTools.isSuccessor(sourceAbsolutePath, targetAbsolutePath)) {
SwingTools.showVerySimpleErrorMessage("repository_copy_into_subfolder");
return false;
}
}
return true;
}
}
@Override
protected void exportDone(JComponent c, Transferable data, int action) {
if (action == MOVE) {
latestAction = MOVE;
} else {
latestAction = 0;
}
}
@Override
public int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
@Override
protected Transferable createTransferable(JComponent c) {
final TreePath[] treePaths = getSelectionPaths();
if (treePaths.length == 0) {
// Nothing selected
return null;
} else if (treePaths.length == 1) {
// Exactly one item selected
Entry e = (Entry) treePaths[0].getLastPathComponent();
final RepositoryLocation location = e.getLocation();
return new Transferable() {
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (flavor.equals(DataFlavor.stringFlavor)) {
return location.getAbsoluteLocation();
} else if (TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR.equals(flavor)) {
return location;
} else {
throw new UnsupportedFlavorException(flavor);
}
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR,
DataFlavor.stringFlavor };
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_FLAVOR.equals(flavor)
|| DataFlavor.stringFlavor.equals(flavor);
}
};
} else {
// Multiple entries selected
final RepositoryLocationList locationList = new RepositoryLocationList();
for (TreePath treePath : treePaths) {
locationList.add(((Entry) treePath.getLastPathComponent()).getLocation());
}
locationList.removeIntersectedLocations();
return new Transferable() {
final RepositoryLocationList locations = locationList;
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR.equals(flavor)
|| DataFlavor.stringFlavor.equals(flavor);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[] { TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR,
DataFlavor.stringFlavor };
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException {
if (TransferableOperator.LOCAL_TRANSFERRED_REPOSITORY_LOCATION_LIST_FLAVOR.equals(flavor)) {
return locations;
} else if (DataFlavor.stringFlavor.equals(flavor)) {
return locations.toString();
} else {
throw new UnsupportedFlavorException(flavor);
}
}
};
}
}
@Override
public Icon getVisualRepresentation(Transferable t) {
return null;
}
/**
* Checks, if the current operation is a MOVE operation. If not, it is a COPY operation.
*
* @param ts
* Provides info about the current operation
* @param isRepositoryInLocations
* This is true, if the sources to copy contain a repository. Repositories can
* not be moved. This results in a copy operation.
* @return true, if the operation is a move operation
* @return false, if the operation is not a move operation (e.g. copy)
*/
private boolean isMoveOperation(TransferSupport ts, boolean isRepositoryInLocations) {
return (latestAction == MOVE || ts.isDrop() && ts.getDropAction() == MOVE) && !isRepositoryInLocations;
}
}
/**
* Holds the RepositoryAction entries.
*/
private static class RepositoryActionEntry {
private Class<? extends AbstractRepositoryAction<?>> actionClass;
private RepositoryActionCondition condition;
private boolean hasSeparatorBefore;
private boolean hasSeparatorAfter;
public RepositoryActionEntry(Class<? extends AbstractRepositoryAction<?>> actionClass,
RepositoryActionCondition condition, boolean hasSeparatorBefore, boolean hasSeparatorAfter) {
this.actionClass = actionClass;
this.condition = condition;
this.hasSeparatorAfter = hasSeparatorAfter;
this.hasSeparatorBefore = hasSeparatorBefore;
}
public boolean hasSeperatorBefore() {
return hasSeparatorBefore;
}
public boolean hasSeperatorAfter() {
return hasSeparatorAfter;
}
public RepositoryActionCondition getRepositoryActionCondition() {
return condition;
}
public Class<? extends AbstractRepositoryAction<?>> getRepositoryActionClass() {
return actionClass;
}
}
public final AbstractRepositoryAction<Entry> RENAME_ACTION = new RenameRepositoryEntryAction(this);
public final AbstractRepositoryAction<Entry> DELETE_ACTION = new DeleteRepositoryEntryAction(this);
public final AbstractRepositoryAction<DataEntry> OPEN_ACTION = new OpenEntryAction(this);
public final AbstractRepositoryAction<Entry> REFRESH_ACTION = new RefreshRepositoryEntryAction(this);
public final AbstractRepositoryAction<Folder> CREATE_FOLDER_ACTION = new CreateFolderAction(this);
public final ResourceActionAdapter SHOW_PROCESS_IN_REPOSITORY_ACTION = new ShowProcessInRepositoryAction(this);
private final Dialog owner;
private List<AbstractRepositoryAction<?>> listToEnable = new LinkedList<>();
private EventListenerList listenerList = new EventListenerList();
final ToggleAction SORT_BY_NAME_ACTION = new SortByNameAction(this);
final ToggleAction SORT_BY_LAST_MODIFIED_DATE_ACTION = new SortByLastModifiedAction(this);
private static final long serialVersionUID = -6613576606220873341L;
private static final List<RepositoryActionEntry> REPOSITORY_ACTIONS = new LinkedList<>();
/** List of actions available for multiple entries selected in menu */
private static final List<RepositoryActionEntry> REPOSITORY_MULTIPLE_ENTRIES_ACTIONS = new LinkedList<>();
/** Configuration: List of actions available for multiple entries selected in menu */
private static final List<String> multipleMenuEntriesActionClasses = new LinkedList<>(
Arrays.asList(CutEntryRepositoryAction.class.getName(), CopyEntryRepositoryAction.class.getName(),
DeleteRepositoryEntryAction.class.getName(), RefreshRepositoryEntryAction.class.getName()));
private final int TREE_ROW_HEIGHT = 24;
static {
addRepositoryAction(ConfigureRepositoryAction.class, new RepositoryActionConditionImplConfigRepository(), false,
true);
addRepositoryAction(OpenEntryAction.class,
new RepositoryActionConditionImplStandard(new Class<?>[] { DataEntry.class }, new Class<?>[] {}), false,
false);
addRepositoryAction(StoreProcessAction.class, new RepositoryActionConditionImplStandard(
new Class<?>[] { ProcessEntry.class, Folder.class }, new Class<?>[] {}), false, false);
addRepositoryAction(RenameRepositoryEntryAction.class,
new RepositoryActionConditionImplStandardNoRepository(new Class<?>[] { Entry.class }, new Class<?>[] {}),
false, false);
addRepositoryAction(CreateFolderAction.class,
new RepositoryActionConditionImplStandard(new Class<?>[] { Folder.class }, new Class<?>[] {}), false, false);
addRepositoryAction(CutEntryRepositoryAction.class,
new RepositoryActionConditionImplStandardNoRepository(new Class<?>[] {}, new Class<?>[] {}), true, false);
addRepositoryAction(CopyEntryRepositoryAction.class,
new RepositoryActionConditionImplStandard(new Class<?>[] {}, new Class<?>[] {}), false, false);
addRepositoryAction(PasteEntryRepositoryAction.class,
new RepositoryActionConditionImplStandard(new Class<?>[] {}, new Class<?>[] {}), false, false);
addRepositoryAction(CopyLocationAction.class,
new RepositoryActionConditionImplStandard(new Class<?>[] {}, new Class<?>[] {}), false, false);
addRepositoryAction(DeleteRepositoryEntryAction.class,
new RepositoryActionConditionImplStandard(new Class<?>[] { Entry.class }, new Class<?>[] {}), false, false);
addRepositoryAction(RefreshRepositoryEntryAction.class,
new RepositoryActionConditionImplStandard(new Class<?>[] { Entry.class }, new Class<?>[] {}), true, false);
addRepositoryAction(OpenInFileBrowserAction.class, new RepositoryActionConditionImplStandard(
new Class<?>[] { Entry.class }, new Class<?>[] { LocalRepository.class }), false, false);
}
public RepositoryTree() {
this(null);
}
public RepositoryTree(Dialog owner) {
this(owner, false);
}
public RepositoryTree(Dialog owner, boolean onlyFolders) {
this(owner, onlyFolders, false, true);
}
public RepositoryTree(Dialog owner, boolean onlyFolders, boolean onlyWritableRepositories) {
this(owner, onlyFolders, onlyWritableRepositories, true);
}
public RepositoryTree(Dialog owner, boolean onlyFolders, boolean onlyWritableRepositories, boolean installDraghandler) {
this(owner, onlyFolders, onlyWritableRepositories, installDraghandler, null);
}
/**
* @param installDraghandler
* when true, the {@link RepositoryTreeTransferhandler} is installed and the user is
* able to drag/drop data.
* @param backgroundColor
* if {@code null} the default background color will be used, otherwise the provided
* background color will be used
*/
public RepositoryTree(Dialog owner, boolean onlyFolders, boolean onlyWritableRepositories, boolean installDraghandler,
final Color backgroundColor) {
super(new RepositoryTreeModel(RepositoryManager.getInstance(null), onlyFolders, onlyWritableRepositories));
this.owner = owner;
((RepositoryTreeModel) getModel()).setParentTree(this);
// these actions are a) needed for the action map or b) needed by other classes for toolbars
// etc
listToEnable.add(DELETE_ACTION);
listToEnable.add(RENAME_ACTION);
listToEnable.add(REFRESH_ACTION);
listToEnable.add(OPEN_ACTION);
listToEnable.add(CREATE_FOLDER_ACTION);
RENAME_ACTION.addToActionMap(this, WHEN_FOCUSED);
DELETE_ACTION.addToActionMap(this, "delete", WHEN_FOCUSED);
REFRESH_ACTION.addToActionMap(this, WHEN_FOCUSED);
setLargeModel(true);
setRowHeight(Math.max(TREE_ROW_HEIGHT, getRowHeight()));
setRootVisible(false);
setShowsRootHandles(true);
if (backgroundColor != null) {
// in case of a custom background color we need to overwrite the getBackground() method
// as the constructor of DefaultTreeCellRenderer uses the method to query the background
// image. Thus we cannot provide the color within the RepositoryTreeCellRenderer
// constructor.
setCellRenderer(new RepositoryTreeCellRenderer() {
private static final long serialVersionUID = 1L;
@Override
public Color getBackground() {
return backgroundColor;
};
@Override
public Color getBackgroundNonSelectionColor() {
return backgroundColor;
};
});
} else {
setCellRenderer(new RepositoryTreeCellRenderer());
}
getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
if (e.getSource() instanceof JTree) {
JTree jtree = (JTree) e.getSource();
jtree.repaint();
}
}
});
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
mouseClickPressReleasePopup(e);
// Doubleclick
if (getSelectionCount() == 1) {
if (e.getClickCount() == 2) {
TreePath path = getSelectionPath();
if (path == null) {
return;
}
fireLocationSelected((Entry) path.getLastPathComponent());
}
}
}
@Override
public void mousePressed(MouseEvent e) {
mouseClickPressReleasePopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
mouseClickPressReleasePopup(e);
}
private void mouseClickPressReleasePopup(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
int mouseRow = getRowForLocation(e.getX(), e.getY());
// Mouse is not over row -> Remove current selection
if (mouseRow == -1) {
setSelectionInterval(mouseRow, mouseRow);
}
// No multiple row selection -> Update selected element
if (getSelectionCount() <= 1) {
setSelectionInterval(mouseRow, mouseRow);
}
// Multiple rows selected -> Update if mouse over other element
if (getSelectionCount() > 1) {
boolean inMultiRow = false;
for (int selectedRow : getSelectionRows()) {
if (selectedRow == mouseRow) {
inMultiRow = true;
}
}
if (!inMultiRow) {
setSelectionInterval(mouseRow, mouseRow);
}
}
// Finally show popup
if (e.isPopupTrigger()) {
showPopup(e);
}
}
}
});
addKeyListener(new KeyListener() {
// status variable to fix bug 987
private int lastPressedKey;
@Override
public void keyTyped(KeyEvent e) {}
/**
* Opens entries on enter pressed; collapses/expands folders
*/
@Override
public void keyReleased(KeyEvent e) {
if (lastPressedKey != e.getKeyCode()) {
e.consume();
return;
}
lastPressedKey = 0;
if (e.getModifiers() == 0) {
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
case KeyEvent.VK_SPACE:
TreePath path = getSelectionPath();
if (path == null) {
return;
}
Entry entry = (Entry) path.getLastPathComponent();
if (entry instanceof Folder) {
if (isExpanded(path)) {
collapsePath(path);
} else {
expandPath(path);
}
} else {
fireLocationSelected((Entry) path.getLastPathComponent());
}
e.consume();
break;
}
}
}
@Override
public void keyPressed(KeyEvent e) {
lastPressedKey = e.getKeyCode();
}
});
if (installDraghandler) {
setDragEnabled(true);
setTransferHandler(new RepositoryTreeTransferhandler());
}
getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
@Override
public void valueChanged(TreeSelectionEvent e) {
enableActions();
}
});
addTreeExpansionListener(new TreeExpansionListener() {
@Override
public void treeExpanded(TreeExpansionEvent event) {
// select the last expanded/collapsed path
selectionModel.setSelectionPath(event.getPath());
}
@Override
public void treeCollapsed(TreeExpansionEvent event) {
// select the last expanded/collapsed path
treeExpanded(event);
}
});
enableActions();
new ToolTipWindow(owner, new TipProvider() {
@Override
public String getTip(Object o) {
if (o instanceof Entry) {
return ToolTipProviderHelper.getTip((Entry) o);
} else {
return null;
}
}
@Override
public Object getIdUnder(Point point) {
TreePath path = getPathForLocation((int) point.getX(), (int) point.getY());
if (path != null) {
return path.getLastPathComponent();
} else {
return null;
}
}
@Override
public Component getCustomComponent(Object o) {
if (o instanceof Entry) {
return ToolTipProviderHelper.getCustomComponent((Entry) o);
} else {
return null;
}
}
}, this, TooltipLocation.RIGHT);
if (backgroundColor != null) {
setBackground(backgroundColor);
}
}
public void enableActions() {
listToEnable.forEach(AbstractRepositoryAction::enable);
}
public void addRepositorySelectionListener(RepositorySelectionListener listener) {
listenerList.add(RepositorySelectionListener.class, listener);
}
public void removeRepositorySelectionListener(RepositorySelectionListener listener) {
listenerList.remove(RepositorySelectionListener.class, listener);
}
/**
* Adds a {@link RepositorySortingMethodListener}
*
* @since 7.4
*/
public void addRepostorySortingMethodListener(RepositorySortingMethodListener l) {
listenerList.add(RepositorySortingMethodListener.class, l);
}
/**
* Removes a {@link RepositorySortingMethodListener}
*
* @since 7.4
*/
public void removeRepostorySortingMethodListener(RepositorySortingMethodListener l) {
listenerList.remove(RepositorySortingMethodListener.class, l);
}
private void fireLocationSelected(Entry entry) {
RepositorySelectionEvent event = null;
for (RepositorySelectionListener l : listenerList.getListeners(RepositorySelectionListener.class)) {
if (event == null) {
event = new RepositorySelectionEvent(entry);
}
l.repositoryLocationSelected(event);
}
}
/**
* Selects as much as possible of the selected path to the given location. Returns true if the
* given location references a folder.
*/
boolean expandIfExists(RepositoryLocation relativeTo, String location) {
RepositoryLocation loc;
boolean full = true;
if (location != null) {
try {
if (relativeTo != null) {
loc = new RepositoryLocation(relativeTo, location);
} else {
loc = new RepositoryLocation(location + "/");
}
} catch (Exception e) {
// do nothing
return false;
}
} else {
loc = relativeTo;
}
if (loc == null) {
return false;
}
Entry entry = null;
while (true) {
try {
entry = loc.locateEntry();
if (entry != null) {
break;
}
} catch (RepositoryException e) {
return false;
}
loc = loc.parent();
if (loc == null) {
return false;
}
full = false;
}
if (entry != null) {
RepositoryTreeModel model = (RepositoryTreeModel) getModel();
TreePath pathTo = model.getPathTo(entry);
expandPath(pathTo);
setSelectionPath(pathTo);
if (entry instanceof Folder) {
return full;
}
}
return false;
// loc = loc.parent();
}
/**
* Expands the tree to select the given entry if it exists.
*/
public void expandAndSelectIfExists(RepositoryLocation location) {
if (location.parent() != null) {
expandIfExists(location.parent(), location.getName());
} else {
expandIfExists(location, null);
}
scrollPathToVisible(getSelectionPath());
}
private void showPopup(MouseEvent e) {
TreePath[] paths = getSelectionPaths();
JPopupMenu menu = new JPopupMenu();
if (paths == null || paths.length < 1) {
return;
} else if (paths.length == 1) {
// Exactly one item selected
Object component = paths[0].getLastPathComponent();
// Add actions
List<Entry> entryList = new ArrayList<>(1);
if (component instanceof Entry) {
entryList.add((Entry) component);
}
List<Action> actionList = createContextMenuActions(this, entryList);
// Go through ordered list of actions and add them
for (Action action : actionList) {
if (action == null) {
menu.addSeparator();
} else {
menu.add(action);
}
}
// Append custom actions if there are any
if (component instanceof Entry) {
Collection<Action> customActions = ((Entry) component).getCustomActions();
if (customActions != null && !customActions.isEmpty()) {
menu.addSeparator();
for (Action customAction : customActions) {
menu.add(customAction);
}
}
}
} else {
// Multiple items selected
// Get all selected entries
List<Entry> selectedEntries = new LinkedList<>();
for (TreePath path : paths) {
Object component = path.getLastPathComponent();
if (component instanceof Entry) {
selectedEntries.add((Entry) component);
}
}
// Get possible actions of selected entries
List<RepositoryActionEntry> evaluatedActions = new LinkedList<>(REPOSITORY_MULTIPLE_ENTRIES_ACTIONS);
for (int i = 0; i < REPOSITORY_MULTIPLE_ENTRIES_ACTIONS.size(); i++) {
RepositoryActionEntry actionEntry = REPOSITORY_MULTIPLE_ENTRIES_ACTIONS.get(i);
if (!actionEntry.getRepositoryActionCondition().evaluateCondition(selectedEntries)) {
evaluatedActions.remove(actionEntry);
}
}
// Add actions
boolean lastWasSeparator = true;
for (RepositoryActionEntry actionEntry : evaluatedActions) {
try {
Constructor<? extends AbstractRepositoryAction<?>> constructor = actionEntry.getRepositoryActionClass()
.getConstructor(new Class[] { RepositoryTree.class });
AbstractRepositoryAction<?> createdAction = constructor.newInstance(this);
createdAction.enable();
if (actionEntry.hasSeparatorBefore && !lastWasSeparator) {
menu.addSeparator();
}
menu.add(createdAction);
if (actionEntry.hasSeparatorAfter) {
menu.addSeparator();
lastWasSeparator = true;
} else {
lastWasSeparator = false;
}
} catch (Exception ex) {
LogService.getRoot().log(Level.SEVERE,
"com.rapidminer.repository.gui.RepositoryTree.creating_repository_action_error",
actionEntry.getRepositoryActionClass());
}
}
}
menu.show(this, e.getX(), e.getY());
}
/** Opens the process held by the given entry (in the background) and opens it. */
public static void openProcess(final ProcessEntry processEntry) {
RepositoryProcessLocation processLocation = new RepositoryProcessLocation(processEntry.getLocation());
if (RapidMinerGUI.getMainFrame().close()) {
OpenAction.open(processLocation, true);
}
/*
* PRE FIX OF BUG 308: When opening process with double click all changes are discarded
* ProgressThread openProgressThread = new ProgressThread("open_process") {
*
* @Override public void run() { try { RepositoryProcessLocation processLocation = new
* RepositoryProcessLocation(processEntry.getLocation()); String xml =
* processEntry.retrieveXML(); try { final Process process = new Process(xml);
* process.setProcessLocation(processLocation); SwingUtilities.invokeLater(new Runnable() {
* public void run() { RapidMinerGUI.getMainFrame().setOpenedProcess(process, true,
* processEntry.getLocation().toString()); } }); } catch (Exception e) {
* RapidMinerGUI.getMainFrame().handleBrokenProxessXML(processLocation, xml, e); } } catch
* (Exception e1) { SwingTools.showSimpleErrorMessage("cannot_fetch_data_from_repository",
* e1); }
*
* } }; openProgressThread.start();
*/
}
public Entry getSelectedEntry() {
TreePath path = getSelectionPath();
if (path == null) {
return null;
}
Object selected = path.getLastPathComponent();
if (selected instanceof Entry) {
return (Entry) selected;
} else {
return null;
}
}
/**
* Gets list of selected entries of this tree
*/
public List<Entry> getSelectedEntries() {
List<Entry> selectedEntries = new LinkedList<>();
TreePath[] paths = getSelectionPaths();
if (paths != null) {
for (TreePath treePath : paths) {
Object selected = treePath.getLastPathComponent();
if (selected instanceof Entry) {
selectedEntries.add((Entry) selected);
}
}
}
return selectedEntries;
}
public Collection<AbstractRepositoryAction<?>> getAllActions() {
List<AbstractRepositoryAction<?>> listOfAbstractRepositoryActions = new LinkedList<>();
for (Action action : createContextMenuActions(this, new LinkedList<Entry>())) {
if (action instanceof AbstractRepositoryAction<?>) {
listOfAbstractRepositoryActions.add((AbstractRepositoryAction<?>) action);
}
}
return listOfAbstractRepositoryActions;
}
/**
* Appends the given {@link AbstractRepositoryAction} extending class to the popup menu actions.
* <p>
* The class <b>MUST</b> have one public constructor taking only a RepositoryTree. </br>
* Example: public MyNewRepoAction(RepositoryTree tree) { ... } </br>
* Otherwise creating the action via reflection will fail.
*
* @param actionClass
* the class extending {@link AbstractRepositoryAction}
* @param condition
* the {@link RepositoryActionCondition} which determines on which selected entries
* the action will be visible.
* @param hasSeparatorBefore
* if true, a separator will be added before the action
* @param hasSeparatorAfter
* if true, a separator will be added after the action
* @return true if the action was successfully added; false otherwise
*/
public static void addRepositoryAction(Class<? extends AbstractRepositoryAction<?>> actionClass,
RepositoryActionCondition condition, boolean hasSeparatorBefore, boolean hasSeparatorAfter) {
addRepositoryAction(actionClass, condition, null, hasSeparatorBefore, hasSeparatorAfter);
}
/**
* Adds the given {@link AbstractRepositoryAction} extending class to the popup menu actions at
* the given index.
* <p>
* The class <b>MUST</b> have one public constructor taking only a RepositoryTree. </br>
* Example: public MyNewRepoAction(RepositoryTree tree) { ... } </br>
* Otherwise creating the action via reflection will fail.
*
* @param actionClass
* the class extending {@link AbstractRepositoryAction}
* @param condition
* the {@link RepositoryActionCondition} which determines on which selected entries
* the action will be visible.
* @param insertAfterThisAction
* the class of the action after which the new action should be inserted. Set to
* {@code null} to append the action at the end.
* @param hasSeparatorBefore
* if true, a separator will be added before the action
* @param hasSeparatorAfter
* if true, a separator will be added after the action
* @return true if the action was successfully added; false otherwise
*/
public static void addRepositoryAction(Class<? extends AbstractRepositoryAction<?>> actionClass,
RepositoryActionCondition condition, Class<? extends Action> insertAfterThisAction, boolean hasSeparatorBefore,
boolean hasSeparatorAfter) {
if (actionClass == null || condition == null) {
throw new IllegalArgumentException("actionClass and condition must not be null!");
}
RepositoryActionEntry newEntry = new RepositoryActionEntry(actionClass, condition, hasSeparatorBefore,
hasSeparatorAfter);
if (insertAfterThisAction == null) {
REPOSITORY_ACTIONS.add(newEntry);
} else {
// searching for class to insert after
boolean inserted = false;
int i = 0;
for (RepositoryActionEntry entry : REPOSITORY_ACTIONS) {
Class<? extends Action> existingAction = entry.getRepositoryActionClass();
if (existingAction.equals(insertAfterThisAction)) {
REPOSITORY_ACTIONS.add(i + 1, newEntry);
inserted = true;
break;
}
i++;
}
// if reference couldn't be found: just add as last
if (!inserted) {
REPOSITORY_ACTIONS.add(newEntry);
}
}
// add action instances for multiple entires
if (multipleMenuEntriesActionClasses.contains(actionClass.getName())) {
REPOSITORY_MULTIPLE_ENTRIES_ACTIONS.add(newEntry);
}
}
/**
* Removes the given action from the popup menu actions.
*
* @param actionClass
* the class of the {@link AbstractRepositoryAction} to remove
*/
public static void removeRepositoryAction(Class<? extends AbstractRepositoryAction<?>> actionClass) {
Iterator<RepositoryActionEntry> iterator = REPOSITORY_ACTIONS.iterator();
while (iterator.hasNext()) {
if (iterator.next().getRepositoryActionClass().equals(actionClass)) {
iterator.remove();
}
}
}
/**
* This method returns a list of actions shown in the context menu if the given
* {@link RepositoryActionCondition} is true. Contains {@code null} elements for each separator.
* This method is called by each {@link RepositoryTree} instance during construction time and
* creates instances via reflection of all registered acionts. See
* {@link #addRepositoryAction(Class, RepositoryActionCondition, Class, boolean, boolean)} to
* add actions.
*/
private static List<Action> createContextMenuActions(RepositoryTree repositoryTree, List<Entry> entryList) {
List<Action> listOfActions = new LinkedList<>();
boolean lastWasSeparator = true;
for (RepositoryActionEntry actionEntry : REPOSITORY_ACTIONS) {
try {
if (actionEntry.getRepositoryActionCondition().evaluateCondition(entryList)) {
if (!lastWasSeparator && actionEntry.hasSeperatorBefore()) {
// add null element which means a separator will be added in the menu
listOfActions.add(null);
}
Constructor<? extends AbstractRepositoryAction<?>> constructor = actionEntry.getRepositoryActionClass()
.getConstructor(new Class[] { RepositoryTree.class });
AbstractRepositoryAction<?> createdAction = constructor.newInstance(repositoryTree);
createdAction.enable();
listOfActions.add(createdAction);
if (actionEntry.hasSeperatorAfter()) {
listOfActions.add(null);
lastWasSeparator = true;
} else {
lastWasSeparator = false;
}
}
} catch (Exception e) {
LogService.getRoot().log(Level.SEVERE,
"com.rapidminer.repository.gui.RepositoryTree.creating_repository_action_error",
actionEntry.getRepositoryActionClass());
}
}
return listOfActions;
}
/**
* Sets the {@link RepositorySortingMethod} with which the {@link RepositoryTreeModel} is sorted
*
* @param method
* The {@link RepositorySortingMethod}
* @since 7.4
*/
public void setSortingMethod(RepositorySortingMethod method) {
if (getModel() instanceof RepositoryTreeModel) {
// Remember expansion state before setting new RepositorySortingMethod
Enumeration<TreePath> expandedDescendants = getExpandedDescendants(new TreePath(treeModel.getRoot()));
List<TreePath> oldExpanded = new ArrayList<>();
if (expandedDescendants != null) {
while (expandedDescendants.hasMoreElements()) {
oldExpanded.add(expandedDescendants.nextElement());
}
}
((RepositoryTreeModel) getModel()).setSortingMethod(method);
for (TreePath oldEx : oldExpanded) {
setExpandedState(oldEx, true);
}
for (RepositorySortingMethodListener l : listenerList.getListeners(RepositorySortingMethodListener.class)) {
l.changedRepositorySortingMethod(method);
}
}
}
/**
* Gets the {@link RepositorySortingMethod} with which this {@link RepositoryTreeModel} is
* sorted
*
* @since 7.4
*/
public RepositorySortingMethod getSortingMethod() {
if (getModel() instanceof RepositoryTreeModel) {
return ((RepositoryTreeModel) getModel()).getSortingMethod();
}
return null;
}
}