blob: 5da2dcaa927588587ebe0fc69693d4d209a0365e [file] [log] [blame]
/*
* 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(new Function<DnDActionInfo, DnDDragStartBean>() {
@Override
public DnDDragStartBean fun(DnDActionInfo 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();
event.setDropPossible(o instanceof RowDragInfo && ((RowDragInfo)o).component == component);
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);
}
}