1 19 20 package org.openide.explorer; 21 22 import java.awt.Toolkit ; 23 import java.awt.datatransfer.Clipboard ; 24 import java.awt.datatransfer.StringSelection ; 25 import java.awt.datatransfer.Transferable ; 26 import java.awt.datatransfer.UnsupportedFlavorException ; 27 import java.awt.event.ActionEvent ; 28 import java.awt.event.ActionListener ; 29 import java.beans.PropertyChangeEvent ; 30 import java.beans.PropertyChangeListener ; 31 import java.beans.PropertyVetoException ; 32 import java.io.IOException ; 33 import java.util.Arrays ; 34 import java.util.HashMap ; 35 import java.util.logging.Level ; 36 import java.util.logging.Logger ; 37 import javax.swing.AbstractAction ; 38 import javax.swing.Action ; 39 import javax.swing.Timer ; 40 import org.netbeans.modules.openide.explorer.ExternalDragAndDrop; 41 import org.openide.DialogDisplayer; 42 import org.openide.NotifyDescriptor; 43 import org.openide.nodes.Node; 44 import org.openide.util.Exceptions; 45 import org.openide.util.Lookup; 46 import org.openide.util.Mutex; 47 import org.openide.util.NbBundle; 48 import org.openide.util.WeakListeners; 49 import org.openide.util.datatransfer.ClipboardEvent; 50 import org.openide.util.datatransfer.ClipboardListener; 51 import org.openide.util.datatransfer.ExClipboard; 52 import org.openide.util.datatransfer.ExTransferable; 53 import org.openide.util.datatransfer.MultiTransferObject; 54 import org.openide.util.datatransfer.PasteType; 55 56 66 final class ExplorerActionsImpl { 67 68 private final CopyCutActionPerformer copyActionPerformer = new CopyCutActionPerformer(true); 69 70 71 private final CopyCutActionPerformer cutActionPerformer = new CopyCutActionPerformer(false); 72 73 74 private final DeleteActionPerformer deleteActionPerformerConfirm = new DeleteActionPerformer(true); 75 76 77 private final DeleteActionPerformer deleteActionPerformerNoConfirm = new DeleteActionPerformer(false); 78 79 80 private final OwnPaste pasteActionPerformer = new OwnPaste(); 81 private ActionStateUpdater actionStateUpdater; 82 83 84 private ExplorerManager manager; 85 86 90 ExplorerActionsImpl() { 91 } 92 93 97 99 final Action copyAction() { 100 return copyActionPerformer; 101 } 102 103 104 final Action cutAction() { 105 return cutActionPerformer; 106 } 107 108 110 final Action deleteAction(boolean confirm) { 111 return confirm ? deleteActionPerformerConfirm : deleteActionPerformerNoConfirm; 112 } 113 114 116 final Action pasteAction() { 117 return pasteActionPerformer; 118 } 119 120 123 public synchronized void attach(ExplorerManager m) { 124 if (manager != null) { 125 detach(); 127 } 128 129 manager = m; 130 131 actionStateUpdater = new ActionStateUpdater(); 134 manager.addPropertyChangeListener(WeakListeners.propertyChange(actionStateUpdater, manager)); 135 136 Clipboard c = getClipboard(); 137 138 if (c instanceof ExClipboard) { 139 ExClipboard clip = (ExClipboard) c; 140 clip.addClipboardListener( 141 WeakListeners.create( 142 ClipboardListener.class, actionStateUpdater, clip 143 ) 144 ); 145 } 146 147 updateActions(); 148 } 149 150 151 public synchronized void detach() { 152 if (manager == null) { 153 return; 154 } 155 156 actionStateUpdater = null; 158 159 stopActions(); 160 161 manager = null; 162 } 163 164 165 private void stopActions() { 166 if (copyActionPerformer != null) { 167 copyActionPerformer.setEnabled(false); 168 cutActionPerformer.setEnabled(false); 169 deleteActionPerformerConfirm.setEnabled(false); 170 deleteActionPerformerNoConfirm.setEnabled(false); 171 pasteActionPerformer.setEnabled(false); 172 } 173 } 174 175 178 private void updateActions() { 179 if (manager == null) { 180 return; 181 } 182 183 Node[] path = manager.getSelectedNodes(); 184 185 int i; 186 int k = (path != null) ? path.length : 0; 187 188 if (k > 0) { 189 boolean incest = false; 190 191 if (k > 1) { 192 HashMap <Node, Object > allNodes = new HashMap <Node, Object >(101); 197 198 for (i = 0; i < k; i++) { 199 if (!checkParents(path[i], allNodes)) { 200 incest = true; 201 202 break; 203 } 204 } 205 } 206 207 for (i = 0; i < k; i++) { 208 if (incest || !path[i].canCopy()) { 209 copyActionPerformer.setEnabled(false); 210 211 break; 212 } 213 } 214 215 if (i == k) { 216 copyActionPerformer.setEnabled(true); 217 } 218 219 for (i = 0; i < k; i++) { 220 if (incest || !path[i].canCut()) { 221 cutActionPerformer.setEnabled(false); 222 223 break; 224 } 225 } 226 227 if (i == k) { 228 cutActionPerformer.setEnabled(true); 229 } 230 231 for (i = 0; i < k; i++) { 232 if (incest || !path[i].canDestroy()) { 233 deleteActionPerformerConfirm.setEnabled(false); 234 deleteActionPerformerNoConfirm.setEnabled(false); 235 236 break; 237 } 238 } 239 240 if (i == k) { 241 deleteActionPerformerConfirm.setEnabled(true); 242 deleteActionPerformerNoConfirm.setEnabled(true); 243 } 244 } else { copyActionPerformer.setEnabled(false); 246 cutActionPerformer.setEnabled(false); 247 deleteActionPerformerConfirm.setEnabled(false); 248 deleteActionPerformerNoConfirm.setEnabled(false); 249 } 250 251 updatePasteAction(path); 252 } 253 254 259 private boolean checkParents(Node node, HashMap <Node, Object > set) { 260 if (set.get(node) != null) { 261 return false; 262 } 263 264 set.put(node, this); 266 267 for (;;) { 268 node = node.getParentNode(); 269 270 if (node == null) { 271 return true; 272 } 273 274 if (set.put(node, node) == this) { 275 return false; 277 } 278 } 279 } 280 281 284 private void updatePasteAction(Node[] path) { 285 ExplorerManager man = manager; 286 287 if (man == null) { 288 pasteActionPerformer.setPasteTypes(null); 289 290 return; 291 } 292 293 if ((path != null) && (path.length > 1)) { 294 pasteActionPerformer.setPasteTypes(null); 295 296 return; 297 } else { 298 Node node = man.getExploredContext(); 299 Node[] selectedNodes = man.getSelectedNodes(); 300 301 if ((selectedNodes != null) && (selectedNodes.length == 1)) { 302 node = selectedNodes[0]; 303 } 304 305 if (node != null) { 306 Transferable trans = getClipboard().getContents(this); 307 updatePasteTypes(trans, node); 308 } 309 } 310 } 311 312 313 private void updatePasteTypes(Transferable trans, Node pan) { 314 if (trans != null) { 315 PasteType[] pasteTypes = (pan == null) ? new PasteType[] { } : pan.getPasteTypes(trans); 318 319 if (pasteTypes.length != 0) { 320 pasteActionPerformer.setPasteTypes(pasteTypes); 321 322 return; 323 } 324 325 if (trans.isDataFlavorSupported(ExTransferable.multiFlavor)) { 326 try { 329 MultiTransferObject obj = (MultiTransferObject) trans.getTransferData(ExTransferable.multiFlavor); 330 int count = obj.getCount(); 331 boolean ok = true; 332 Transferable [] t = new Transferable [count]; 333 PasteType[] p = new PasteType[count]; 334 335 for (int i = 0; i < count; i++) { 336 t[i] = obj.getTransferableAt(i); 337 pasteTypes = (pan == null) ? new PasteType[] { } : pan.getPasteTypes(t[i]); 338 339 if (pasteTypes.length == 0) { 340 ok = false; 341 342 break; 343 } 344 345 p[i] = pasteTypes[0]; 347 } 348 349 if (ok) { 350 PasteType[] arrOfPaste = new PasteType[] { new MultiPasteType(t, p) }; 351 pasteActionPerformer.setPasteTypes(arrOfPaste); 352 353 return; 354 } 355 } catch (UnsupportedFlavorException e) { 356 } catch (IOException e) { 358 } 360 } 361 } 362 363 pasteActionPerformer.setPasteTypes(null); 364 } 365 366 367 private static Clipboard getClipboard() { 368 Clipboard c = Lookup.getDefault().lookup(Clipboard .class); 369 370 if (c == null) { 371 c = Toolkit.getDefaultToolkit().getSystemClipboard(); 372 } 373 374 return c; 375 } 376 377 378 private void updateActionsState() { 379 ActionStateUpdater asu; 380 381 synchronized (this) { 382 asu = actionStateUpdater; 383 } 384 385 if (asu != null) { 386 asu.update(); 387 } 388 } 389 390 391 private static class MultiPasteType extends PasteType { 392 393 Transferable [] t; 394 395 396 PasteType[] p; 397 398 399 MultiPasteType(Transferable [] t, PasteType[] p) { 400 this.t = t; 401 this.p = p; 402 } 403 404 409 public Transferable paste() throws IOException { 410 int size = p.length; 411 Transferable [] arr = new Transferable [size]; 412 413 for (int i = 0; i < size; i++) { 414 Transferable newTransferable = p[i].paste(); 415 416 if (newTransferable != null) { 417 arr[i] = newTransferable; 418 } else { 419 arr[i] = t[i]; 421 } 422 } 423 424 return new ExTransferable.Multi(arr); 425 } 426 } 427 428 430 private class OwnPaste extends AbstractAction { 431 private PasteType[] pasteTypes; 432 433 OwnPaste() { 434 } 435 436 public boolean isEnabled() { 437 updateActionsState(); 438 439 return super.isEnabled(); 440 } 441 442 public void setPasteTypes(PasteType[] arr) { 443 synchronized (this) { 444 this.pasteTypes = arr; 445 } 446 447 setEnabled(arr != null); 448 } 449 450 public void actionPerformed(ActionEvent e) { 451 PasteType[] arr = this.pasteTypes; 452 throw new IllegalStateException ( 453 "Should not be invoked at all. Paste types: " + (arr == null ? null : Arrays.asList(arr)) ); 455 } 456 457 public Object getValue(String s) { 458 updateActionsState(); 459 460 if ("delegates".equals(s)) { 462 return pasteTypes; 463 } 464 465 return super.getValue(s); 466 } 467 } 468 469 470 private class CopyCutActionPerformer extends AbstractAction { 471 472 private boolean copyCut; 473 474 475 public CopyCutActionPerformer(boolean b) { 476 copyCut = b; 477 } 478 479 public boolean isEnabled() { 480 updateActionsState(); 481 482 return super.isEnabled(); 483 } 484 485 public void actionPerformed(ActionEvent ev) { 486 Transferable trans = null; 487 Node[] sel = manager.getSelectedNodes(); 488 489 if (sel.length != 1) { 490 Transferable [] arrayTrans = new Transferable [sel.length]; 491 492 for (int i = 0; i < sel.length; i++) 493 if ((arrayTrans[i] = getTransferableOwner(sel[i])) == null) { 494 return; 495 } 496 497 trans = ExternalDragAndDrop.maybeAddExternalFileDnd( new ExTransferable.Multi(arrayTrans) ); 498 } else { 499 trans = getTransferableOwner(sel[0]); 500 } 501 502 if (trans != null) { 503 Clipboard clipboard = getClipboard(); 504 505 clipboard.setContents(trans, new StringSelection ("")); } 507 } 508 509 private Transferable getTransferableOwner(Node node) { 510 try { 511 return copyCut ? node.clipboardCopy() : node.clipboardCut(); 512 } catch (IOException e) { 513 Logger.getLogger(ExplorerActionsImpl.class.getName()).log(Level.WARNING, null, e); 514 515 return null; 516 } 517 } 518 } 519 520 521 private class DeleteActionPerformer extends AbstractAction implements Runnable { 522 private boolean confirmDelete; 523 524 DeleteActionPerformer(boolean confirmDelete) { 525 this.confirmDelete = confirmDelete; 526 } 527 528 public boolean isEnabled() { 529 updateActionsState(); 530 531 return super.isEnabled(); 532 } 533 534 public void actionPerformed(ActionEvent ev) { 535 final Node[] sel = manager.getSelectedNodes(); 536 537 if ((sel == null) || (sel.length == 0)) { 538 return; 539 } 540 541 if (!confirmDelete || doConfirm(sel)) { 543 try { 545 if (manager != null) { 546 manager.setSelectedNodes(new Node[] { }); 547 } 548 } catch (PropertyVetoException e) { 549 } 551 552 doDestroy(sel); 553 554 Mutex.EVENT.readAccess(this); 556 } 557 } 558 559 561 public void run() { 562 setEnabled(false); 563 } 564 565 private boolean doConfirm(Node[] sel) { 566 String message; 567 String title; 568 boolean customDelete = true; 569 570 for (int i = 0; i < sel.length; i++) { 571 if (!Boolean.TRUE.equals(sel[i].getValue("customDelete"))) { customDelete = false; 573 574 break; 575 } 576 } 577 578 if (customDelete) { 579 return true; 580 } 581 582 if (sel.length == 1) { 583 message = NbBundle.getMessage( 584 ExplorerActionsImpl.class, "MSG_ConfirmDeleteObject", sel[0].getDisplayName() 585 ); 586 title = NbBundle.getMessage(ExplorerActionsImpl.class, "MSG_ConfirmDeleteObjectTitle"); 587 } else { 588 message = NbBundle.getMessage( 589 ExplorerActionsImpl.class, "MSG_ConfirmDeleteObjects", Integer.valueOf(sel.length) 590 ); 591 title = NbBundle.getMessage(ExplorerActionsImpl.class, "MSG_ConfirmDeleteObjectsTitle"); 592 } 593 594 NotifyDescriptor desc = new NotifyDescriptor.Confirmation(message, title, NotifyDescriptor.YES_NO_OPTION); 595 596 return NotifyDescriptor.YES_OPTION.equals(DialogDisplayer.getDefault().notify(desc)); 597 } 598 599 private void doDestroy(final Node[] sel) { 600 for (int i = 0; i < sel.length; i++) { 601 try { 602 sel[i].destroy(); 603 } 604 catch (IOException e) { 605 Exceptions.printStackTrace(e); 606 } 607 } 608 } 609 610 } 611 612 614 private class ActionStateUpdater implements PropertyChangeListener , ClipboardListener, ActionListener , Runnable { 615 private final Timer timer; 616 private boolean planned; 617 618 ActionStateUpdater() { 619 timer = new FixIssue29405Timer(150, this); 620 timer.setCoalesce(true); 621 timer.setRepeats(false); 622 } 623 624 public synchronized void propertyChange(PropertyChangeEvent e) { 625 timer.restart(); 626 planned = true; 627 } 628 629 public void clipboardChanged(ClipboardEvent ev) { 630 if (!ev.isConsumed()) { 631 Mutex.EVENT.readAccess(this); 632 } 633 } 634 635 public void run() { 636 ExplorerManager em = manager; 637 638 if (em != null) { 639 updatePasteAction(em.getSelectedNodes()); 640 } 641 } 642 643 public void actionPerformed(ActionEvent evt) { 644 updateActions(); 645 646 synchronized (this) { 647 timer.stop(); 648 planned = false; 649 } 650 } 651 652 653 public void update() { 654 boolean update; 655 656 synchronized (this) { 657 update = planned; 658 } 659 660 if (update) { 661 timer.stop(); 662 updateActions(); 663 } 664 } 665 } 666 667 668 private static class FixIssue29405Timer extends Timer { 669 private boolean running; 670 671 public FixIssue29405Timer(int delay, ActionListener l) { 672 super(delay, l); 673 } 674 675 public void restart() { 676 super.restart(); 677 running = true; 678 } 679 680 public void stop() { 681 running = false; 682 super.stop(); 683 } 684 685 public boolean isRunning() { 686 return running; 687 } 688 } 689 } 691 | Popular Tags |