/* * Copyright 2000-2011 JetBrains s.r.o. * * Licensed 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 com.intellij.ui; import com.intellij.ide.dnd.*; import com.intellij.ui.awt.RelativeRectangle; import com.intellij.util.Function; import com.intellij.util.ui.EditableModel; import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; import static com.intellij.ui.RowsDnDSupport.RefinedDropSupport.Position.*; /** * @author Konstantin Bulenkov */ public class RowsDnDSupport { private RowsDnDSupport() { } public static void install(@NotNull final JTable table, @NotNull final EditableModel model) { table.setDragEnabled(true); installImpl(table, model); } public static void install(@NotNull final JList list, @NotNull final EditableModel model) { list.setDragEnabled(true); installImpl(list, model); } public static void install(@NotNull final JTree tree, @NotNull final EditableModel model) { tree.setDragEnabled(true); installImpl(tree, model); } private static void installImpl(@NotNull final JComponent component, @NotNull final EditableModel model) { component.setTransferHandler(new TransferHandler(null)); DnDSupport.createBuilder(component) .setBeanProvider(info -> { final Point p = info.getPoint(); return new DnDDragStartBean(new RowDragInfo(component, Integer.valueOf(getRow(component, p)))); }) .setTargetChecker(new DnDTargetChecker() { @Override public boolean update(DnDEvent event) { final Object o = event.getAttachedObject(); if (! (o instanceof RowDragInfo) || ((RowDragInfo)o).component != component) { event.setDropPossible(false, ""); return true; } event.setDropPossible(true); int oldIndex = ((RowDragInfo)o).row; int newIndex = getRow(component, event.getPoint()); if (newIndex == -1) { event.setDropPossible(false, ""); return true; } Rectangle cellBounds = getCellBounds(component, newIndex); if (model instanceof RefinedDropSupport) { RefinedDropSupport.Position position = ((RefinedDropSupport)model).isDropInto(component, oldIndex, newIndex) ? INTO : (event.getPoint().y < cellBounds.y + cellBounds.height / 2) ? ABOVE : BELOW; boolean canDrop = ((RefinedDropSupport)model).canDrop(oldIndex, newIndex, position); event.setDropPossible(canDrop); if (canDrop && oldIndex != newIndex) { if (position == BELOW) { cellBounds.y += cellBounds.height - 2; } RelativeRectangle rectangle = new RelativeRectangle(component, cellBounds); switch (position) { case INTO: event.setHighlighting(rectangle, DnDEvent.DropTargetHighlightingType.RECTANGLE); break; case ABOVE: case BELOW: rectangle.getDimension().height = 2; event.setHighlighting(rectangle, DnDEvent.DropTargetHighlightingType.FILLED_RECTANGLE); break; } return true; } else { event.hideHighlighter(); return true; } } else { if (oldIndex == newIndex) { // Drag&Drop always starts with new==old and we shouldn't display 'rejecting' cursor in this case return true; } boolean canExchange = model.canExchangeRows(oldIndex, newIndex); if (canExchange) { if (oldIndex < newIndex) { cellBounds.y += cellBounds.height - 2; } RelativeRectangle rectangle = new RelativeRectangle(component, cellBounds); rectangle.getDimension().height = 2; event.setDropPossible(true); event.setHighlighting(rectangle, DnDEvent.DropTargetHighlightingType.FILLED_RECTANGLE); } else { event.setDropPossible(false); } return true; } } }) .setDropHandler(new DnDDropHandler() { @Override public void drop(DnDEvent event) { final Object o = event.getAttachedObject(); final Point p = event.getPoint(); if (o instanceof RowDragInfo && ((RowDragInfo)o).component == component) { int oldIndex = ((RowDragInfo)o).row; if (oldIndex == -1) return; int newIndex = getRow(component, p); if (newIndex == -1) { newIndex = getRowCount(component) - 1; } if (oldIndex != newIndex) { if (model instanceof RefinedDropSupport) { Rectangle cellBounds = getCellBounds(component, newIndex); RefinedDropSupport.Position position = ((RefinedDropSupport)model).isDropInto(component, oldIndex, newIndex) ? INTO : (event.getPoint().y < cellBounds.y + cellBounds.height / 2) ? ABOVE : BELOW; if (((RefinedDropSupport)model).canDrop(oldIndex, newIndex, position)) { ((RefinedDropSupport)model).drop(oldIndex, newIndex, position); } } else { if (model.canExchangeRows(oldIndex, newIndex)) { model.exchangeRows(oldIndex, newIndex); setSelectedRow(component, newIndex); } } } } event.hideHighlighter(); } }) .install(); } private static int getRow(JComponent component, Point point) { if (component instanceof JTable) { return ((JTable)component).rowAtPoint(point); } else if (component instanceof JList) { return ((JList)component).locationToIndex(point); } else if (component instanceof JTree) { return ((JTree)component).getClosestRowForLocation(point.x, point.y); } else { throw new IllegalArgumentException("Unsupported component: " + component); } } private static int getRowCount(JComponent component) { if (component instanceof JTable) { return ((JTable)component).getRowCount(); } else if (component instanceof JList) { return ((JList)component).getModel().getSize(); } else if (component instanceof JTree) { return ((JTree)component).getRowCount(); } else { throw new IllegalArgumentException("Unsupported component: " + component); } } private static Rectangle getCellBounds(JComponent component, int row) { if (component instanceof JTable) { Rectangle rectangle = ((JTable)component).getCellRect(row, 0, true); rectangle.width = component.getWidth(); return rectangle; } else if (component instanceof JList) { return ((JList)component).getCellBounds(row, row); } else if (component instanceof JTree) { return ((JTree)component).getRowBounds(row); } else { throw new IllegalArgumentException("Unsupported component: " + component); } } private static void setSelectedRow(JComponent component, int row) { if (component instanceof JTable) { ((JTable)component).getSelectionModel().setSelectionInterval(row, row); } else if (component instanceof JList) { ((JList)component).setSelectedIndex(row); } else if (component instanceof JTree) { ((JTree)component).setSelectionRow(row); } else { throw new IllegalArgumentException("Unsupported component: " + component); } } private static class RowDragInfo { public final JComponent component; public final int row; RowDragInfo(JComponent component, int row) { this.component = component; this.row = row; } } public interface RefinedDropSupport { enum Position {ABOVE, INTO, BELOW} boolean isDropInto(JComponent component, int oldIndex, int newIndex); //oldIndex may be equal to newIndex boolean canDrop(int oldIndex, int newIndex, @NotNull Position position); //This method is also responsible for selection changing void drop(int oldIndex, int newIndex, @NotNull Position position); } }