blob: f977bb0b8f5ca19623e7345d4dd378c0d51d94d3 [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.core.execution;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.Introspector;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.text.DefaultEditorKit;
import org.netbeans.TopSecurityManager;
import org.netbeans.core.ModuleActions;
import org.netbeans.modules.progress.spi.Controller;
import org.netbeans.modules.progress.spi.InternalHandle;
import org.openide.DialogDescriptor;
import org.openide.DialogDisplayer;
import org.openide.awt.Mnemonics;
import org.openide.execution.ExecutorTask;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.ListView;
import org.openide.modules.OnStart;
import org.openide.modules.OnStop;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.NodeEvent;
import org.openide.nodes.NodeListener;
import org.openide.nodes.NodeMemberEvent;
import org.openide.nodes.NodeReorderEvent;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.actions.SystemAction;
/**
* Registers security manager for execution.
* Also shows Pending Tasks dialog at shutdown time.
* Also adds/removes specific beaninfo and property editor search paths.
* @author Jesse Glick
*/
@OnStart
public class Install implements Runnable {
private static final Logger LOG = Logger.getLogger(Install.class.getName());
public @Override void run() {
TopSecurityManager.register(SecMan.DEFAULT);
}
@OnStop
public static final class Down implements Runnable {
public @Override void run() {
showPendingTasks();
TopSecurityManager.unregister(SecMan.DEFAULT);
}
}
@OnStop
public static final class Closing implements Callable {
@Override
public Boolean call() throws Exception {
return showPendingTasks();
}
}
/** A class that server as a pending dialog manager.
* It closes the dialog if there are no more pending tasks
* and also servers as the action listener for killing the tasks.
*/
private static class PendingDialogCloser extends WindowAdapter implements Runnable,
PropertyChangeListener, ActionListener, NodeListener {
private Dialog[] dialogHolder;
private Object exitOption;
PendingDialogCloser(Dialog[] holder, Object exit) {
dialogHolder = holder;
exitOption = exit;
}
public void run() {
dialogHolder[0].setVisible(false);
}
// Beware: this may be called also from rootNode's prop changes
// Once all pending tasks are gone, close the dialog.
public void propertyChange(PropertyChangeEvent evt) {
if(ExplorerManager.PROP_EXPLORED_CONTEXT.equals(evt.getPropertyName())) {
checkClose();
}
}
// kill pending tasks and close the dialog
public void actionPerformed(ActionEvent evt) {
if(evt.getSource() == exitOption) {
killPendingTasks();
Mutex.EVENT.readAccess(this); // close in AWT
}
}
public void childrenRemoved(NodeMemberEvent evt) {
checkClose();
}
// Dialog was opened but pending tasks could disappear inbetween.
public @Override void windowOpened(WindowEvent evt) {
checkClose();
}
/** Checks if there are pending tasks and closes (in AWT)
* the dialog if not. */
private void checkClose() {
if(dialogHolder[0] != null && getPendingTasks().isEmpty()) {
Mutex.EVENT.readAccess(this);
}
}
// noop - rest of node listener
public void childrenAdded(NodeMemberEvent ev) {}
public void childrenReordered(NodeReorderEvent ev) {}
public void nodeDestroyed(NodeEvent ev) {}
}
// Remainder moved from ExitDialog:
/** Shows dialog which waits for finishing of pending tasks,
* (currently actions only) and offers to user to leave IDE
* immediatelly interrupting those tasks.
* @return <code>true</code> if to continue with the action
* <code>false</code> if the action to cancel
*/
private static boolean showPendingTasks() {
// Avoid showing the tasks in the dialog when either running internal tests
if (Boolean.getBoolean("netbeans.full.hack") // NOI18N
// or there are no pending tasks.
|| getPendingTasks().isEmpty()) {
return true;
}
EM panel = new EM();
Dialog[] dialog = new Dialog[1];
Node root = new AbstractNode(new PendingChildren());
JButton exitOption = new JButton();
Mnemonics.setLocalizedText(exitOption, NbBundle.getMessage(Install.class, "LAB_EndTasks"));
// No default button.
// exitOption.setDefaultCapable(false);
exitOption.getAccessibleContext().setAccessibleDescription(
NbBundle.getMessage(Install.class, "ACSD_EndTasks"));
PendingDialogCloser closer = new PendingDialogCloser(dialog, exitOption);
panel.getExplorerManager().setRootContext(root);
// closer will autoclose the dialog if all pending tasks finish
panel.getExplorerManager().addPropertyChangeListener(closer);
DialogDescriptor dd = new DialogDescriptor(
panel,
NbBundle.getMessage(Install.class, "CTL_PendingTitle"),
true, // modal
new Object[] {
exitOption,
DialogDescriptor.CANCEL_OPTION
},
exitOption,
DialogDescriptor.DEFAULT_ALIGN,
null,
closer
);
// #33135 - no Help button for this dialog
dd.setHelpCtx(null);
if(!getPendingTasks().isEmpty()) {
root.addNodeListener(closer);
dialog[0] = DialogDisplayer.getDefault().createDialog(dd);
dialog[0].addWindowListener(closer);
dialog[0].setVisible(true);
dialog[0].dispose();
if(dd.getValue() == DialogDescriptor.CANCEL_OPTION
|| dd.getValue() == DialogDescriptor.CLOSED_OPTION) {
return false;
}
}
return true;
}
private static class EM extends JPanel implements ExplorerManager.Provider {
private ExplorerManager manager = new ExplorerManager();
private org.openide.util.Lookup lookup;
public EM() {
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)); // or false
lookup = ExplorerUtils.createLookup(manager, map);
initComponent();
}
private void initComponent() {
setLayout(new GridBagLayout());
GridBagConstraints cons = new GridBagConstraints();
cons.gridx = 0;
cons.gridy = 0;
cons.weightx = 1.0D;
cons.fill = GridBagConstraints.HORIZONTAL;
cons.insets = new Insets(11, 11, 0, 12);
JLabel label = new JLabel();
Mnemonics.setLocalizedText(label, NbBundle.getMessage(Install.class, "LAB_PendingTasks"));
add(label, cons);
cons.gridy = 1;
cons.weighty = 1.0D;
cons.fill = GridBagConstraints.BOTH;
cons.insets = new Insets(2, 11, 0, 12);
ListView view = new ListView();
//#66881
view.setBorder(UIManager.getBorder("Nb.ScrollPane.border"));
label.setLabelFor(view);
add(view, cons);
view.getAccessibleContext().setAccessibleDescription(
NbBundle.getMessage(Install.class, "ACSD_PendingTasks"));
getAccessibleContext().setAccessibleDescription(
NbBundle.getMessage(Install.class, "ACSD_PendingTitle"));
// set size requested by HIE guys
Dimension origSize = getPreferredSize();
setPreferredSize(new Dimension(origSize.width * 5 / 4, origSize.height));
}
public ExplorerManager getExplorerManager() {
return manager;
}
public Lookup getLookup() {
return lookup;
}
public @Override void addNotify() {
super.addNotify();
ExplorerUtils.activateActions(manager, true);
}
public @Override void removeNotify() {
ExplorerUtils.activateActions(manager, false);
super.removeNotify();
}
}
/** Gets pending (running) tasks. Used as keys
* for pending dialog root node children. Currently it gets pending
* actions only. */
static Collection<?> getPendingTasks() {
ArrayList<Object> pendingTasks = new ArrayList<Object>( 10 ); // Action | ExecutorTask | InternalHandle
pendingTasks.addAll(ModuleActions.getDefaultInstance().getRunningActions());
ExecutionEngine ee = ExecutionEngine.getExecutionEngine();
if (ee != null) {
pendingTasks.addAll(ee.getRunningTasks());
}
pendingTasks.addAll(Arrays.asList(Controller.getDefault().getModel().getHandles()));
// [PENDING] When it'll be added another types of tasks (locks etc.)
// add them here to the list. Then you need to create also a nodes
// for them in PendingChildren.createNodes.
return pendingTasks;
}
/** Ends penidng tasks. */
private static void killPendingTasks() {
// no way to kill actions
final LogRecord r = new LogRecord(Level.INFO, "KILL_PENDING_TASKS"); //NOI18N
r.setLoggerName(LOG.getName());
LOG.log(r);
for (InternalHandle h : Controller.getDefault().getModel().getHandles()) {
h.requestCancel();
}
killRunningExecutors();
// [PENDING] When it'll be added another types of tasks (locks etc.)
// kill them here.
}
/** Tries to kill running executions */
private static void killRunningExecutors() {
ExecutionEngine ee = ExecutionEngine.getExecutionEngine();
if (ee == null) {
return;
}
for (ExecutorTask et : new ArrayList<ExecutorTask>(ee.getRunningTasks())) {
if ( !et.isFinished() ) {
et.stop();
}
}
}
/** Children showing pending tasks. */
/* non private because of tests - was private before */
static class PendingChildren extends Children.Keys<Object /* Action|ExecutorTask|InternalHandle*/>
implements ExecutionListener, ListDataListener {
/** Listens on changes of sources from getting the tasks from.
* Currently on module actions only. */
// private PropertyChangeListener propertyListener;
/** Constructs new children. */
public PendingChildren() {
/* XXX no equiv yet in CallableSystemAction
propertyListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (ModuleActions.PROP_RUNNING_ACTIONS.equals(evt.getPropertyName())) {
setKeys(getPendingTasks());
}
}
};
ModuleActions.getDefault().addPropertyChangeListener(
org.openide.util.WeakListeners.propertyChange (propertyListener, ModuleActions.getDefault())
);
*/
ExecutionEngine ee = ExecutionEngine.getExecutionEngine();
if (ee != null) {
ee.addExecutionListener(this);
}
Controller.getDefault().getModel().addListDataListener(this);
}
/** Implements superclass abstract method. Creates nodes from key.
* @return <code>PendingActionNode</code> if key is of
* <code>Action</code> type otherwise <code>null</code> */
protected Node[] createNodes(Object key) {
Node n = null;
if(key instanceof Action) {
Action action = (Action)key;
Icon icon = (action instanceof SystemAction) ?
((SystemAction)action).getIcon() : null;
String actionName = (String)action.getValue(Action.NAME);
if (actionName == null) actionName = ""; // NOI18N
actionName = org.openide.awt.Actions.cutAmpersand(actionName);
n = new NoActionNode(icon, actionName, NbBundle.getMessage(
Install.class, "CTL_ActionInProgress", actionName));
} else if (key instanceof ExecutorTask) {
n = new NoActionNode(null, key.toString(),
NbBundle.getMessage(Install.class, "CTL_PendingExternalProcess2",
// getExecutionEngine() had better be non-null, since getPendingTasks gave an ExecutorTask:
ExecutionEngine.getExecutionEngine().getRunningTaskName((ExecutorTask) key))
);
} else if (key instanceof InternalHandle) {
n = new NoActionNode(null, ((InternalHandle)key).getDisplayName(), null);
}
return n == null ? null : new Node[] { n };
}
protected @Override void addNotify() {
setKeys(getPendingTasks());
super.addNotify();
}
protected @Override void removeNotify() {
setKeys(Collections.emptySet());
super.removeNotify();
ExecutionEngine ee = ExecutionEngine.getExecutionEngine();
if (ee != null) {
ee.removeExecutionListener(this);
}
Controller.getDefault().getModel().removeListDataListener(this);
}
// ExecutionListener implementation ------------------------------------
public void startedExecution( ExecutionEvent ev ) {
setKeys(getPendingTasks());
}
public void finishedExecution( ExecutionEvent ev ) {
setKeys(getPendingTasks());
}
public void intervalAdded(ListDataEvent e) {
setKeys(getPendingTasks());
}
public void intervalRemoved(ListDataEvent e) {
setKeys(getPendingTasks());
}
public void contentsChanged(ListDataEvent e) {
setKeys(getPendingTasks());
}
} // End of class PendingChildren.
/** Node without any actions. */
private static class NoActionNode extends AbstractNode {
private Image img;
/** Creates node for action. */
public NoActionNode(Icon icon, String name, String display) {
super(Children.LEAF);
if (icon != null) img = ImageUtilities.icon2Image(icon);
setName(name);
if (display != null) setDisplayName(display);
}
/** @return empty array of actions */
public @Override Action[] getActions(boolean context) {
return new Action[0];
}
/** @return provided icon or delegate to superclass if no icon provided.
*/
public @Override Image getIcon(int type) {
return img == null ? super.getIcon(type) : img;
}
}
}