Package net.sf.jabref.groups

Source Code of net.sf.jabref.groups.GroupsTree

/*
All programs in this directory and subdirectories are published under the
GNU General Public License as described below.

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option)
any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc., 59
Temple Place, Suite 330, Boston, MA 02111-1307 USA

Further information about the GNU GPL is available at:
http://www.gnu.org/copyleft/gpl.ja.html
*/

package net.sf.jabref.groups;

import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.awt.event.InputEvent;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.undo.AbstractUndoableEdit;

import net.sf.jabref.BibtexEntry;
import net.sf.jabref.Globals;
import net.sf.jabref.Util;

public class GroupsTree extends JTree implements DragSourceListener,
    DropTargetListener, DragGestureListener {
  /** distance from component borders from which on autoscrolling starts. */
  private static final int dragScrollActivationMargin = 10;

  /** number of pixels to scroll each time handler is called. */
  private static final int dragScrollDistance = 5;

  /** time of last autoscroll event (for limiting speed). */
  private static long lastDragAutoscroll = 0L;

  /** minimum interval between two autoscroll events (for limiting speed). */
  private static final long minAutoscrollInterval = 50L;

  /**
   * the point on which the cursor is currently idling during a drag
   * operation.
   */
  private Point idlePoint;

  /** time since which cursor is idling. */
  private long idleStartTime = 0L;

  /** max. distance cursor may move in x or y direction while idling. */
  private static final int idleMargin = 1;

  /** idle time after which the node below is expanded. */
  private static final long idleTimeToExpandNode = 1000L;

  private GroupSelector groupSelector;

  private GroupTreeNode dragNode = null;

  private final GroupTreeCellRenderer cellRenderer = new GroupTreeCellRenderer();

  public GroupsTree(GroupSelector groupSelector) {
    this.groupSelector = groupSelector;
    DragGestureRecognizer dgr = DragSource.getDefaultDragSource()
        .createDefaultDragGestureRecognizer(this,
            DnDConstants.ACTION_MOVE, this);
    // Eliminates right mouse clicks as valid actions
    dgr.setSourceActions(dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK);
    new DropTarget(this, this);
    setCellRenderer(cellRenderer);
    setFocusable(false);
    setToggleClickCount(0);
    ToolTipManager.sharedInstance().registerComponent(this);
    setShowsRootHandles(false);
    setVisibleRowCount(Globals.prefs.getInt("groupsVisibleRows"));
    getSelectionModel().setSelectionMode(
        TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
  }

  public void dragEnter(DragSourceDragEvent dsde) {
    // ignore
  }

  /** This is for moving of nodes within myself */
  public void dragOver(DragSourceDragEvent dsde) {
    final Point p = dsde.getLocation(); // screen coordinates!
    SwingUtilities.convertPointFromScreen(p, this);
    final TreePath path = getPathForLocation(p.x, p.y);
    if (path == null) {
      dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
      return;
    }
    final GroupTreeNode target = (GroupTreeNode) path
        .getLastPathComponent();
    if (target == null || dragNode.isNodeDescendant(target)
        || dragNode == target) {
      dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop);
      return;
    }
    dsde.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop);
  }

  public void dropActionChanged(DragSourceDragEvent dsde) {
    // ignore
  }

  public void dragDropEnd(DragSourceDropEvent dsde) {
    dragNode = null;
  }

  public void dragExit(DragSourceEvent dse) {
    // ignore
  }

  public void dragEnter(DropTargetDragEvent dtde) {
    // ignore
  }

  /** This handles dragging of nodes (from myself) or entries (from the table) */
  public void dragOver(DropTargetDragEvent dtde) {
    final Point cursor = dtde.getLocation();
    final long currentTime = System.currentTimeMillis();
    if (idlePoint == null)
      idlePoint = cursor;

    // determine node over which the user is dragging
    final TreePath path = getPathForLocation(cursor.x, cursor.y);
    final GroupTreeNode target = path == null ? null : (GroupTreeNode) path
        .getLastPathComponent();
    setHighlight1Cell(target);

    // accept or reject
    if (dtde.isDataFlavorSupported(GroupTreeNode.flavor)) {
      // accept: move nodes within tree
      dtde.acceptDrag(DnDConstants.ACTION_MOVE);
    } else if (dtde
        .isDataFlavorSupported(TransferableEntrySelection.flavorInternal)) {
      // check if node accepts explicit assignment
      if (path == null) {
        dtde.rejectDrag();
      } else {
        // this would be the place to check if the dragging entries
        // maybe are in this group already, but I think that's not
        // worth the bother (DropTargetDragEvent does not provide
        // access to the drag object)...
        // it might even be irritating to the user.
        if (target.getGroup().supportsAdd()) {
          // accept: assignment from EntryTable
          dtde.acceptDrag(DnDConstants.ACTION_LINK);
        } else {
          dtde.rejectDrag();
        }
      }
    } else {
      dtde.rejectDrag();
    }

    // auto open
    if (Math.abs(cursor.x - idlePoint.x) < idleMargin
        && Math.abs(cursor.y - idlePoint.y) < idleMargin) {
      if (currentTime - idleStartTime >= idleTimeToExpandNode) {
        if (path != null) {
          expandPath(path);
        }
      }
    } else {
      idlePoint = cursor;
      idleStartTime = currentTime;
    }

    // autoscrolling
    if (currentTime - lastDragAutoscroll < minAutoscrollInterval)
      return;
    final Rectangle r = getVisibleRect();
    final boolean scrollUp = cursor.y - r.y < dragScrollActivationMargin;
    final boolean scrollDown = r.y + r.height - cursor.y < dragScrollActivationMargin;
    final boolean scrollLeft = cursor.x - r.x < dragScrollActivationMargin;
    final boolean scrollRight = r.x + r.width - cursor.x < dragScrollActivationMargin;
    if (scrollUp)
      r.translate(0, -dragScrollDistance);
    else if (scrollDown)
      r.translate(0, +dragScrollDistance);
    if (scrollLeft)
      r.translate(-dragScrollDistance, 0);
    else if (scrollRight)
      r.translate(+dragScrollDistance, 0);
    scrollRectToVisible(r);
    lastDragAutoscroll = currentTime;
  }

  public void dropActionChanged(DropTargetDragEvent dtde) {
    // ignore
  }

  public void drop(DropTargetDropEvent dtde) {
    setHighlight1Cell(null);
    try {
      // initializations common to all flavors
      final Transferable transferable = dtde.getTransferable();
      final Point p = dtde.getLocation();
      final TreePath path = getPathForLocation(p.x, p.y);
      if (path == null) {
        dtde.rejectDrop();
        return;
      }
      final GroupTreeNode target = (GroupTreeNode) path
          .getLastPathComponent();
      // check supported flavors
      if (transferable.isDataFlavorSupported(GroupTreeNode.flavor)) {
        GroupTreeNode source = (GroupTreeNode) transferable
            .getTransferData(GroupTreeNode.flavor);
        if (source == target) {
          dtde.rejectDrop(); // ignore this
          return;
        }
        if (source.isNodeDescendant(target)) {
          dtde.rejectDrop();
          return;
        }
        Enumeration<TreePath> expandedPaths = groupSelector.getExpandedPaths();
        UndoableMoveGroup undo = new UndoableMoveGroup(groupSelector,
            groupSelector.getGroupTreeRoot(), source, target,
            target.getChildCount());
        target.add(source);
        dtde.getDropTargetContext().dropComplete(true);
        // update selection/expansion state
        groupSelector.revalidateGroups(new TreePath[] { new TreePath(
            source.getPath()) }, refreshPaths(expandedPaths));
        groupSelector.concludeMoveGroup(undo, source);
      } else if (transferable
          .isDataFlavorSupported(TransferableEntrySelection.flavorInternal)) {
        final AbstractGroup group = target.getGroup();
        if (!group.supportsAdd()) {
          // this should never happen, because the same condition
          // is checked in dragOver already
          dtde.rejectDrop();
          return;
        }
        final TransferableEntrySelection selection = (TransferableEntrySelection) transferable
            .getTransferData(TransferableEntrySelection.flavorInternal);
        final BibtexEntry[] entries = selection.getSelection();
        int assignedEntries = 0;
        for (int i = 0; i < entries.length; ++i) {
          if (!target.getGroup().contains(entries[i]))
            ++assignedEntries;
        }

        // warn if assignment has undesired side effects (modifies a
        // field != keywords)
        if (!Util.warnAssignmentSideEffects(
            new AbstractGroup[] { group },
            selection.getSelection(), groupSelector
                .getActiveBasePanel().getDatabase(),
            groupSelector.frame))
          return; // user aborted operation

        // if an editor is showing, its fields must be updated
        // after the assignment, and before that, the current
        // edit has to be stored:
        groupSelector.getActiveBasePanel().storeCurrentEdit();

        AbstractUndoableEdit undo = group.add(selection.getSelection());
        if (undo instanceof UndoableChangeAssignment)
          ((UndoableChangeAssignment) undo).setEditedNode(target);
        dtde.getDropTargetContext().dropComplete(true);
        groupSelector.revalidateGroups();
        groupSelector.concludeAssignment(undo, target, assignedEntries);
      } else {
        dtde.rejectDrop();
        return;
      }
    } catch (IOException ioe) {
      // ignore
    } catch (UnsupportedFlavorException e) {
      // ignore
    }
  }

  public void dragExit(DropTargetEvent dte) {
    setHighlight1Cell(null);
  }

  public void dragGestureRecognized(DragGestureEvent dge) {
    GroupTreeNode selectedNode = getSelectedNode();
    if (selectedNode == null)
      return; // nothing to transfer (select manually?)
    Cursor cursor = DragSource.DefaultMoveDrop;
    dragNode = selectedNode;
    dge.getDragSource().startDrag(dge, cursor, selectedNode, this);
  }

  /** Returns the first selected node, or null if nothing is selected. */
  public GroupTreeNode getSelectedNode() {
    TreePath selectionPath = getSelectionPath();
    return selectionPath != null ? (GroupTreeNode) selectionPath
        .getLastPathComponent() : null;
  }

    /**
     * Refresh paths that may have become invalid due to node movements within
     * the tree. This method creates new paths to the last path components
     * (which must still exist) of the specified paths.
     *
     * @param paths
     *            Paths that may have become invalid.
     * @return Refreshed paths that are all valid.
     */
    public Enumeration<TreePath> refreshPaths(Enumeration<TreePath> paths) {
        Vector<TreePath> freshPaths = new Vector<TreePath>();
        while (paths.hasMoreElements()) {
            freshPaths.add(new TreePath(
                    ((DefaultMutableTreeNode)paths.nextElement()
                            .getLastPathComponent()).getPath()));
        }
        return freshPaths.elements();
    }

    /**
     * Refresh paths that may have become invalid due to node movements within
     * the tree. This method creates new paths to the last path components
     * (which must still exist) of the specified paths.
     *
     * @param paths
     *            Paths that may have become invalid.
     * @return Refreshed paths that are all valid.
     */
    public TreePath[] refreshPaths(TreePath[] paths) {
        TreePath[] freshPaths = new TreePath[paths.length];
        for (int i = 0; i < paths.length; ++i) {
            freshPaths[i] = new TreePath(((DefaultMutableTreeNode) paths[i]
                            .getLastPathComponent()).getPath());
        }
        return freshPaths;
    }

  /** Highlights the specified cell or disables highlight if cell == null */
  public void setHighlight1Cell(Object cell) {
    cellRenderer.setHighlight1Cell(cell);
    repaint();
  }

  /** Highlights the specified cells or disables highlight if cells == null */
  public void setHighlight2Cells(Object[] cells) {
    cellRenderer.setHighlight2Cells(cells);
    repaint();
  }

  /** Highlights the specified cells or disables highlight if cells == null */
  public void setHighlight3Cells(Object[] cells) {
    cellRenderer.setHighlight3Cells(cells);
    repaint();
  }
   
    /** Highlights the specified cell or disables highlight if cell == null */
    public void setHighlightBorderCell(GroupTreeNode node) {
        cellRenderer.setHighlightBorderCell(node);
        repaint();
    }

  /** Sort immediate children of the specified node alphabetically. */
  public void sort(GroupTreeNode node, boolean recursive) {
    sortWithoutRevalidate(node, recursive);
    groupSelector.revalidateGroups();
  }

  /** This sorts without revalidation of groups */
  protected void sortWithoutRevalidate(GroupTreeNode node, boolean recursive) {
    if (node.isLeaf())
      return; // nothing to sort
    GroupTreeNode child1, child2;
    int j = node.getChildCount() - 1;
    int lastModified;
    while (j > 0) {
      lastModified = j + 1;
      j = -1;
      for (int i = 1; i < lastModified; ++i) {
        child1 = (GroupTreeNode) node.getChildAt(i - 1);
        child2 = (GroupTreeNode) node.getChildAt(i);
        if (child2.getGroup().getName().compareToIgnoreCase(
            child1.getGroup().getName()) < 0) {
          node.remove(child1);
          node.insert(child1, i);
          j = i;
        }
      }
    }
    if (recursive) {
      for (int i = 0; i < node.getChildCount(); ++i) {
        sortWithoutRevalidate((GroupTreeNode) node.getChildAt(i), true);
      }
    }
  }

  /** Expand this node and all its children. */
  public void expandSubtree(GroupTreeNode node) {
    for (Enumeration<GroupTreeNode> e = node.depthFirstEnumeration(); e.hasMoreElements();)
      expandPath(new TreePath(e.nextElement().getPath()));
  }

  /** Collapse this node and all its children. */
  public void collapseSubtree(GroupTreeNode node) {
    for (Enumeration<GroupTreeNode> e = node.depthFirstEnumeration(); e.hasMoreElements();)
      collapsePath(new TreePath((e.nextElement())
          .getPath()));
  }

  /**
   * Returns true if the node specified by path has at least one descendant
   * that is currently expanded.
   */
  public boolean hasExpandedDescendant(TreePath path) {
    GroupTreeNode node = (GroupTreeNode) path.getLastPathComponent();
    for (Enumeration<GroupTreeNode> e = node.children(); e.hasMoreElements();) {
      GroupTreeNode child = e.nextElement();
      if (child.isLeaf())
        continue; // don't care about this case
      TreePath pathToChild = path.pathByAddingChild(child);
      if (isExpanded(pathToChild) || hasExpandedDescendant(pathToChild))
        return true;
    }
    return false;
  }

  /**
   * Returns true if the node specified by path has at least one descendant
   * that is currently collapsed.
   */
  public boolean hasCollapsedDescendant(TreePath path) {
    GroupTreeNode node = (GroupTreeNode) path.getLastPathComponent();
    for (Enumeration<GroupTreeNode> e = node.children(); e.hasMoreElements();) {
      GroupTreeNode child = e.nextElement();
      if (child.isLeaf())
        continue; // don't care about this case
      TreePath pathToChild = path.pathByAddingChild(child);
      if (isCollapsed(pathToChild) || hasCollapsedDescendant(pathToChild))
        return true;
    }
    return false;
  }
}
TOP

Related Classes of net.sf.jabref.groups.GroupsTree

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.