/**
*
*/
package photoSpreadObjects.photoSpreadComponents;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import photoSpreadUtilities.Const;
import photoSpreadUtilities.Misc;
import photoSpreadUtilities.Const.CursorMoveEffect;
import photoSpreadUtilities.Const.CursorOffScreen;
import photoSpreadUtilities.Const.Direction;
/**
* @author paepcke
*
* Class to handle the selection of items in the Workspace
* using keyboard and mouse wheel.
*/
public class WorkspaceSelector {
static private Workspace _workspace;
static private ArrayList<PhotoSpreadAddable> _labels;
static private DraggableLabel _labelWithCursor = null;
static private int _displayedLabelIndex = 0;
static private int _indexAtXactionStart = 0;
static private int _pageAtXactionStart = 0;
static private Direction _direction = Direction.INDETERMINATE;
private static CursorOffScreen _cursorVisibility = CursorOffScreen.NO;
static private boolean _inSelectionXaction = false;
/****************************************************
* Constructor(s)
*****************************************************/
public static void init (Workspace theWorkspace) {
_workspace = theWorkspace;
_labels = getDisplayedLabels();
Misc.bindKey(_workspace.getWorkspacePanel(), "shift RIGHT", extendSelectionToRight);
Misc.bindKey(_workspace.getWorkspacePanel(), "shift LEFT", extendSelectionToLeft);
Misc.bindKey(_workspace.getWorkspacePanel(), "shift UP", extendSelectionUp);
Misc.bindKey(_workspace.getWorkspacePanel(), "shift DOWN", extendSelectionDown);
Misc.bindKey(_workspace.getWorkspacePanel(), "RIGHT", cursorToRight);
Misc.bindKey(_workspace.getWorkspacePanel(), "LEFT", cursorToLeft);
Misc.bindKey(_workspace.getWorkspacePanel(), "UP", cursorUp);
Misc.bindKey(_workspace.getWorkspacePanel(), "DOWN", cursorDown);
}
/****************************************************
* Actions
*****************************************************/
static Action extendSelectionToRight = new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execExtendSelectionToRight();
}
};
static Action extendSelectionToLeft= new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execExtendSelectionToLeft();
}
};
static Action extendSelectionUp= new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execExtendSelectionUp();
}
};
static Action extendSelectionDown= new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execExtendSelectionDown();
}
};
static Action cursorToRight = new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execCursorToRight ();
}
};
static Action cursorToLeft = new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execCursorToLeft ();
}
};
static Action cursorUp = new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execCursorUp();
}
};
static Action cursorDown = new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
execCursorDown();
}
};
/****************************************************
* Methods
*****************************************************/
/**
* Called from Next/Prev/Home/Last button handlers
* to warn of user's manual page changes. We handle
* those gracefully, not closing the selection transaction.
*
* @param pageFlipDirection Direction of page flip: Direction.FORWARD
* or Direction.BACKWARD.
*/
public static void userChangedPage (Direction pageFlipDirection) {
if ((!_inSelectionXaction) ||
(_direction == Direction.INDETERMINATE))
return;
if (pageFlipDirection == Direction.FORWARD) {
_displayedLabelIndex = 0;
_cursorVisibility = CursorOffScreen.NO;
}
else {
_displayedLabelIndex = _labels.size() - 1;
_cursorVisibility = CursorOffScreen.NO;
}
}
/**
* Do what all selection extensions do at the outset.
* We follow the Windows File Explorer Thumbnail View
* example, which first de-selects everything.
*/
private static void startSelectionXaction(Direction direction) {
_workspace.deSelectAll();
_labels = new ArrayList<PhotoSpreadAddable> (_workspace.getDrawnLabels().values());
PhotoSpreadAddable label = _workspace.getLastLabelClicked();
if (label == null)
_displayedLabelIndex = 0;
// A previously clicked label exists:
else {
_displayedLabelIndex = _labels.indexOf(label);
// HOWEVER: when pages are flipped objects get
// reloaded. So above indexOf() call can fail.
// Don't think this should happen given that
// the next/prev buttons call userChangedPage().
// But just in case: do something reasonable:
if ((_displayedLabelIndex < 0) &&
(direction == Direction.FORWARD))
_displayedLabelIndex = 0;
if ((_displayedLabelIndex < 0) &&
(direction == Direction.BACKWARD))
_displayedLabelIndex = _labels.size() - 1;
}
_indexAtXactionStart = _displayedLabelIndex;
_pageAtXactionStart = _workspace.getPage();
_cursorVisibility = CursorOffScreen.NO;
_workspace.selectObject(_labels.get(_displayedLabelIndex), true);
_direction = direction;
if (_direction == Direction.FORWARD)
incDisplayLabelIndex();
else
decDisplayLabelIndex();
_inSelectionXaction = true;
}
protected static void endSelectionXaction() {
setCursorMark(Const.NOT_SELECTED);
_inSelectionXaction = false;
}
private static void execExtendSelectionToRight() {
if (!_inSelectionXaction) {
startSelectionXaction(Direction.FORWARD);
}
// If sitting on last obj of last page, do nothing:
if (_cursorVisibility == CursorOffScreen.RIGHT)
return;
// Did user just change direction?
if (_direction == Direction.BACKWARD) {
_direction = Direction.FORWARD;
if (incDisplayLabelIndex() == CursorMoveEffect.ON_LAST_PAGE)
return;
}
setCursorMark(false);
if ((_displayedLabelIndex == _indexAtXactionStart) &&
(_workspace.getPage() ==_pageAtXactionStart)) {
_workspace.selectObject(_labels.get(_displayedLabelIndex), true);
if (incDisplayLabelIndex() == CursorMoveEffect.ON_LAST_PAGE)
return;
}
if (_displayedLabelIndex >= _labels.size())
incDisplayLabelIndex();
_workspace.flipObjectSelection(_labels.get(_displayedLabelIndex), true);
incDisplayLabelIndex();
}
private static void execExtendSelectionToLeft() {
if (!_inSelectionXaction) {
startSelectionXaction(Direction.BACKWARD);
}
// If cursor is already off screen on
// the left, do nothing:
if (_cursorVisibility == CursorOffScreen.LEFT)
return;
// Did user just change direction from moving
// forward (right-arrow key presses) to backward?
if (_direction == Direction.FORWARD) {
_direction = Direction.BACKWARD;
if (decDisplayLabelIndex() == CursorMoveEffect.ON_FIRST_PAGE)
return;
}
setCursorMark(false);
// Obj where selection transaction started is special:
// It always stays selected:
if ((_displayedLabelIndex == _indexAtXactionStart) &&
(_workspace.getPage() ==_pageAtXactionStart)) {
_workspace.selectObject(_labels.get(_displayedLabelIndex), true);
if (decDisplayLabelIndex() == CursorMoveEffect.ON_FIRST_PAGE)
return;
}
if (_displayedLabelIndex <= -1)
decDisplayLabelIndex();
_workspace.flipObjectSelection(_labels.get(_displayedLabelIndex), true);
decDisplayLabelIndex();
}
private static void execExtendSelectionUp() {
for (int i = 0; i < _workspace.getWorkspacePanel().getColumns(); i++) {
execExtendSelectionToLeft();
}
}
private static void execExtendSelectionDown() {
for (int i = 0; i < _workspace.getWorkspacePanel().getColumns(); i++) {
execExtendSelectionToRight();
}
}
private static void execCursorToRight () {
if (!_inSelectionXaction) {
startSelectionXaction(Direction.FORWARD);
}
// If sitting on last obj of last page, do nothing:
if (_cursorVisibility == CursorOffScreen.RIGHT)
return;
// Did user just change direction?
if (_direction == Direction.BACKWARD) {
_direction = Direction.FORWARD;
// Need to move forward by two, b/c
// the _displayedLabelIndex is always one
// ahead during left-movement:
if (_cursorVisibility != CursorOffScreen.LEFT)
incDisplayLabelIndex();
incDisplayLabelIndex();
}
if (_displayedLabelIndex >= _labels.size())
incDisplayLabelIndex();
setCursorMark(Const.SELECTED);
incDisplayLabelIndex();
}
private static void execCursorToLeft() {
if (!_inSelectionXaction) {
startSelectionXaction(Direction.BACKWARD);
}
// If cursor is already off screen on
// the left, do nothing:
if (_cursorVisibility == CursorOffScreen.LEFT)
return;
// Did user just switch direction?
if (_direction == Direction.FORWARD) {
_direction = Direction.BACKWARD;
// Need to backtrack by one, b/c
// the _displayedLabelIndex is always one
// ahead:
if (_cursorVisibility != CursorOffScreen.RIGHT)
decDisplayLabelIndex();
decDisplayLabelIndex();
}
if (_displayedLabelIndex <= -1)
decDisplayLabelIndex();
setCursorMark(Const.SELECTED);
decDisplayLabelIndex();
}
private static void execCursorUp() {
for (int i = 0; i < _workspace.getWorkspacePanel().getColumns(); i++) {
execCursorToLeft();
}
}
private static void execCursorDown() {
for (int i = 0; i < _workspace.getWorkspacePanel().getColumns(); i++) {
execCursorToRight();
}
}
/**
* Increments ptr to currently targeted label in the Workspace.
* Attempts to flip page if necessary.
* @return Enum CursorMoveEffect: SAME_PAGE if no page flipping was required.
* FLIPPED_PAGE if Workspace was made to advance one page, or ON_LAST_PAGE
* if page advance would have been required, but Workspace was on last page.
*/
private static CursorMoveEffect incDisplayLabelIndex () {
if (_cursorVisibility == CursorOffScreen.RIGHT)
return CursorMoveEffect.ON_LAST_PAGE;
if ((_workspace.getPage() == _workspace.getLastPage()) &&
(_displayedLabelIndex >= _labels.size())) {
_displayedLabelIndex = _labels.size() - 1;
_cursorVisibility = CursorOffScreen.RIGHT;
return CursorMoveEffect.ON_LAST_PAGE;
}
_displayedLabelIndex++;
// Cursor now definitely not off left edge:
if (_cursorVisibility == CursorOffScreen.LEFT)
_cursorVisibility = CursorOffScreen.NO;
if (_displayedLabelIndex > _labels.size()) {
boolean pageFlipHappened = _workspace.nextPage();
if (pageFlipHappened) {
_labels = getDisplayedLabels();
_displayedLabelIndex = 0;
return CursorMoveEffect.FLIPPED_PAGE;
}
else {
// Remember that cursor now off to the right:
_cursorVisibility = CursorOffScreen.RIGHT;
// Just keep pointing to the last label on that last page:
_displayedLabelIndex = _labels.size() - 1;
return CursorMoveEffect.ON_LAST_PAGE;
}
}
return CursorMoveEffect.SAME_PAGE;
}
/**
* Decrements ptr to currently targeted label in the Workspace.
* Attempts to flip page if necessary.
* @return Enum CursorMoveEffect: SAME_PAGE if no page flipping was required.
* FLIPPED_PAGE if Workspace was successfully made to go back one page, or ON_FIRST_PAGE
* if page decrement would have been required, but Workspace was on first page.
*/
private static CursorMoveEffect decDisplayLabelIndex () {
if (_cursorVisibility == CursorOffScreen.LEFT)
return CursorMoveEffect.ON_FIRST_PAGE;
if ((_workspace.getPage() == 0) &&
(_displayedLabelIndex == 0)) {
_cursorVisibility = CursorOffScreen.LEFT;
return CursorMoveEffect.ON_FIRST_PAGE;
}
_displayedLabelIndex--;
// Cursor now definitely not off right edge:
if (_cursorVisibility == CursorOffScreen.RIGHT)
_cursorVisibility = CursorOffScreen.NO;
if (_displayedLabelIndex < -1) {
boolean pageFlipHappened = _workspace.prevPage();
if (pageFlipHappened) {
_labels = getDisplayedLabels();
_displayedLabelIndex = _labels.size() - 1;
return CursorMoveEffect.FLIPPED_PAGE;
}
else {
// Remember that cursor is now off left edge:
_cursorVisibility = CursorOffScreen.LEFT;
// Just keep pointing to the first label on that first page:
_displayedLabelIndex = 0;
return CursorMoveEffect.ON_FIRST_PAGE;
}
}
return CursorMoveEffect.SAME_PAGE;
}
private static void setCursorMark (boolean setTo) {
DraggableLabel label = null;
// If we have previously set another label to be the one
// with the cursor, then erase the cursor-marking border:
if (_labelWithCursor != null) {
_labelWithCursor.setBorder(null);
// If that formerly cursor-marked label was selected
// before we changed its border to the cursor border,
// then restore the selection highlight on that label:
if (_workspace.isObjectSelected(_labelWithCursor))
_labelWithCursor.highlight();
_labelWithCursor.repaint();
}
if (setTo == Const.SELECTED) {
// Set the border of the current label to indicate
// that the cursor is on this label now:
label = (DraggableLabel) _labels.get(_displayedLabelIndex);
label.setBorder(BorderFactory.createLineBorder(Const.activeCellFrameColor));
label.repaint();
}
_labelWithCursor = label;
}
private static ArrayList<PhotoSpreadAddable> getDisplayedLabels () {
return new ArrayList<PhotoSpreadAddable> (_workspace.getDrawnLabels().values());
}
}