/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.nodes; import java.awt.*; import java.awt.dnd.*; import java.awt.datatransfer.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeListener; import java.io.IOException; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import org.openide.ErrorManager; import org.openide.util.NbBundle; import org.openide.util.WeakListener; import org.openide.util.datatransfer.ExTransferable; /** A dialog for reordering nodes. This dialog can reorder * nodes for all implementors of the {@link Index} cookie. * The dialog can invoke reorder actions on a given <code>Index</code> * implementation immediatelly, or these actions can be accumulated * and invoked at once, when the dialog is closed. * * <p>This class is final only for performance reasons. * * @author Jan Jancura, Ian Formanek, Dafe Simonek * @deprecated Better to use Index.Support.showIndexedCustomizer which behaves better * with the window system. */ public final class IndexedCustomizer extends JDialog implements java.beans.Customizer { // variables ..................................................................................... /** The actual JList control */ private JList control; /** Buttons */ private JButton buttonUp, buttonDown, buttonClose; /** index to sort */ private Index index; private Node[] nodes; /** Whether or not change the order immediatelly */ private boolean immediateReorder = true; /** Permutation array, which stores moves in case when * immediateReorder property is false */ private int[] permutation; /** Listener to the changes in the nodes */ private ChangeListener nodeChangesL; // initializations ................................................................................ static final long serialVersionUID =-8731362267771694641L; /** Construct a new customizer. */ public IndexedCustomizer () { this(null, true); } /** Construct a dummy customizer. * Might not actually be used as a JDialog, however its GUI * layout and logic will be used. * Cf. #9323. * @param c a container on which to draw the GUI * @param closeButton if true, add a Close button and other dialog logic, else no */ IndexedCustomizer(Container p, boolean closeButton) { super (TMUtil.mainWindow (), true); GridBagConstraints constraints; if (closeButton) { setDefaultCloseOperation (javax.swing.JDialog.DISPOSE_ON_CLOSE); // attach cancel also to Escape key getRootPane().registerKeyboardAction( new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { setVisible (false); dispose (); } }, javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_ESCAPE, 0, true), javax.swing.JComponent.WHEN_IN_FOCUSED_WINDOW ); setTitle(Node.getString("LAB_order")); } // closeButton if (p == null) p = getContentPane (); p.setLayout (new GridBagLayout()); JLabel l = new JLabel (Node.getString("LAB_listOrder")); l.setDisplayedMnemonic(Node.getString("LAB_listOrder_Mnemonic").charAt(0)); constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 0; constraints.anchor = GridBagConstraints.WEST; constraints.insets = new Insets(12, 12, 2, 12); p.add(l, constraints); control = new AutoscrollJList(); l.setLabelFor(control); control.addListSelectionListener (new ListSelectionListener () { public void valueChanged(ListSelectionEvent e) { if (control.isSelectionEmpty ()) { buttonUp.setEnabled (false); buttonDown.setEnabled (false); } else { int i = control.getSelectedIndex (); if (i > 0) //PENDING - jeste testovat, jestli jsou OrderedCookie.Child buttonUp.setEnabled (true); else buttonUp.setEnabled (false); if (i < (nodes.length - 1)) buttonDown.setEnabled (true); else buttonDown.setEnabled (false); } } } ); control.setCellRenderer (new IndexedListCellRenderer ()); control.setVisibleRowCount (15); control.setSelectionMode (javax.swing.ListSelectionModel.SINGLE_SELECTION); // list has to be scrolling constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 1; constraints.fill = GridBagConstraints.BOTH; constraints.weightx = 1.0; constraints.weighty = 1.0; constraints.insets = new Insets(0, 12, 11, 11); p.add(new JScrollPane(control), constraints); JPanel bb = new JPanel (); if (closeButton) { buttonClose = new JButton (Node.getString("Button_close")); buttonClose.setMnemonic(Node.getString("Button_close_Mnemonic").charAt(0)); } buttonUp = new JButton (Node.getString("Button_up")); buttonUp.setMnemonic(Node.getString("Button_up_Mnemonic").charAt(0)); buttonDown = new JButton (Node.getString("Button_down")); buttonDown.setMnemonic(Node.getString("Button_down_Mnemonic").charAt(0)); bb.setLayout(new GridBagLayout()); constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 0; constraints.anchor = GridBagConstraints.NORTH; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(0, 0, 5, 11); bb.add(buttonUp, constraints); constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 1; constraints.anchor = GridBagConstraints.NORTH; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.weighty = 1.0; constraints.insets = new Insets(0, 0, 0, 11); bb.add(buttonDown, constraints); if (closeButton) { constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 2; constraints.anchor = GridBagConstraints.SOUTH; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.insets = new Insets(0, 0, 11, 11); bb.add(buttonClose, constraints); } buttonUp.addActionListener (new ActionListener () { public void actionPerformed (ActionEvent e) { int i = control.getSelectedIndex (); moveUp (i); updateList (); control.setSelectedIndex (i - 1); control.ensureIndexIsVisible(i - 1); control.repaint (); } }); buttonDown.addActionListener (new ActionListener () { public void actionPerformed (ActionEvent e) { int i = control.getSelectedIndex (); moveDown (i); updateList (); control.setSelectedIndex (i + 1); control.ensureIndexIsVisible(i + 1); control.repaint (); } }); if (closeButton) { buttonClose.addActionListener (new ActionListener () { public void actionPerformed (ActionEvent e) { doClose(); dispose (); } }); } buttonUp.setEnabled (false); buttonDown.setEnabled (false); constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = 1; constraints.fill = GridBagConstraints.VERTICAL; p.add(bb, constraints); // disable drag support, as DnD crashes all environment // under current implementation of VMs on unixes, ans causes // some deadlocks on windows... //dragSupport = new IndexedDragSource(control); //dropSupport = new IndexedDropTarget(this, dragSupport); if (closeButton) { pack(); setBounds(org.openide.util.Utilities.findCenterBounds(getSize())); buttonClose.requestFocus (); // to get shortcuts to work buttonClose.getAccessibleContext().setAccessibleDescription(Node.getString("ACSD_Button_close")); } buttonUp.getAccessibleContext().setAccessibleDescription(Node.getString("ACSD_Button_up")); buttonDown.getAccessibleContext().setAccessibleDescription(Node.getString("ACSD_Button_down")); control.getAccessibleContext().setAccessibleDescription(Node.getString("ACSD_ListOrder")); p.getAccessibleContext().setAccessibleDescription(Node.getString("ACSD_IndexedCustomizer")); getAccessibleContext().setAccessibleDescription(Node.getString("ACSD_IndexedCustomizer")); } /** Simulate behavior of the Close button, minus actual dialog disposal. * Might do the same as setImmediateReorder(true), but I am not sure. */ void doClose() { if ((!immediateReorder) && (index != null) && (permutation != null)) { int[] realPerm = new int[permutation.length]; for (int i = 0; i < realPerm.length; i++) { realPerm[permutation[i]] = i; //System.out.println (i + "-->" + permutation[i]); // NOI18N } index.reorder(realPerm); } } // other methods ................................................................................ /** Called when an explored context changes and the list needs to be * recreated. */ private void updateList () { if (index == null) return; Node[] localNodes = index.getNodes(); //System.out.println ("Nodes taken, size: " + localNodes.length); // NOI18N // obtain nodes with help from permutation array, if // conditions met if (!immediateReorder) { getPermutation(); int origLength = permutation.length; int newLength = localNodes.length; if (origLength < newLength) { // some nodes added, we must synchronize the permutation nodes = new Node[newLength]; int[] newPerm = new int[newLength]; System.arraycopy(newPerm, 0, permutation, 0, origLength); for (int i = 0; i < newLength; i++) { if (i < origLength) nodes[i] = localNodes[permutation[i]]; else { // added nodes.... nodes[i] = localNodes[i]; newPerm[i] = i; } } permutation = newPerm; } else if (origLength > newLength) { // some nodes removed, we must re-initialize the permutation nodes = new Node[newLength]; permutation = new int[newLength]; for (int i = 0; i < newLength; i++) { nodes[i] = localNodes[i]; permutation[i] = i; } } else { // node count is the same, only permute the nodes nodes = new Node[newLength]; for (int i = 0; i < newLength; i++) nodes[i] = localNodes[permutation[i]]; } } else { nodes = (Node[])localNodes.clone(); } control.setListData (nodes); } public Dimension getPreferredSize () { return new Dimension (300, super.getPreferredSize ().height); } /** Will reorders be reflected immediately? * @return <code>true</code> if so */ public boolean isImmediateReorder () { return immediateReorder; } /** Set whether reorders will take effect immediately. * @param immediateReorder <code>true</code> if so */ public void setImmediateReorder (boolean immediateReorder) { if (this.immediateReorder == immediateReorder) return; this.immediateReorder = immediateReorder; if (immediateReorder) { if (permutation != null) { index.reorder(permutation); permutation = null; updateList(); } } } // implementation of Customizer ............................................................ /** Set the nodes to reorder. * @param bean must implement {@link Index} * @throws IllegalArgumentException if not */ public void setObject (Object bean) { if (bean instanceof Index) { index = (Index)bean; // add weak listener to the Index nodeChangesL = new ChangeListener() { public void stateChanged (ChangeEvent ev) { SwingUtilities.invokeLater(new Runnable() { public void run () { updateList(); } }); } }; updateList (); control.invalidate(); validate(); index.addChangeListener(WeakListener.change(nodeChangesL, index)); } else { throw new IllegalArgumentException (); } } // I don't change any property... public void addPropertyChangeListener(PropertyChangeListener listener) { } public void removePropertyChangeListener(PropertyChangeListener listener) { } /** Moves up. Performs differently according to * immediateReorder property value. */ private void moveUp (final int position) { if (index == null) return; if (immediateReorder) { index.moveUp(position); } else { getPermutation(); int temp = permutation[position]; permutation[position] = permutation[position - 1]; permutation[position - 1] = temp; } } /** Moves down. Performs differently according to * immediateReorder property value. */ private void moveDown (final int position) { if (index == null) return; if (immediateReorder) { index.moveDown(position); } else { getPermutation(); int temp = permutation[position]; permutation[position] = permutation[position + 1]; permutation[position + 1] = temp; } } /** Safe getter for permutation. * Initializes permutation to identical permutation if it is null.<br> * index variable must not be null when this method called */ private int[] getPermutation () { if (permutation == null) { if (nodes == null) nodes = (Node[])index.getNodes().clone(); permutation = new int[nodes.length]; for (int i = 0; i < nodes.length; permutation[i] = i++); } return permutation; } /** Permute the list as given permutation dictates. * Sets selection to the given selected index. * Called from dropSupport as result of succesfull DnD operation. */ void performReorder (int[] perm, int selected) { if (immediateReorder) { index.reorder(perm); } else { // merge current and reversed given permutation // (reverse given permutation first) int[] reversed = new int[perm.length]; for (int i = 0; i < reversed.length; i++) reversed[perm[i]] = i; int[] orig = getPermutation(); permutation = new int[orig.length]; for (int i = 0; i < orig.length; i++) { permutation[i] = orig[reversed[i]]; // System.out.println(permutation[i] + " ----> " + i); // NOI18N } } updateList(); control.setSelectedIndex(selected); control.repaint(); } /** Implementation of drag functionality in * reorder dialog. */ private static final class IndexedDragSource implements DragGestureListener, DragSourceListener { /** Asociated JList component where the drag will * take place */ JList comp; /** User gesture that initiated the drag */ DragGestureEvent dge; /** Out data flavor used to transfer the index */ DataFlavor myFlavor; /** Creates drag source with asociated list where drag * will take place. * Also creates the default gesture and asociates this with * given component */ IndexedDragSource (JList comp) { this.comp = comp; // initialize gesture DragSource ds = DragSource.getDefaultDragSource(); ds.createDefaultDragGestureRecognizer( comp, DnDConstants.ACTION_MOVE, this); } /** Initiating the drag */ public void dragGestureRecognized(DragGestureEvent dge) { // check allowed actions if ((dge.getDragAction() & DnDConstants.ACTION_MOVE) == 0) return; // prepare transferable and start the drag int index = comp.locationToIndex(dge.getDragOrigin()); // no index, then no dragging... if (index < 0) return; // System.out.println("Starting drag..."); // NOI18N // create our flavor for transferring the index myFlavor = new DataFlavor(String.class, NbBundle.getBundle(IndexedCustomizer.class). getString("IndexedFlavor")); try { dge.startDrag(DragSource.DefaultMoveDrop, new IndexTransferable(myFlavor, index), this); // remember the gesture this.dge = dge; } catch (InvalidDnDOperationException exc) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, exc); // PENDING notify user - cannot start the drag } } public void dragEnter(DragSourceDragEvent dsde) { } public void dragOver(DragSourceDragEvent dsde) { } public void dropActionChanged(DragSourceDragEvent dsde) { } public void dragExit(DragSourceEvent dse) { } public void dragDropEnd(DragSourceDropEvent dsde) { } /** Utility accessor */ DragGestureEvent getDragGestureEvent () { return dge; } } // end of IndexedDragSource /** Implementation of drop functionality in * reorder dialog. */ private static final class IndexedDropTarget implements DropTargetListener { /** Asociated JList component for dropping */ JList comp; /** Cell renderer which renders the list items */ IndexedListCellRenderer cellRenderer; /** Indexed dialog */ IndexedCustomizer dialog; /** Drag source support instance */ IndexedDragSource ids; /** last index dragged over */ int lastIndex = -1; /** Creates the instance, makes given component active for * drop operation. */ IndexedDropTarget (IndexedCustomizer dialog, IndexedDragSource ids) { this.dialog = dialog; this.comp = dialog.control; this.cellRenderer = (IndexedListCellRenderer)this.comp.getCellRenderer(); this.ids = ids; new DropTarget(comp, DnDConstants.ACTION_MOVE, this, true); } /** User is starting to drag over us */ public void dragEnter(DropTargetDragEvent dtde) { if (!checkConditions(dtde)) dtde.rejectDrag(); else { lastIndex = comp.locationToIndex(dtde.getLocation()); cellRenderer.draggingEnter(lastIndex, ids.getDragGestureEvent().getDragOrigin(), dtde.getLocation()); comp.repaint(comp.getCellBounds(lastIndex, lastIndex)); } } /** User drag over us */ public void dragOver(DropTargetDragEvent dtde) { if (!checkConditions(dtde)) { dtde.rejectDrag(); if (lastIndex >= 0) { cellRenderer.draggingExit(); comp.repaint(comp.getCellBounds(lastIndex, lastIndex)); lastIndex = -1; } } else { dtde.acceptDrag(DnDConstants.ACTION_MOVE); int index = comp.locationToIndex(dtde.getLocation()); if (lastIndex == index) cellRenderer.draggingOver(index, ids.getDragGestureEvent().getDragOrigin(), dtde.getLocation()); else { if (lastIndex < 0) lastIndex = index; cellRenderer.draggingExit(); cellRenderer.draggingEnter(index, ids.getDragGestureEvent().getDragOrigin(), dtde.getLocation()); comp.repaint(comp.getCellBounds(lastIndex, index)); lastIndex = index; } } } public void dropActionChanged(DropTargetDragEvent dtde) { } /** User exits the dragging */ public void dragExit(DropTargetEvent dte) { if (lastIndex >= 0) { cellRenderer.draggingExit(); comp.repaint(comp.getCellBounds(lastIndex, lastIndex)); } } /** Takes given index transferable and reorders * the items as appropriate (and if possible) */ public void drop(DropTargetDropEvent dtde) { // reject all but local moves if ((DnDConstants.ACTION_MOVE != dtde.getDropAction()) || !dtde.isLocalTransfer()) dtde.rejectDrop(); int target = comp.locationToIndex(dtde.getLocation()); if (target < 0) { dtde.rejectDrop(); return; } Transferable t = dtde.getTransferable(); // System.out.println("Dropping..."); // NOI18N dtde.acceptDrop(DnDConstants.ACTION_MOVE); try { int source = Integer.parseInt( (String)t.getTransferData(ids.myFlavor)); if (source != target) { performReorder(source, target); dtde.dropComplete(true); } else dtde.dropComplete(false); } catch (IOException exc) { dtde.dropComplete(false); } catch (UnsupportedFlavorException exc) { dtde.dropComplete(false); } catch (NumberFormatException exc) { dtde.dropComplete(false); } } /** Actually performs the reordering which results from * succesfull drag-drop operation. * @param source */ void performReorder (int source, int target) { int[] myPerm = new int[comp.getModel().getSize()]; // positions will change only between source and target // indexes, the rest remains the same for (int i = 0; i < Math.min(source, target); i++) myPerm[i] = i; for (int i = Math.max(source, target) + 1; i < myPerm.length; i++) myPerm[i] = i; // reorder the rest myPerm[source] = target; if (source > target) { // dragging was up the list for (int i = target; i < source; i++) myPerm[i] = i + 1; } else { // dragging was down the list for (int i = source + 1; i < target + 1; i++) myPerm[i] = i - 1; } // and finally perform the reordering dialog.performReorder(myPerm, target); } /** @return True if conditions to continue with DnD * operation were satisfied */ boolean checkConditions (DropTargetDragEvent dtde) { int index = comp.locationToIndex(dtde.getLocation()); return DnDConstants.ACTION_MOVE == dtde.getDropAction() && index >= 0; } } // end of IndexedDropTarget /** This class takes responsibility of presenting the * asociated index as transferable object. */ private static final class IndexTransferable extends ExTransferable.Single { /** Index to transfer */ int index; /** Creates transferable of given index */ IndexTransferable (DataFlavor flavor, int index) { super(flavor); this.index = index; } /* Returns string representation of index */ protected Object getData () throws IOException, UnsupportedFlavorException { return String.valueOf(index); } } // end of IndexTransferable /** Implements drag and drop visual feedback * support for node list cell rendeder. */ private static final class IndexedListCellRenderer implements javax.swing.ListCellRenderer { /** delegate to use for rendering. Usually NodeRenderer, but if run * without explorer, then it uses DefaultListCellRenderer */ private javax.swing.ListCellRenderer delegate = TMUtil.findListCellRenderer (); /** Index of currently drag under cell in parent list */ int dragIndex; /** True if move was up the list */ boolean up; static final long serialVersionUID =-5526451942677242944L; protected static Border hasFocusBorder; static { hasFocusBorder = new LineBorder(UIManager.getColor("List.focusCellHighlight")); // NOI18N } /** Creates new renderer */ IndexedListCellRenderer () { dragIndex = -1; } /** DnD operation enters, update visual * presentation to the drag under state */ public void draggingEnter (int index, Point startingLoc, Point currentLoc) { // System.out.println("Entering index: " + index); // NOI18N this.dragIndex = index; up = startingLoc.y > currentLoc.y; } /** DnD operation dragging over. */ public void draggingOver (int index, Point startingLoc, Point currentLoc) { } /** DnD operation exits, reset visual state * back to the normal */ public void draggingExit () { dragIndex = -1; } public Component getListCellRendererComponent ( JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JComponent result = (JComponent)delegate.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus); if (index == dragIndex) { // System.out.println("Drawing...."); // NOI18N result.setBorder(hasFocusBorder); } return result; } } // end of IndexedListCellRenderer /** Implements autoscrolling support for JList. * However, JList must be contained in some JViewport. */ private static class AutoscrollJList extends JList implements Autoscroll { /** Autoscroll insets */ Insets scrollInsets; /** Insets for the autoscroll method to decide * whether really perform or not */ Insets realInsets; /** Viewport we are in */ JViewport viewport; AutoscrollJList() {} static final long serialVersionUID =5495776972406885734L; /** notify the Component to autoscroll */ public void autoscroll (Point cursorLoc) { JViewport viewport = getViewport(); Point viewPos = viewport.getViewPosition(); int viewHeight = viewport.getExtentSize().height; if ((cursorLoc.y - viewPos.y) <= realInsets.top) // scroll up viewport.setViewPosition(new Point(viewPos.x, Math.max(viewPos.y - realInsets.top, 0))); else if ((viewPos.y + viewHeight - cursorLoc.y) <= realInsets.bottom) // scroll down viewport.setViewPosition(new Point(viewPos.x, Math.min(viewPos.y + realInsets.bottom, this.getHeight() - viewHeight))); } /** @return the Insets describing the autoscrolling * region or border relative to the geometry of the * implementing Component. */ public Insets getAutoscrollInsets () { if (scrollInsets == null) { int height = this.getHeight(); scrollInsets = new Insets(height, 0, height, 0); // compute also autoscroll insets for viewport //Rectangle rect = getViewport().getViewRect(); realInsets = new Insets(15, 0, 15, 0); } return scrollInsets; } /** Asociates given viewport with this list. * (Viewport is usually parent containing this component) */ JViewport getViewport () { if (viewport == null) { Component comp = this; while (!(comp instanceof JViewport) && (comp != null)) { comp = comp.getParent(); } viewport = (JViewport)comp; } return viewport; } } // end of AutoscrollJViewport }