/*
* #%L
* gitools-ui-app
* %%
* Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group
* %%
* 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 3 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, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
package org.gitools.ui.app.heatmap.panel;
import org.gitools.api.matrix.view.Direction;
import org.gitools.api.matrix.view.IMatrixViewDimension;
import org.gitools.heatmap.AbstractMatrixViewDimension;
import org.gitools.heatmap.header.HeatmapHeader;
import org.gitools.ui.core.HeatmapPosition;
import org.gitools.ui.platform.os.OSProperties;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import static org.gitools.ui.core.interaction.Interaction.*;
import static org.gitools.ui.core.interaction.InteractionStatus.getInteractionStatus;
import static org.gitools.ui.core.interaction.InteractionStatus.setInteractionStatus;
public class HeatmapHeaderMouseController implements MouseListener, MouseMotionListener, MouseWheelListener {
private final JViewport viewPort;
private final HeatmapHeaderPanel headerPanel;
private final HeatmapPanel panel;
private final boolean horizontal;
private Point point;
private HeatmapPosition position;
private int selLast;
private Point startPoint;
private Point startScrollValue;
private final IMatrixViewDimension dimension;
private final List<HeatmapMouseListener> listeners = new ArrayList<>(1);
private int selectionMoveLastIndex;
private boolean selectionHasMoved = false;
private int ctrlMask = OSProperties.get().getCtrlMask();
private int shiftMask = OSProperties.get().getShiftMask();
private int altMask = OSProperties.get().getAltMask();
private int metaMask = OSProperties.get().getMetaMask();
private Timer timer;
private HeatmapPanelInputProcessor inputProcessor;
private MouseEvent lastMovingEvent;
public HeatmapHeaderMouseController(HeatmapPanel panel,
HeatmapPanelInputProcessor inputProcessor,
boolean horizontal) {
this.viewPort = horizontal ? panel.getColumnViewPort() : panel.getRowViewPort();
this.headerPanel = horizontal ? panel.getColumnPanel() : panel.getRowPanel();
this.panel = panel;
this.horizontal = horizontal;
this.dimension = horizontal ? panel.getHeatmap().getColumns() : panel.getHeatmap().getRows();
viewPort.addMouseListener(this);
viewPort.addMouseMotionListener(this);
viewPort.addMouseWheelListener(this);
this.inputProcessor = inputProcessor;
setInteractionStatus(none);
this.timer = new Timer(50, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
processStoredEvents();
}
});
}
private void processStoredEvents() {
if (lastMovingEvent != null) {
updateSelectionMove(lastMovingEvent, true);
lastMovingEvent = null;
}
}
public void addHeatmapMouseListener(HeatmapMouseListener listener) {
listeners.add(listener);
}
@Override
public void mouseClicked(MouseEvent e) {
// Skip right click
if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
return;
}
panel.requestFocusInWindow();
int index = convertToIndex(e);
if (!isValidIndex(index)) {
return;
}
int row = horizontal ? -1 : index;
int col = horizontal ? index : -1;
for (HeatmapMouseListener l : listeners)
l.mouseClicked(row, col, e);
}
private int convertToIndex(MouseEvent e) {
point = e.getPoint();
Point viewPosition = viewPort.getViewPosition();
point.translate(viewPosition.x, viewPosition.y);
position = headerPanel.getDrawer().getPosition(point);
return horizontal ? position.column : position.row;
}
@Override
public void mousePressed(MouseEvent e) {
// Skip right click
if ((e.getModifiers() & MouseEvent.BUTTON3_MASK) != 0) {
return;
}
panel.configureHeaders(e);
// check if it's a already selected
int index = convertToIndex(e);
if (isSelectedIndex(index)) {
setInteractionStatus(movingSelected);
} else {
setInteractionStatus(selectingRowsAndCols);
}
switch (getInteractionStatus()) {
case selectingRowsAndCols:
updateSelection(e, false);
break;
case movingSelected:
selectionHasMoved = false;
updateSelectionMove(e, false);
break;
}
panel.requestFocusInWindow();
}
private boolean isSelectedIndex(int index) {
return isValidIndex(index) && dimension.getSelected().contains(dimension.getLabel(index));
}
@Override
public void mouseReleased(MouseEvent e) {
panel.mouseReleased(e);
int modifiers = e.getModifiers();
boolean altDown = ((modifiers & altMask) != 0);
int index = convertToIndex(e);
if (getInteractionStatus() == movingSelected) {
if (!selectionHasMoved) {
//There was no dragging, user wanted to unselect the selection
inputProcessor.switchSelection(dimension, index, altDown);
setLeading(e);
setInteractionStatus(none);
} else {
setInteractionStatus(none);
dimension.forceUpdate(AbstractMatrixViewDimension.PROPERTY_VISIBLE);
//updateSelectionMove(e, true);
selectionHasMoved = false;
}
}
}
private void setLeading(MouseEvent e) {
int index = convertToIndex(e);
if (isValidIndex(index)) {
dimension.setFocus(dimension.getLabel(index));
}
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseDragged(MouseEvent e) {
switch (getInteractionStatus()) {
case selectingRowsAndCols:
updateSelection(e, true);
break;
case moving:
updateScroll(e, true);
break;
case movingSelected:
updateSelectionMove(e, true);
break;
}
}
@Override
public void mouseMoved(MouseEvent e) {
int index = convertToIndex(e);
if (!isValidIndex(index)) {
return;
}
int row = horizontal ? -1 : index;
int col = horizontal ? index : -1;
for (HeatmapMouseListener l : listeners)
l.mouseMoved(row, col, e);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
int unitsToScroll = e.getUnitsToScroll();
int modifiers = e.getModifiers();
boolean ctrlDown = ((modifiers & ctrlMask) != 0);
setInteractionStatus((ctrlDown) ? zooming : scrolling);
if (getInteractionStatus() == scrolling) {
HeatmapPosition pos = panel.getScrollPosition();
if (horizontal) {
panel.setScrollColumnPosition(pos.column + unitsToScroll);
} else {
panel.setScrollRowPosition(pos.row + unitsToScroll);
}
} else {
if (inputProcessor.isKeyPressed(KeyEvent.VK_C) ||
inputProcessor.isKeyPressed(KeyEvent.VK_R)) {
inputProcessor.zoomHeatmap(unitsToScroll > 0 ? 1 : -1);
} else {
point = e.getPoint();
Point viewPosition = viewPort.getViewPosition();
point.translate(viewPosition.x, viewPosition.y);
HeatmapHeader header = headerPanel.getHeaderDrawer().getHeader(point);
if (inputProcessor.isKeyPressed(KeyEvent.VK_M)) {
int margin = header.getMargin() + unitsToScroll * -2;
if (margin < 1) {
margin = 1;
} else {
int size = header.getSize() + unitsToScroll * -2;
if (size < 10) {
size = 10;
}
header.setSize(size);
}
header.setMargin(margin);
} else {
int size = header.getSize() + unitsToScroll * -2;
if (size < 10) {
size = 10;
}
header.setSize(size);
}
header.getHeatmapDimension().updateHeaders();
}
}
}
private void updateSelectionMove(MouseEvent e, boolean dragging) {
if (!timer.isRunning()) {
timer.start();
} else {
lastMovingEvent = e;
return;
}
// Scroll heatmap if needed
Point point = e.getPoint();
if (horizontal) {
int left = (int) point.getX() - viewPort.getWidth();
if (left > 0) {
panel.setScrollColumnValue(panel.getScrollValue().x + left);
} else {
if (point.getX() < 0) {
panel.setScrollColumnValue(panel.getScrollValue().x + (int) point.getX());
}
}
} else {
int down = (int) point.getY() - viewPort.getHeight();
if (down > 0) {
panel.setScrollRowValue(panel.getScrollValue().y + down);
} else {
if (point.getY() < 0) {
panel.setScrollRowValue(panel.getScrollValue().y + (int) point.getY());
}
}
}
int index = convertToIndex(e);
if (!isValidIndex(index)) {
return;
}
if (!dragging) {
selectionMoveLastIndex = index;
} else {
int indexDiff = selectionMoveLastIndex - index;
selectionMoveLastIndex = index;
if (indexDiff > 0) {
selectionHasMoved = true;
for (int i = 0; i < indexDiff; i++) {
dimension.move(Direction.LEFT, dimension.getSelected());
inputProcessor.shiftSelStart(dimension, -1);
}
}
if (indexDiff < 0) {
selectionHasMoved = true;
for (int i = 0; i > indexDiff; i--) {
dimension.move(Direction.RIGHT, dimension.getSelected());
inputProcessor.shiftSelStart(dimension, 1);
}
}
}
}
private void updateSelection(MouseEvent e, boolean dragging) {
int index = convertToIndex(e);
if (!isValidIndex(index)) {
return;
}
boolean indexChanged = (selLast != index);
if (indexChanged) {
setLeading(e);
}
selLast = index;
int modifiers = e.getModifiers();
boolean shiftDown = ((modifiers & shiftMask) != 0);
if (!dragging && !shiftDown) {
inputProcessor.switchSelection(dimension, index, false);
} else if (shiftDown) {
int lastSelected = horizontal ? inputProcessor.getLastSelectedCol() : inputProcessor.getLastSelectedRow();
inputProcessor.addToSelected(lastSelected, index, dimension);
inputProcessor.setLastSelected(index, horizontal);
}
}
private int getIndexCount() {
return dimension.size();
}
private boolean isValidIndex(int index) {
if (index < 0 || index >= getIndexCount()) {
return false;
}
return true;
}
private void updateScroll(MouseEvent e, boolean dragging) {
point = e.getPoint();
if (!dragging) {
startPoint = point;
startScrollValue = panel.getScrollValue();
} else {
int widthOffset = point.x - startPoint.x;
int heightOffset = point.y - startPoint.y;
if (horizontal) {
panel.setScrollColumnValue(startScrollValue.x - widthOffset);
} else {
panel.setScrollRowValue(startScrollValue.y - heightOffset);
}
}
}
}