All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.explorer.ExplorerActions Maven / Gradle / Ivy

The newest version!
/*
 * 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.openide.explorer;

import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.actions.CopyAction;
import org.openide.actions.CutAction;
import org.openide.actions.DeleteAction;
import org.openide.actions.PasteAction;
import org.openide.filesystems.FileSystem;
import org.openide.nodes.Node;
import org.openide.util.Mutex;
import org.openide.util.NbBundle;
import org.openide.util.actions.ActionPerformer;
import org.openide.util.datatransfer.*;
import java.awt.datatransfer.*;
import java.awt.event.*;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import java.io.IOException;
import java.text.MessageFormat;

import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.*;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;


/**
 * This class contains the default implementation of reactions to the standard
 * explorer actions. It can be attached to any {@link ExplorerManager}. Then
 * this class will listen to changes of selected nodes or the explored context
 * of that manager, and update the state of cut/copy/paste/delete actions.  

* An instance of this class can only be attached to one manager at a time. Use * {@link #attach} and {@link #detach} to make the connection. * * @deprecated Use {@link ExplorerUtils#actionCopy}, etc, see {@link ExplorerUtils} javadoc * for details * @author Jan Jancura, Petr Hamernik, Ian Formanek, Jaroslav Tulach */ @Deprecated public class ExplorerActions { /** actions to work with */ private static CopyAction copy = null; private static CutAction cut = null; private static DeleteAction delete = null; private static PasteAction paste = null; /** copy action performer */ private final CopyCutActionPerformer copyActionPerformer = new CopyCutActionPerformer(true); /** cut action performer */ private final CopyCutActionPerformer cutActionPerformer = new CopyCutActionPerformer(false); /** delete action performer */ private final DeleteActionPerformer deleteActionPerformer = new DeleteActionPerformer(); /** own paste action */ private final OwnPaste pasteActionPerformer = new OwnPaste(); private ActionStateUpdater actionStateUpdater; /** the manager we are listening on */ private ExplorerManager manager; /** must the delete be confirmed */ private Boolean confirmDelete; /** attach as performers or just update the actions */ private boolean attachPerformers; /** Creates new instance. */ public ExplorerActions() { this(true); } /** Creates new instance with a decision whether the action should update * performers (the old behaviour) or only set the state of cut,copy,delete, * and paste actions. */ ExplorerActions(boolean attachPerformers) { this.attachPerformers = attachPerformers; } /** Getter for the copy action. */ final Action copyAction() { return copyActionPerformer; } /** The cut action */ final Action cutAction() { return cutActionPerformer; } /** The delete action */ final Action deleteAction() { return deleteActionPerformer; } /** Own paste action */ final Action pasteAction() { return pasteActionPerformer; } /** Attach to new manager. * @param m the manager to listen on */ public synchronized void attach(ExplorerManager m) { if (manager != null) { // first of all detach detach(); } manager = m; // update ExplorerPanel possibly associated with the manager ExplorerPanel.associateActions(this, m); // Sets action state updater and registers listening on manager and // exclipboard. actionStateUpdater = new ActionStateUpdater(); manager.addPropertyChangeListener(org.openide.util.WeakListeners.propertyChange(actionStateUpdater, manager)); Clipboard c = getClipboard(); if (c instanceof ExClipboard) { ExClipboard clip = (ExClipboard) c; clip.addClipboardListener( org.openide.util.WeakListeners.create(ClipboardListener.class, actionStateUpdater, clip) ); } updateActions(); } /** Detach from manager currently being listened on. */ public synchronized void detach() { if (manager == null) { return; } // Unregisters (weak) listening on manager and exclipboard (see attach). actionStateUpdater = null; stopActions(); manager = null; } /** Access method for use from ExplorerPanel, and also * via reflection (!) from RegistryImpl in core. * @deprecated Kill me later; see #18137 for explanation. */ @Deprecated ExplorerManager getAttachedManager() { return manager; } /** Set whether to confirm deletions. * @param yes true to confirm deletions */ public final void setConfirmDelete(boolean yes) { confirmDelete = Boolean.valueOf(yes); } /** Should deletions be confirmed? * @return true if deletions must be confirmed */ public final boolean isConfirmDelete() { return (confirmDelete == null) ? ExplorerPanel.isConfirmDelete() : confirmDelete.booleanValue(); } /** Stops listening on all actions */ private void stopActions() { if (copyActionPerformer != null) { if (attachPerformers) { if (copy.getActionPerformer() instanceof CopyCutActionPerformer) { copy.setActionPerformer(null); } if (cut.getActionPerformer() instanceof CopyCutActionPerformer) { cut.setActionPerformer(null); } paste.setPasteTypes(null); if (delete.getActionPerformer() instanceof DeleteActionPerformer) { delete.setActionPerformer(null); } } else { copyActionPerformer.setEnabled(false); cutActionPerformer.setEnabled(false); deleteActionPerformer.setEnabled(false); pasteActionPerformer.setEnabled(false); } } } /** Updates the state of all actions. * @param path list of selected nodes */ private void updateActions() { if (manager == null) { return; } Node[] path = manager.getSelectedNodes(); if (copy == null) { copy = CopyAction.findObject(CopyAction.class, true); cut = CutAction.findObject(CutAction.class, true); paste = PasteAction.findObject(PasteAction.class, true); delete = DeleteAction.findObject(DeleteAction.class, true); } int i; int k = (path != null) ? path.length : 0; if (k > 0) { boolean incest = false; if (k > 1) { // Do a special check for parenthood. Affects delete (for a long time), // copy (#13418), cut (#13426). If one node is a parent of another, // assume that the situation is sketchy and prevent it. // For k==1 it is impossible so do not waste time on it. Map allNodes = new HashMap<>(101); for (i = 0; i < k; i++) { if (!checkParents(path[i], allNodes)) { incest = true; break; } } } for (i = 0; i < k; i++) { if (incest || !path[i].canCopy()) { if (attachPerformers) { copy.setActionPerformer(null); } else { copyActionPerformer.setEnabled(false); } break; } } if (i == k) { if (attachPerformers) { copy.setActionPerformer(copyActionPerformer); } else { copyActionPerformer.setEnabled(true); } } for (i = 0; i < k; i++) { if (incest || !path[i].canCut()) { if (attachPerformers) { cut.setActionPerformer(null); } else { cutActionPerformer.setEnabled(false); } break; } } if (i == k) { if (attachPerformers) { cut.setActionPerformer(cutActionPerformer); } else { cutActionPerformer.setEnabled(true); } } for (i = 0; i < k; i++) { if (incest || !path[i].canDestroy()) { if (attachPerformers) { delete.setActionPerformer(null); } else { deleteActionPerformer.setEnabled(false); } break; } } if (i == k) { if (attachPerformers) { delete.setActionPerformer(deleteActionPerformer); } else { deleteActionPerformer.setEnabled(true); } } } else { // k==0, i.e. no nodes selected if (attachPerformers) { copy.setActionPerformer(null); cut.setActionPerformer(null); delete.setActionPerformer(null); } else { copyActionPerformer.setEnabled(false); cutActionPerformer.setEnabled(false); deleteActionPerformer.setEnabled(false); } } updatePasteAction(path); } /** Adds all parent nodes into the set. * @param set set of all nodes * @param node the node to check * @return false if one of the nodes is parent of another */ private boolean checkParents(Node node, Map set) { if (set.get(node) != null) { return false; } // this signals that this node is the original one set.put(node, this); for (;;) { node = node.getParentNode(); if (node == null) { return true; } if (set.put(node, node) == this) { // our parent is a node that is also in the set return false; } } } /** Updates paste action. * @param path selected nodes */ private void updatePasteAction(Node[] path) { ExplorerManager man = manager; if (man == null) { if (attachPerformers) { paste.setPasteTypes(null); } else { pasteActionPerformer.setPasteTypes(null); } return; } if ((path != null) && (path.length > 1)) { if (attachPerformers) { paste.setPasteTypes(null); } else { pasteActionPerformer.setPasteTypes(null); } return; } else { Node node = man.getExploredContext(); Node[] selectedNodes = man.getSelectedNodes(); if ((selectedNodes != null) && (selectedNodes.length == 1)) { node = selectedNodes[0]; } if (node != null) { Transferable trans = getClipboard().getContents(this); updatePasteTypes(trans, node); } } } /** Actually updates paste types. */ private void updatePasteTypes(Transferable trans, Node pan) { if (trans != null) { // First, just ask the node if it likes this transferable, whatever it may be. // If it does, then fine. PasteType[] pasteTypes = (pan == null) ? new PasteType[] { } : pan.getPasteTypes(trans); if (pasteTypes.length != 0) { if (attachPerformers) { paste.setPasteTypes(pasteTypes); } else { pasteActionPerformer.setPasteTypes(pasteTypes); } return; } boolean flavorSupported = false; try { flavorSupported = trans.isDataFlavorSupported(ExTransferable.multiFlavor); } catch (java.lang.Exception e) { // patch to get the Netbeans start under Solaris // [PENDINGworkaround] } if (flavorSupported) { // The node did not accept this multitransfer as is--try to break it into // individual transfers and paste them in sequence instead. try { MultiTransferObject obj = (MultiTransferObject) trans.getTransferData(ExTransferable.multiFlavor); int count = obj.getCount(); boolean ok = true; Transferable[] t = new Transferable[count]; PasteType[] p = new PasteType[count]; for (int i = 0; i < count; i++) { t[i] = obj.getTransferableAt(i); pasteTypes = (pan == null) ? new PasteType[] { } : pan.getPasteTypes(t[i]); if (pasteTypes.length == 0) { ok = false; break; } // [PENDING] this is ugly! ideally should be some way of comparing PasteType's for similarity? p[i] = pasteTypes[0]; } if (ok) { PasteType[] arrOfPaste = new PasteType[] { new MultiPasteType(t, p) }; if (attachPerformers) { paste.setPasteTypes(arrOfPaste); } else { pasteActionPerformer.setPasteTypes(arrOfPaste); } return; } } catch (UnsupportedFlavorException e) { // [PENDING] notify?! } catch (IOException e) { // [PENDING] notify?! } } } if (attachPerformers) { if (paste != null) { paste.setPasteTypes(null); } } else { pasteActionPerformer.setPasteTypes(null); } } /** If our clipboard is not found return the default system clipboard. */ private static Clipboard getClipboard() { Clipboard c = org.openide.util.Lookup.getDefault().lookup( java.awt.datatransfer.Clipboard.class ); if (c == null) { c = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard(); } return c; } /** Updates actions state via updater (if the updater is present). */ private void updateActionsState() { ActionStateUpdater asu; synchronized (this) { asu = actionStateUpdater; } if (asu != null) { asu.update(); } } /** Paste type used when in clipbopard is MultiTransferable */ private static class MultiPasteType extends PasteType { /** Array of transferables */ Transferable[] t; /** Array of paste types */ PasteType[] p; /** Constructs new MultiPasteType for the given content of the clipboard */ MultiPasteType(Transferable[] t, PasteType[] p) { this.t = t; this.p = p; } /** Performs the paste action. * @return Transferable which should be inserted into the clipboard after * paste action. It can be null, which means that clipboard content * should be cleared. */ public Transferable paste() throws IOException { int size = p.length; Transferable[] arr = new Transferable[size]; for (int i = 0; i < size; i++) { Transferable newTransferable = p[i].paste(); if (newTransferable != null) { arr[i] = newTransferable; } else { // keep the orginal arr[i] = t[i]; } } return new ExTransferable.Multi(arr); } } /** Own implementation of paste action */ private class OwnPaste extends AbstractAction { private PasteType[] pasteTypes = new PasteType[] { }; OwnPaste() { } @Override public boolean isEnabled() { updateActionsState(); return super.isEnabled(); } public void setPasteTypes(PasteType[] arr) { synchronized (this) { this.pasteTypes = arr; } setEnabled(arr != null); } public void actionPerformed(ActionEvent e) { // bugfix #36262, added check to avoid NPE if pasteTypes are null if (pasteTypes == null) { throw new IllegalStateException("Should not be invoked at all. Paste types: null"); // NOI18N } else { throw new IllegalStateException( "Should not be invoked at all. Paste types: " + java.util.Arrays.asList(pasteTypes) ); // NOI18N } } @Override public Object getValue(String s) { updateActionsState(); if ("delegates".equals(s)) { // NOI18N return pasteTypes; } return super.getValue(s); } } /** Class which performs copy and cut actions */ private class CopyCutActionPerformer extends AbstractAction implements org.openide.util.actions.ActionPerformer { /** determine if adapter is used for copy or cut action. */ private boolean copyCut; /** Create new adapter */ public CopyCutActionPerformer(boolean b) { copyCut = b; } @Override public boolean isEnabled() { updateActionsState(); return super.isEnabled(); } /** Perform copy or cut action. */ public void performAction(org.openide.util.actions.SystemAction action) { Transferable trans = null; Node[] sel = manager.getSelectedNodes(); if (sel.length != 1) { Transferable[] arrayTrans = new Transferable[sel.length]; for (int i = 0; i < sel.length; i++) if ((arrayTrans[i] = getTransferableOwner(sel[i])) == null) { return; } trans = new ExTransferable.Multi(arrayTrans); } else { trans = getTransferableOwner(sel[0]); } if (trans != null) { Clipboard clipboard = getClipboard(); clipboard.setContents(trans, new StringSelection("")); // NOI18N } } private Transferable getTransferableOwner(Node node) { try { return copyCut ? node.clipboardCopy() : node.clipboardCut(); } catch (java.io.IOException e) { Logger.getLogger(ExplorerActions.class.getName()).log(Level.WARNING, null, e); return null; } } /** Invoked when an action occurs. * */ public void actionPerformed(ActionEvent e) { performAction(null); } } /** Class which performs delete action */ private class DeleteActionPerformer extends AbstractAction implements ActionPerformer { DeleteActionPerformer() { } @Override public boolean isEnabled() { updateActionsState(); return super.isEnabled(); } /** Perform delete action. */ public void performAction(org.openide.util.actions.SystemAction action) { final Node[] sel = manager.getSelectedNodes(); if ((sel == null) || (sel.length == 0)) { return; } // perform action if confirmed if (!isConfirmDelete() || doConfirm(sel)) { // clear selected nodes try { if (manager != null) { manager.setSelectedNodes(new Node[] { }); } } catch (java.beans.PropertyVetoException e) { // never thrown, setting empty selected nodes cannot be vetoed } doDestroy(sel); class Run implements Runnable { public void run() { if (attachPerformers) { delete.setActionPerformer(null); // fixes bug #673 } else { setEnabled(false); } } } org.openide.util.Mutex.EVENT.readAccess(new Run()); } } private boolean doConfirm(Node[] sel) { String message; String title; boolean customDelete = true; for (int i = 0; i < sel.length; i++) { if (!Boolean.TRUE.equals(sel[i].getValue("customDelete"))) { // NOI18N customDelete = false; break; } } if (customDelete) { return true; } ResourceBundle bundle = NbBundle.getBundle("org.netbeans.modules.openide.explorer.Bundle"); if (sel.length == 1) { message = MessageFormat.format(bundle.getString("MSG_ConfirmDeleteObject"), sel[0].getDisplayName()); title = bundle.getString("MSG_ConfirmDeleteObjectTitle"); } else { message = MessageFormat.format(bundle.getString("MSG_ConfirmDeleteObjects"), sel.length); title = bundle.getString("MSG_ConfirmDeleteObjectsTitle"); } NotifyDescriptor desc = new NotifyDescriptor.Confirmation(message, title, NotifyDescriptor.YES_NO_OPTION); return NotifyDescriptor.YES_OPTION.equals(DialogDisplayer.getDefault().notify(desc)); } /* private String fullName(org.openide.loaders.DataObject obj) { FileObject f = obj.getPrimaryFile(); if (f.isRoot()) { try { return f.getFileSystem().getDisplayName(); } catch (FileStateInvalidException e) { return ""; //NOI18N } } else { return f.toString(); } } */ private void doDestroy(final Node[] sel) { try { FileUtil.runAtomicAction(new FileSystem.AtomicAction() { public void run() throws IOException { for (int i = 0; i < sel.length; i++) { try { sel[i].destroy(); } catch (IOException e) { Exceptions.printStackTrace(e); } } } }); } catch (IOException ioe) { throw (IllegalStateException) new IllegalStateException(ioe.toString()).initCause(ioe); } } /** Invoked when an action occurs. * */ public void actionPerformed(ActionEvent e) { performAction(null); } } /** Class which register changes in manager, and clipboard, coalesces * them if they are frequent and performs the update of actions state. */ private class ActionStateUpdater implements PropertyChangeListener, ClipboardListener, ActionListener { private final Timer timer; private boolean planned; ActionStateUpdater() { timer = new FixIssue29405Timer(150, this); timer.setCoalesce(true); timer.setRepeats(false); } public synchronized void propertyChange(PropertyChangeEvent e) { timer.restart(); planned = true; } public void clipboardChanged(ClipboardEvent ev) { final ExplorerManager em = manager; if (!ev.isConsumed() && (em != null)) { Mutex.EVENT.writeAccess( new Runnable() { public void run() { updatePasteAction(em.getSelectedNodes()); } } ); } } public void actionPerformed(ActionEvent evt) { updateActions(); synchronized (this) { timer.stop(); planned = false; } } /** Updates actions states now if there is pending event. */ public void update() { boolean update; synchronized (this) { update = planned; planned = false; } if (update) { timer.stop(); updateActions(); } } } /** Timer which fixes problem with running status (issue #29405). */ private static class FixIssue29405Timer extends javax.swing.Timer { private boolean running; public FixIssue29405Timer(int delay, ActionListener l) { super(delay, l); } @Override public void restart() { super.restart(); running = true; } @Override public void stop() { running = false; super.stop(); } @Override public boolean isRunning() { return running; } } // End of FixIssue29405Timer class. }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy