| /* |
| * 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; |
| } |
| } |
| } |