blob: caeb72a492dbe20816d24d6f0648a4d7bf363da6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.project.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.BeanInfo;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.DefaultEditorKit;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.modules.project.ui.groups.Group;
import org.openide.DialogDisplayer;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.awt.StatusDisplayer;
import org.openide.awt.UndoRedo;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.BeanTreeView;
import org.openide.explorer.view.Visualizer;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.NodeNotFoundException;
import org.openide.nodes.NodeOp;
import org.openide.util.Exceptions;
import org.openide.util.HelpCtx;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbCollections;
import org.openide.util.NbPreferences;
import org.openide.util.RequestProcessor;
import org.openide.util.RequestProcessor.Task;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.actions.BooleanStateAction;
import org.openide.util.lookup.Lookups;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
/** TopComponment for viewing open projects.
* <P>
* PENEDING : Fix persistence when new Winsys allows
*
* @author Petr Hrebejk
*/
public class ProjectTab extends TopComponent
implements ExplorerManager.Provider, PropertyChangeListener, UndoRedo.Provider {
public static final String ID_LOGICAL = "projectTabLogical_tc"; // NOI18N
public static final String ID_PHYSICAL = "projectTab_tc"; // NOI18N
private static final @StaticResource String PROJECT_TAB = "org/netbeans/modules/project/ui/resources/projectTab.png";
private static final @StaticResource String FILES_TAB = "org/netbeans/modules/project/ui/resources/filesTab.png";
private static final Image ICON_LOGICAL = ImageUtilities.loadImage( PROJECT_TAB);
private static final Image ICON_PHYSICAL = ImageUtilities.loadImage( FILES_TAB);
private static final Logger LOG = Logger.getLogger(ProjectTab.class.getName());
private static Map<String, ProjectTab> tabs = new HashMap<String, ProjectTab>();
private final transient ExplorerManager manager;
private transient Node rootNode;
private String id;
private final transient ProjectTreeView btv;
private final JLabel noProjectsLabel = new JLabel(NbBundle.getMessage(ProjectTab.class, "NO_PROJECT_OPEN"));
private boolean synchronizeViews = false;
private FileObject objectToSelect;
private boolean prompt;
private Task selectionTask;
private static final int NODE_SELECTION_DELAY = 200;
private final NodeSelectionProjectPanel nodeSelectionProjectPanel;
public ProjectTab( String id ) {
this();
this.id = id;
initValues();
}
public ProjectTab() {
// See #36315
manager = new ExplorerManager();
ActionMap map = getActionMap();
map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(manager));
map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(manager));
map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(manager));
map.put("delete", ExplorerUtils.actionDelete(manager, true));
initComponents();
btv = new ProjectTreeView(); // Add the BeanTreeView
btv.setDragSource (true);
btv.setRootVisible(false);
add( btv, BorderLayout.CENTER );
OpenProjects.getDefault().addPropertyChangeListener(this);
noProjectsLabel.addMouseListener(new LabelPopupDisplayer(noProjectsLabel));
noProjectsLabel.setHorizontalAlignment(SwingConstants.CENTER);
noProjectsLabel.setEnabled(false);
Color usualWindowBkg = UIManager.getColor("window"); // NOI18N
if( null != usualWindowBkg ) {
noProjectsLabel.setBackground(usualWindowBkg);
noProjectsLabel.setOpaque(true);
} else {
noProjectsLabel.setOpaque(false);
}
associateLookup( ExplorerUtils.createLookup(manager, map) );
selectionTask = createSelectionTask();
Preferences nbPrefs = NbPreferences.forModule(SyncEditorWithViewsAction.class);
synchronizeViews = nbPrefs.getBoolean(SyncEditorWithViewsAction.SYNC_ENABLED_PROP_NAME, false);
nbPrefs.addPreferenceChangeListener(new NbPrefsListener());
nodeSelectionProjectPanel = new NodeSelectionProjectPanel();
ActualSelectionProject actualSelectionProject = new ActualSelectionProject(nodeSelectionProjectPanel);
manager.addPropertyChangeListener(actualSelectionProject);
btv.getViewport().addChangeListener(actualSelectionProject);
add(nodeSelectionProjectPanel, BorderLayout.SOUTH);
}
/**
* Update display to reflect {@link Group#getActiveGroup}.
* @param group current group, or null
*/
public void setGroup(Group g) {
if (id.equals(ID_LOGICAL)) {
if (g != null) {
setName(NbBundle.getMessage(ProjectTab.class, "LBL_projectTabLogical_tc_with_group", g.getName()));
} else {
setName(NbBundle.getMessage(ProjectTab.class, "LBL_projectTabLogical_tc"));
}
} else {
setName(NbBundle.getMessage(ProjectTab.class, "LBL_projectTab_tc"));
}
// Seems to be useless: setToolTipText(getName());
}
private void initValues() {
setGroup(Group.getActiveGroup());
if (id.equals(ID_LOGICAL)) {
setIcon( ICON_LOGICAL );
}
else {
setIcon( ICON_PHYSICAL );
}
if ( rootNode == null ) {
// Create the node which lists open projects
rootNode = new ProjectsRootNode(id.equals(ID_LOGICAL) ? ProjectsRootNode.LOGICAL_VIEW : ProjectsRootNode.PHYSICAL_VIEW);
}
manager.setRootContext( rootNode );
}
/** Explorer manager implementation
*/
@Override
public ExplorerManager getExplorerManager() {
return manager;
}
@Override
public UndoRedo getUndoRedo() {
final UndoRedo undoRedo = Lookups.forPath("org/netbeans/modules/refactoring").lookup(UndoRedo.class);
return undoRedo==null?UndoRedo.NONE:undoRedo;
}
/* Singleton accessor. As ProjectTab is persistent singleton this
* accessor makes sure that ProjectTab is deserialized by window system.
* Uses known unique TopComponent ID TC_ID = "projectTab_tc" to get ProjectTab instance
* from window system. "projectTab_tc" is name of settings file defined in module layer.
* For example ProjectTabAction uses this method to create instance if necessary.
*/
public static synchronized ProjectTab findDefault( String tcID ) {
ProjectTab tab = tabs.get(tcID);
if ( tab == null ) {
//If settings file is correctly defined call of WindowManager.findTopComponent() will
//call TestComponent00.getDefault() and it will set static field component.
TopComponent tc = WindowManager.getDefault().findTopComponent( tcID );
if (tc != null) {
if (!(tc instanceof ProjectTab)) {
//This should not happen. Possible only if some other module
//defines different settings file with the same name but different class.
//Incorrect settings file?
IllegalStateException exc = new IllegalStateException
("Incorrect settings file. Unexpected class returned." // NOI18N
+ " Expected:" + ProjectTab.class.getName() // NOI18N
+ " Returned:" + tc.getClass().getName()); // NOI18N
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, exc);
//Fallback to accessor reserved for window system.
tab = ProjectTab.getDefault( tcID );
}
else {
tab = (ProjectTab)tc;
}
}
else {
//This should not happen when settings file is correctly defined in module layer.
//TestComponent00 cannot be deserialized
//Fallback to accessor reserved for window system.
tab = ProjectTab.getDefault( tcID );
}
}
return tab;
}
/* Singleton accessor reserved for window system ONLY. Used by window system to create
* ProjectTab instance from settings file when method is given. Use <code>findDefault</code>
* to get correctly deserialized instance of ProjectTab */
public static synchronized ProjectTab getDefault( String tcID ) {
ProjectTab tab = tabs.get(tcID);
if ( tab == null ) {
tab = new ProjectTab( tcID );
tabs.put( tcID, tab );
}
return tab;
}
public static TopComponent getLogical() {
return getDefault( ID_LOGICAL );
}
public static TopComponent getPhysical() {
return getDefault( ID_PHYSICAL );
}
@Override
protected String preferredID () {
return id;
}
@Override
public HelpCtx getHelpCtx() {
return ExplorerUtils.getHelpCtx(
manager.getSelectedNodes(),
ID_LOGICAL.equals( id ) ? new HelpCtx( "ProjectTab_Projects" ) : new HelpCtx( "ProjectTab_Files" ) );
}
@Override
public int getPersistenceType() {
return TopComponent.PERSISTENCE_ALWAYS;
}
// APPEARANCE
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the FormEditor.
*/
private void initComponents() {//GEN-BEGIN:initComponents
setLayout(new java.awt.BorderLayout());
}//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
@Override
public boolean requestFocusInWindow() {
super.requestFocusInWindow();
return btv.requestFocusInWindow();
}
//#41258: In the SDI, requestFocus is called rather than requestFocusInWindow:
@Override
public void requestFocus() {
super.requestFocus();
btv.requestFocus();
}
// PERSISTENCE
private static final long serialVersionUID = 9374872358L;
@Override
public void writeExternal (ObjectOutput out) throws IOException {
super.writeExternal( out );
out.writeObject( id );
out.writeObject( rootNode.getHandle() );
out.writeObject( btv.getExpandedPaths() );
out.writeObject( getSelectedPaths() );
}
public @Override void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
super.readExternal( in );
id = (String)in.readObject();
rootNode = ((Node.Handle)in.readObject()).getNode();
final List<String[]> exPaths = NbCollections.checkedListByCopy((List<?>) in.readObject(), String[].class, true);
final List<String[]> selPaths = new ArrayList<String[]>();
try {
selPaths.addAll(NbCollections.checkedListByCopy((List<?>) in.readObject(), String[].class, true));
}
catch ( java.io.OptionalDataException e ) {
// Sel paths missing
}
initValues();
if (!"false".equals(System.getProperty("netbeans.keep.expansion"))) { // #55701
KeepExpansion ke = new KeepExpansion(exPaths, selPaths);
ke.task.schedule(0);
}
}
private class KeepExpansion implements Runnable {
final RequestProcessor.Task task;
final List<String[]> exPaths;
final List<String[]> selPaths;
KeepExpansion(List<String[]> exPaths, List<String[]> selPaths) {
this.exPaths = exPaths;
this.selPaths = selPaths;
this.task = RP.create(this);
}
@Override
public void run() {
try {
LOG.log(Level.FINE, "{0}: waiting for projects being open", id);
OpenProjects.getDefault().openProjects().get(10, TimeUnit.SECONDS);
} catch (TimeoutException ex) {
LOG.log(Level.FINE, "{0}: Timeout. Will retry in a second", id);
task.schedule(1000);
return;
} catch (ExecutionException ex) {
Exceptions.printStackTrace(ex);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
}
LOG.log(Level.FINE, "{0}: Checking node state", id);
for (Node n : rootNode.getChildren().getNodes()) {
if (btv.isExpanded(n)) {
LOG.log(Level.FINE, "{0}: Node {1} has been expanded. Giving up.", new Object[] {id, n});
return;
}
}
LOG.log(Level.FINE, "{0}: expanding paths", id);
btv.expandNodes(exPaths);
LOG.log(Level.FINE, "{0}: selecting paths", id);
final List<Node> selectedNodes = new ArrayList<Node>();
Node root = manager.getRootContext();
for (String[] sp : selPaths) {
LOG.log(Level.FINE, "{0}: selecting {1}", new Object[] {id, Arrays.asList(sp)});
try {
Node n = NodeOp.findPath(root, sp);
if (n != null) {
selectedNodes.add(n);
}
} catch (NodeNotFoundException x) {
LOG.log(Level.FINE, null, x);
}
}
if (!selectedNodes.isEmpty()) {
LOG.log(Level.FINE, "{0}: Switching to AWT", id);
EventQueue.invokeLater(new Runnable() {
@Override public void run() {
try {
manager.setSelectedNodes(selectedNodes.toArray(new Node[selectedNodes.size()]));
} catch (PropertyVetoException x) {
LOG.log(Level.FINE, null, x);
}
LOG.log(Level.FINE, "{0}: done.", id);
}
});
}
}
}
// MANAGING ACTIONS
@Override
protected void componentActivated() {
ExplorerUtils.activateActions(manager, true);
}
@Override
protected void componentDeactivated() {
ExplorerUtils.activateActions(manager, false);
}
// SEARCHING NODES
private static final Lookup context = Utilities.actionsGlobalContext();
private static final Lookup.Result<FileObject> foSelection = context.lookup(new Lookup.Template<FileObject>(FileObject.class));
private static final Lookup.Result<DataObject> doSelection = context.lookup(new Lookup.Template<DataObject>(DataObject.class));
private final LookupListener baseListener = new LookupListener() {
@Override
public void resultChanged(LookupEvent ev) {
if (TopComponent.getRegistry().getActivated() == ProjectTab.this) {
// Do not want to go into a loop.
return;
}
if (synchronizeViews) {
Collection<? extends FileObject> fos = foSelection.allInstances();
if (fos.size() == 1) {
selectNodeAsyncNoSelect(fos.iterator().next(), false);
} else {
Collection<? extends DataObject> dos = doSelection.allInstances();
if (dos.size() == 1) {
selectNodeAsyncNoSelect((dos.iterator().next()).getPrimaryFile(), false);
}
}
}
}
};
private final LookupListener weakListener = WeakListeners.create(LookupListener.class, baseListener, null);
private void startListening() {
foSelection.addLookupListener(weakListener);
doSelection.addLookupListener(weakListener);
baseListener.resultChanged(null);
}
private void stopListening() {
foSelection.removeLookupListener(weakListener);
doSelection.removeLookupListener(weakListener);
}
@Override
protected void componentShowing() {
super.componentShowing();
startListening();
}
@Override
protected void componentHidden() {
super.componentHidden();
stopListening();
}
public static final RequestProcessor RP = new RequestProcessor(ProjectTab.class);
public void selectNodeAsync(FileObject object) {
setCursor( Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR) );
open();
requestActive();
selectNodeAsyncNoSelect(object, true);
}
private Task createSelectionTask() {
Task task = RP.create(new Runnable() {
@Override
public void run() {
if (objectToSelect == null) {
return;
}
ProjectsRootNode root = (ProjectsRootNode) manager.getRootContext();
Node tempNode = root.findNode(objectToSelect);
if (tempNode == null) {
Project project = FileOwnerQuery.getOwner(objectToSelect);
Project found = null;
for (;;) {
if (project != null) {
for (Project p : OpenProjectList.getDefault().getOpenProjects()) {
if (p.getProjectDirectory().equals(project.getProjectDirectory())) {
found = p;
break;
}
}
}
if (found instanceof LazyProject) {
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Exceptions.printStackTrace(ex);
}
} else {
tempNode = root.findNode(objectToSelect);
break;
}
}
if (prompt && project != null && found == null) {
String message = NbBundle.getMessage(ProjectTab.class, "MSG_openProject_confirm", //NOI18N
ProjectUtils.getInformation(project).getDisplayName());
String title = NbBundle.getMessage(ProjectTab.class, "MSG_openProject_confirm_title");//NOI18N
NotifyDescriptor.Confirmation confirm =
new NotifyDescriptor.Confirmation(message, title, NotifyDescriptor.OK_CANCEL_OPTION);
DialogDisplayer.getDefault().notify(confirm);
if (confirm.getValue() == NotifyDescriptor.OK_OPTION) {
if (!OpenProjectList.getDefault().isOpen(project)) {
OpenProjects.getDefault().open(new Project[] { project }, false);
ProjectsRootNode.ProjectChildren.RP.post(new Runnable() {@Override public void run() {}}).waitFinished(); // #199669
}
tempNode = root.findNode(objectToSelect);
}
}
}
final Node selectedNode = tempNode;
// Back to AWT // Back to AWT
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
if ( selectedNode != null ) {
try {
manager.setSelectedNodes( new Node[] { selectedNode } );
btv.scrollToNode(selectedNode);
StatusDisplayer.getDefault().setStatusText( "" ); // NOI18N
}
catch ( PropertyVetoException e ) {
// Bad day node found but can't be selected
}
} else if (prompt) {
try {
manager.setSelectedNodes( new Node[] {} );
StatusDisplayer.getDefault().setStatusText(
NbBundle.getMessage( ProjectTab.class,
ID_LOGICAL.equals( id ) ? "MSG_NodeNotFound_ProjectsTab" : "MSG_NodeNotFound_FilesTab" ) ); // NOI18N
} catch (PropertyVetoException ex) {
Exceptions.printStackTrace(ex);
}
}
setCursor( null );
}
} );
}
});
return task;
}
private void selectNodeAsyncNoSelect(FileObject object, boolean prompt) {
objectToSelect = object;
this.prompt = prompt;
selectionTask.schedule(NODE_SELECTION_DELAY);
}
Node findNode(FileObject object) {
return ((ProjectsRootNode) manager.getRootContext()).findNode(object);
}
void selectNode(final Node node) {
Mutex.EVENT.writeAccess(new Runnable() {
public @Override void run() {
try {
manager.setSelectedNodes(new Node[] {node});
btv.scrollToNode(node);
} catch (PropertyVetoException e) {
// Bad day node found but can't be selected
}
}
});
}
void expandNode(Node node) {
btv.expandNode( node );
}
private List<String[]> getSelectedPaths() {
List<String[]> result = new ArrayList<String[]>();
Node root = manager.getRootContext();
for (Node n : manager.getSelectedNodes()) {
String[] path = NodeOp.createPath(n, root);
LOG.log(Level.FINE, "path from {0} to {1}: {2}", new Object[] {root, n, Arrays.asList(path)});
if (path != null) {
result.add(path);
}
}
return result;
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (OpenProjects.PROPERTY_OPEN_PROJECTS.equals(evt.getPropertyName())) {
final boolean someProjectsOpen = OpenProjects.getDefault().getOpenProjects().length > 0;
Mutex.EVENT.readAccess(new Runnable() {
public @Override void run() {
if (someProjectsOpen) {
restoreTreeView();
} else {
showNoProjectsLabel();
}
}
});
}
}
private void showNoProjectsLabel() {
if (noProjectsLabel.isShowing()) {
return;
}
remove(btv);
add(noProjectsLabel, BorderLayout.CENTER);
revalidate();
repaint();
}
private void restoreTreeView() {
if (btv.isShowing()) {
return;
}
remove(noProjectsLabel);
add(btv, BorderLayout.CENTER );
revalidate();
repaint();
}
// Private innerclasses ----------------------------------------------------
/** Extending bean treeview. To be able to persist the selected paths
*/
private class ProjectTreeView extends BeanTreeView {
public void scrollToNode(final Node n) {
// has to be delayed to be sure that events for Visualizers
// were processed and TreeNodes are already in hierarchy
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TreeNode tn = Visualizer.findVisualizer(n);
if (tn == null) {
return;
}
TreeModel model = tree.getModel();
if (!(model instanceof DefaultTreeModel)) {
return;
}
TreePath path = new TreePath(((DefaultTreeModel) model).getPathToRoot(tn));
Rectangle r = tree.getPathBounds(path);
if (r != null) {
tree.scrollRectToVisible(r);
}
}
});
}
public List<String[]> getExpandedPaths() {
List<String[]> result = new ArrayList<String[]>();
TreeNode rtn = Visualizer.findVisualizer( rootNode );
TreePath tp = new TreePath( rtn ); // Get the root
for( Enumeration exPaths = tree.getExpandedDescendants( tp ); exPaths != null && exPaths.hasMoreElements(); ) {
TreePath ep = (TreePath)exPaths.nextElement();
Node en = Visualizer.findNode( ep.getLastPathComponent() );
String[] path = NodeOp.createPath( en, rootNode );
// System.out.print("EXP "); ProjectTab.print( path );
result.add( path );
}
return result;
}
/** Expands all the paths, when exists
*/
public void expandNodes(List<String[]> exPaths) {
for (final String[] sp : exPaths) {
LOG.log(Level.FINE, "{0}: expanding {1}", new Object[] {id, Arrays.asList(sp)});
Node n;
try {
n = NodeOp.findPath(rootNode, sp);
} catch (NodeNotFoundException e) {
LOG.log(Level.FINE, "got {0}", e.toString());
n = e.getClosestNode();
}
if (n == null) { // #54832: it seems that sometimes we get unparented node
LOG.log(Level.FINE, "nothing from {0} via {1}", new Object[] {rootNode, Arrays.toString(sp)});
continue;
}
final Node leafNode = n;
EventQueue.invokeLater(new Runnable() {
public @Override void run() {
TreeNode tns[] = new TreeNode[sp.length + 1];
Node n = leafNode;
for (int i = sp.length; i >= 0; i--) {
if (n == null) {
LOG.log(Level.FINE, "lost parent node at #{0} from {1}", new Object[] {i, leafNode});
return;
}
tns[i] = Visualizer.findVisualizer(n);
n = n.getParentNode();
}
showPath(new TreePath(tns));
}
});
}
}
public void showOrHideNodeSelectionProjectPanel(final Node n, final Node sn) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TreeNode tn = Visualizer.findVisualizer(n);
TreeNode tsn = Visualizer.findVisualizer(sn);
if (tn == null || tsn == null) {
return;
}
TreeModel model = tree.getModel();
if (!(model instanceof DefaultTreeModel)) {
return;
}
TreePath path = new TreePath(((DefaultTreeModel) model).getPathToRoot(tn));
TreePath snPath = new TreePath(((DefaultTreeModel) model).getPathToRoot(tsn));
Rectangle projectNodeCoordinates = tree.getPathBounds(path);
Rectangle selectedNodeCoordinates = tree.getPathBounds(snPath);
Rectangle prjTabScrollCoordinates = tree.getVisibleRect();
//Constant 0.5 was choosed, b/c sometimes is project node partially visible
Integer projectTabTopPos = prjTabScrollCoordinates.y;
Integer projectTabBottomPos = prjTabScrollCoordinates.y + prjTabScrollCoordinates.height +
(nodeSelectionProjectPanel.isMinimized()?0:(NodeSelectionProjectPanel.COMPONENT_HEIGHT));
if (projectNodeCoordinates != null && selectedNodeCoordinates != null) {
Double projectNodePos = projectNodeCoordinates.y + (projectNodeCoordinates.height * 0.5);
Double selectedNodePos = selectedNodeCoordinates.y + (selectedNodeCoordinates.height * 0.5);
//Adding and subtacting 1 for project tab bottom y-index, b/c this index is slightly changing, when panel appears, then disappears and again appears
if ((projectTabTopPos < projectNodePos && projectTabBottomPos > projectNodePos)
|| (projectTabTopPos > selectedNodePos
|| (projectTabBottomPos < selectedNodePos
|| projectTabBottomPos + 1 < selectedNodePos
|| projectTabBottomPos - 1 < selectedNodePos))) {
nodeSelectionProjectPanel.minimize();
} else {
nodeSelectionProjectPanel.maximize();
}
} else {
nodeSelectionProjectPanel.minimize();
}
}
});
}
}
// showing popup on right click in projects tab when label <No Project Open> is shown
private class LabelPopupDisplayer extends MouseAdapter {
private Component component;
public LabelPopupDisplayer(Component comp) {
component = comp;
}
private void showPopup(int x, int y) {
Action actions[] = rootNode.getActions(false);
JPopupMenu popup = Utilities.actionsToPopup(actions, component);
popup.show(component, x, y);
}
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger() && id.equals(ID_LOGICAL)) {
showPopup(e.getX(), e.getY());
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger() && id.equals(ID_LOGICAL)) {
showPopup(e.getX(), e.getY());
}
}
}
private class NbPrefsListener implements PreferenceChangeListener {
@Override
public void preferenceChange(PreferenceChangeEvent evt) {
if (SyncEditorWithViewsAction.SYNC_ENABLED_PROP_NAME.equals(evt.getKey())) {
synchronizeViews = Boolean.parseBoolean(evt.getNewValue());
}
}
}
@ActionID(category="Project", id="org.netbeans.modules.project.ui.collapseAllNodes")
@ActionRegistration(displayName="#collapseAllNodes")
@ActionReferences({
@ActionReference(path=ProjectsRootNode.ACTIONS_FOLDER, position=1510),
@ActionReference(path=ProjectsRootNode.ACTIONS_FOLDER_PHYSICAL, position=1000)
})
@Messages("collapseAllNodes=Collapse All")
public static class CollapseAll implements ActionListener {
private final String type;
public CollapseAll(String type) {
this.type = type;
}
@Override public void actionPerformed(ActionEvent e) {
RP.post(new Runnable() {
@Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final ProjectTab tab = findDefault(type);
final Children children = tab.manager.getRootContext().getChildren();
for (Node root : children.getNodes()) {
if( tab.btv.isExpanded(root) ) {
collapseNodes(root, tab);
tab.btv.collapseNode(root);
}
}
Mutex.EVENT.writeAccess(new Runnable() {
public @Override void run() {
FileObject activeFile = null;
Iterator<TopComponent> iterator = TopComponent.getRegistry().getOpened().iterator();
while(iterator.hasNext()) {
TopComponent componentIter = iterator.next();
if(componentIter.isVisible() && componentIter.getLookup().lookup(FileObject.class) != null) {
activeFile = componentIter.getLookup().lookup(FileObject.class);
break;
}
}
if ( activeFile != null ) {
Project projectOwner = FileOwnerQuery.getOwner(activeFile);
if (projectOwner != null) {
Node projectNode = null;
for (Node node : children.getNodes(true)) {
if(projectOwner.equals(node.getLookup().lookup(Project.class))) {
projectNode = node;
break;
}
}
if (projectNode != null) {
try {
tab.manager.setSelectedNodes(new Node[] {projectNode});
tab.btv.scrollToNode(projectNode);
} catch (PropertyVetoException pve) {
Logger.getLogger(ProjectTab.class.getName()).log(Level.WARNING, null, pve);
}
}
}
}
}
});
}
});
}
});
}
private void collapseNodes(Node node, ProjectTab tab) {
if ( node.getChildren().getNodesCount() != 0 ) {
for ( Node nodeIter : node.getChildren().getNodes() ) {
if( tab.btv.isExpanded(nodeIter) ) {
collapseNodes(nodeIter, tab);
tab.btv.collapseNode(nodeIter);
}
}
}
}
}
@ActionID(category="Project", id="org.netbeans.modules.project.ui.NodeSelectionProjectAction")
@ActionRegistration(displayName="#CTL_MenuItem_NodeSelectionProjectAction", lazy = false)
@ActionReferences({
@ActionReference(path=ProjectsRootNode.ACTIONS_FOLDER, position=1550),
@ActionReference(path=ProjectsRootNode.ACTIONS_FOLDER_PHYSICAL, position=1100)
})
@Messages("CTL_MenuItem_NodeSelectionProjectAction=Show Selected Node(s) Project Owner")
public static class NodeSelectionProjectAction extends BooleanStateAction {
public NodeSelectionProjectAction() {
super();
}
@Override
public String getName() {
return NbBundle.getMessage(NodeSelectionProjectAction.class, "CTL_MenuItem_NodeSelectionProjectAction");
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean getBooleanState() {
return NodeSelectionProjectPanel.prefs.getBoolean(NodeSelectionProjectPanel.KEY_ACTUALSELECTIONPROJECT, false);
}
@Override
public HelpCtx getHelpCtx() {
return new HelpCtx(NodeSelectionProjectAction.class);
}
@Override
public void actionPerformed(ActionEvent e) {
boolean show = NodeSelectionProjectPanel.prefs.getBoolean(NodeSelectionProjectPanel.KEY_ACTUALSELECTIONPROJECT, false);
NodeSelectionProjectPanel.prefs.putBoolean(NodeSelectionProjectPanel.KEY_ACTUALSELECTIONPROJECT, !show);
}
}
@Messages({"MSG_none_node_selected=None of the nodes selected",
"MSG_nodes_from_more_projects=Selected nodes are from more than one project"})
private class ActualSelectionProject implements PropertyChangeListener, ChangeListener {
private final JPanel selectionsProjectPanel;
private JLabel actualProjectLabel;
private Node [] lastSelectedNodes;
public ActualSelectionProject(JPanel selectionsProjectPanel) {
this.selectionsProjectPanel = selectionsProjectPanel;
this.actualProjectLabel = new JLabel(Bundle.MSG_none_node_selected());
setSelectionLabelProperties(null);
this.selectionsProjectPanel.add(actualProjectLabel);
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ( evt.getPropertyName().equals("selectedNodes")
&& NodeSelectionProjectPanel.prefs.getBoolean(NodeSelectionProjectPanel.KEY_ACTUALSELECTIONPROJECT, false) ) {
performChange(lastSelectedNodes = (Node [])evt.getNewValue());
}
}
private void performChange(Node [] selectedNodes) {
String text = "";
Node projectNode = null;
if( selectedNodes != null && selectedNodes.length > 0 ) {
Node selectedNode = selectedNodes[0];
Node originallySelectedNode = selectedNodes[0];
Node rootNode = ProjectTab.this.manager.getRootContext();
while ( selectedNode.getParentNode() != null && !selectedNode.getParentNode().equals(rootNode)) {
selectedNode = selectedNode.getParentNode();
}
projectNode = selectedNode;
//Tests whether other selected items have same project owner
if( selectedNodes.length > 1 ) {
for ( int i = 1; i < selectedNodes.length; i ++) {
selectedNode = selectedNodes[i];
while ( !selectedNode.getParentNode().equals(rootNode) ) {
selectedNode = selectedNode.getParentNode();
}
if ( !projectNode.equals(selectedNode) ) {
projectNode = null;
text = Bundle.MSG_nodes_from_more_projects();
break;
}
}
}
if ( projectNode != null ) {
ProjectTab.this.btv.showOrHideNodeSelectionProjectPanel(projectNode, originallySelectedNode);
text = projectNode.getDisplayName();
}
} else {
text = Bundle.MSG_none_node_selected();
}
if ( this.actualProjectLabel != null ) {
this.actualProjectLabel.setText(text);
setSelectionLabelProperties(projectNode);
} else {
this.actualProjectLabel = new JLabel(text);
setSelectionLabelProperties(projectNode);
this.selectionsProjectPanel.add(actualProjectLabel);
}
}
private void setSelectionLabelProperties( Node projectNode ) {
if ( projectNode != null ) {
this.actualProjectLabel.setIcon(ImageUtilities.image2Icon(projectNode.getIcon(BeanInfo.ICON_COLOR_16x16)));
} else {
this.actualProjectLabel.setIcon(null);
}
this.actualProjectLabel.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 0));
}
@Override
public void stateChanged(ChangeEvent evt) {
if ( NodeSelectionProjectPanel.prefs.getBoolean(NodeSelectionProjectPanel.KEY_ACTUALSELECTIONPROJECT, false) ) {
performChange(lastSelectedNodes);
}
}
}
}