package es.icarto.gvsig.navtable.navigation;
import static es.udc.cartolab.gvsig.navtable.AbstractNavTable.EMPTY_REGISTER;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import org.apache.log4j.Logger;
import com.hardcode.gdbms.driver.exceptions.ReadDriverException;
import com.iver.andami.PluginServices;
import com.iver.cit.gvsig.fmap.layers.FBitSet;
import com.iver.cit.gvsig.fmap.layers.SelectableDataSource;
import com.iver.cit.gvsig.fmap.layers.SelectionEvent;
import com.iver.cit.gvsig.fmap.layers.SelectionListener;
import com.iver.utiles.extensionPoints.ExtensionPoints;
import com.iver.utiles.extensionPoints.ExtensionPointsSingleton;
import es.udc.cartolab.gvsig.navtable.AbstractNavTable;
import es.udc.cartolab.gvsig.navtable.listeners.PositionEvent;
import es.udc.cartolab.gvsig.navtable.listeners.PositionEventSource;
import es.udc.cartolab.gvsig.navtable.listeners.PositionListener;
public class NavigationHandler implements ActionListener, SelectionListener {
private static final Logger logger = Logger
.getLogger(NavigationHandler.class);
private final PositionEventSource positionEventSource = new PositionEventSource();
private JButton firstB = null;
private JButton beforeB = null;
private JTextField posTF = null;
private JLabel totalLabel = null;
private JButton nextB = null;
private JButton lastB = null;
private JPanel navToolBar;
// Selection widgets
private JCheckBox onlySelectedCB = null;
private JCheckBox alwaysSelectCB = null;
private JButton selectionB = null;
private JPanel optionsPanel;
private long currentPosition = 0;
private final AbstractNavTable nt;
private RowSorter<? extends SelectableDataSource> sorter;
public NavigationHandler(AbstractNavTable nt) {
this.nt = nt;
initWidgets();
sorter = new NTRowSorter<SelectableDataSource>(nt.getRecordset());
}
public JPanel getToolBar() {
return navToolBar;
}
private void initWidgets() {
initNavigationWidgets();
initSelectionWidgets();
}
private void initNavigationWidgets() {
registerNavTableButtonsOnNavigationToolBarExtensionPoint();
navToolBar = new JPanel(new FlowLayout());
navToolBar.add(firstB);
navToolBar.add(beforeB);
navToolBar.add(posTF);
navToolBar.add(totalLabel);
navToolBar.add(nextB);
navToolBar.add(lastB);
}
private void initSelectionWidgets() {
onlySelectedCB = getNavTableCheckBox(onlySelectedCB, "selectedCheckBox");
alwaysSelectCB = getNavTableCheckBox(alwaysSelectCB, "selectCheckBox");
optionsPanel = new JPanel(new FlowLayout());
optionsPanel.add(onlySelectedCB);
optionsPanel.add(alwaysSelectCB);
}
private void registerNavTableButtonsOnNavigationToolBarExtensionPoint() {
firstB = getNavTableButton(firstB, "/go-first.png",
"goFirstButtonTooltip");
beforeB = getNavTableButton(beforeB, "/go-previous.png",
"goPreviousButtonTooltip");
posTF = new JTextField(5);
posTF.addActionListener(this);
totalLabel = new JLabel();
nextB = getNavTableButton(nextB, "/go-next.png", "goNextButtonTooltip");
lastB = getNavTableButton(lastB, "/go-last.png", "goLastButtonTooltip");
}
public void next() {
if (isOnlySelected()) {
nextSelected();
} else {
// setPosition(getPosition() + 1);
int viewPos = sorter.convertRowIndexToView((int) getPosition());
int viewLastPos = sorter.getViewRowCount() - 1;
if (viewPos < viewLastPos) {
int modelNextPos = sorter.convertRowIndexToModel(viewPos + 1);
setPosition(modelNextPos);
}
}
}
// int pos = bitset.nextSetBit((int) getPosition() + 1);
// if (pos != EMPTY_REGISTER) {
// setPosition(pos);
// }
/**
* This implementation should be tested with spare selections on big files
* to check if has an acceptable performance
*/
private void nextSelected() {
FBitSet bitset = nt.getRecordset().getSelection();
int viewPos = sorter.convertRowIndexToView((int) getPosition());
for (int i = viewPos + 1; i < sorter.getViewRowCount(); i++) {
int modelPos = sorter.convertRowIndexToModel(i);
if (bitset.get(modelPos)) {
setPosition(modelPos);
return;
}
}
}
public void last() {
if (isOnlySelected()) {
lastSelected();
} else {
// setPosition(getRecordset().getRowCount() - 1);
int viewLastPos = sorter.getViewRowCount() - 1;
int modelLastPos = sorter.convertRowIndexToModel(viewLastPos);
setPosition(modelLastPos);
}
}
// int pos = bitset.length();
// if (pos != 0) {
// setPosition(pos - 1);
// }
public void lastSelected() {
FBitSet bitset = nt.getRecordset().getSelection();
int viewLastPos = sorter.getViewRowCount() - 1;
for (int i = viewLastPos; i >= 0; i--) {
int modelPos = sorter.convertRowIndexToModel(i);
if (bitset.get(modelPos)) {
setPosition(modelPos);
return;
}
}
}
public void first() {
if (isOnlySelected()) {
firstSelected();
} else {
// setPosition(0);
int viewFirstPos = 0;
int modelFirstPos = sorter.convertRowIndexToModel(viewFirstPos);
setPosition(modelFirstPos);
}
}
public void firstSelected() {
FBitSet bitset = nt.getRecordset().getSelection();
int viewFirstPos = 0;
for (int i = viewFirstPos; i < sorter.getViewRowCount(); i++) {
int modelPos = sorter.convertRowIndexToModel(i);
if (bitset.get(modelPos)) {
setPosition(modelPos);
return;
}
}
}
// returns the index of the record in the datasource that matches the
// previous (getPosition() - 1) record in the view
// if the view position is the first (0) it returns -1
public int getPreviousPositionInModel() {
int viewPos = sorter.convertRowIndexToView((int) getPosition());
if (viewPos == 0) {
return -1;
}
int modelPrevPos = sorter.convertRowIndexToModel(viewPos - 1);
return modelPrevPos;
}
public void goToPreviousInView() {
if (isOnlySelected()) {
goToPreviousSelectedInView();
} else {
int modelPrevPos = getPreviousPositionInModel();
if (modelPrevPos > -1) {
setPosition(modelPrevPos);
}
}
}
private int getPreviusPositionSelectedInModel() {
FBitSet bitset = nt.getRecordset().getSelection();
int viewPos = sorter.convertRowIndexToView((int) getPosition());
for (int i = viewPos - 1; i >= 0; i--) {
int modelPos = sorter.convertRowIndexToModel(i);
if (bitset.get(modelPos)) {
return modelPos;
}
}
return -1;
}
// int pos = (int) (getPosition() - 1);
// for (; pos >= 0 && !bitset.get(pos); pos--) {
// ;
// }
// if (pos != EMPTY_REGISTER) {
// setPosition(pos);
// }
private void goToPreviousSelectedInView() {
int modelPos = getPreviusPositionSelectedInModel();
if (modelPos != -1) {
setPosition(modelPos);
}
}
/**
* {@link #init()} method must be called before this
*
* @param newPosition
* zero-based index on recordset
*/
public void setPosition(long newPosition) {
if (!isValidPosition(newPosition)) {
return;
}
try {
if (newPosition >= nt.getRecordset().getRowCount()) {
newPosition = nt.getRecordset().getRowCount() - 1;
} else if (newPosition < EMPTY_REGISTER) {
newPosition = 0;
}
PositionEvent evt = new PositionEvent(this, currentPosition,
newPosition);
positionEventSource.fireBeforePositionChange(evt);
currentPosition = newPosition;
positionEventSource.fireOnPositionChange(evt);
} catch (ReadDriverException e) {
e.printStackTrace();
}
}
public long getPosition() {
return currentPosition;
}
private boolean isValidPosition(long pos) {
if (isOnlySelected()) {
return isRecordSelected(pos);
}
return true;
}
private void setTotalLabelText() {
try {
long numberOfRowsInRecordset = nt.getRecordset().getRowCount();
if (isOnlySelected()) {
totalLabel.setText("/" + "(" + nt.getNumberOfRowsSelected()
+ ") " + numberOfRowsInRecordset);
} else {
totalLabel.setText("/" + numberOfRowsInRecordset);
}
} catch (ReadDriverException e) {
logger.error(e.getStackTrace(), e);
}
}
private void posTFChanged() {
String pos = posTF.getText();
try {
long userViewPos = Long.parseLong(pos) - 1;
int modelPos = sorter.convertRowIndexToModel((int) userViewPos);
setPosition(modelPos);
} catch (NumberFormatException e) {
logger.error(e.getMessage(), e);
refreshGUI(firstB.isEnabled());
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == nextB) {
next();
} else if (e.getSource() == lastB) {
last();
} else if (e.getSource() == firstB) {
first();
} else if (e.getSource() == beforeB) {
goToPreviousInView();
} else if (e.getSource() == posTF) {
posTFChanged();
} else if (e.getSource() == onlySelectedCB) {
if (alwaysSelectCB.isSelected()) {
alwaysSelectCB.setSelected(false);
nt.getRecordset().addSelectionListener(this);
}
if (onlySelectedCB.isSelected()) {
if (!isEmptyRegister()) {
viewOnlySelected();
}
} else {
if (isEmptyRegister()) {
setPosition(0);
}
}
nt.refreshGUI();
} else if (e.getSource() == alwaysSelectCB) {
onlySelectedCB.setSelected(false);
if (alwaysSelectCB.isSelected()) {
nt.getRecordset().removeSelectionListener(this);
} else {
nt.getRecordset().addSelectionListener(this);
}
nt.refreshGUI();
} else if (e.getSource() == selectionB) {
nt.selectCurrentFeature();
nt.refreshGUI();
}
}
/**
* Forces the application to navigate only between selected features.
*/
private void viewOnlySelected() {
if (nt.getNumberOfRowsSelected() == 0) {
setPosition(EMPTY_REGISTER);
}
if (!isRecordSelected()) {
nt.firstSelected();
} else {
refreshGUI(onlySelectedCB.isEnabled());
}
}
/**
* @return true if the current row is selected, false if not.
*/
private boolean isRecordSelected() {
return isRecordSelected(getPosition());
}
private boolean isRecordSelected(long position) {
FBitSet bitset = null;
if (position == EMPTY_REGISTER) {
return false;
}
int pos = Long.valueOf(position).intValue();
if (nt.getRecordset() == null) {
return false;
}
bitset = nt.getRecordset().getSelection();
return bitset.get(pos);
}
@Override
public void selectionChanged(SelectionEvent e) {
/*
* Variable isSomeNavTableForm open is used as workaround to control
* null pointers exceptions when all forms using navtable are closed
* but, for some strange reason, some of the listeners is still active.
*/
// if (!isSomeNavTableFormOpen()) {
// return;
// }
if (onlySelectedCB.isSelected() && !isRecordSelected()) {
nt.first();
}
nt.refreshGUI();
}
public boolean isEmptyRegister() {
return getPosition() == EMPTY_REGISTER;
}
public void addEventListener(PositionListener l) {
positionEventSource.addEventListener(l);
}
public void removeEventListener(PositionListener l) {
positionEventSource.removeEventListener(l);
}
public Component getOptionsPanel() {
return optionsPanel;
}
public void setOnlySelected(boolean bool) {
if (bool != onlySelectedCB.isSelected()) {
onlySelectedCB.doClick();
}
}
public boolean isOnlySelected() {
return onlySelectedCB.isSelected();
}
public void refreshGUI(boolean navEnabled) {
refreshGUISelection(navEnabled);
refreshGUINavigation(navEnabled);
}
private void refreshGUISelection(boolean navEnabled) {
if (alwaysSelectCB.isSelected()) {
nt.clearSelection();
nt.selectCurrentFeature();
}
if (isRecordSelected()) {
ImageIcon imagenUnselect = nt.getIcon("/Unselect.png");
selectionB.setIcon(imagenUnselect);
} else {
ImageIcon imagenSelect = nt.getIcon("/Select.png");
selectionB.setIcon(imagenSelect);
}
selectionB.setEnabled(navEnabled);
alwaysSelectCB.setEnabled(navEnabled);
}
private void refreshGUINavigation(boolean navEnabled) {
firstB.setEnabled(navEnabled);
beforeB.setEnabled(navEnabled);
nextB.setEnabled(navEnabled);
lastB.setEnabled(navEnabled);
if (isEmptyRegister()) {
posTF.setText("");
} else {
// user will set a 1-based index to navigate through layer,
// so we need to adapt it to currentPosition (a zero-based
// index)
try {
int p = sorter.convertRowIndexToView((int) getPosition());
posTF.setText(String.valueOf(p + 1));
} catch (IndexOutOfBoundsException e) {
/*
* fpuga. 10/12/2014. Workaround When the user delete the last
* record the editionChanged in EditionListener is called, and
* the gui is refreshed, but getPosition returns a removed
* position
*/
logger.error(e.getStackTrace(), e);
int p = sorter.convertRowIndexToView((int) getPosition() - 1);
posTF.setText(String.valueOf(p + 1));
}
}
setTotalLabelText();
if (isRecordSelected()) {
posTF.setBackground(Color.YELLOW);
} else {
posTF.setBackground(Color.WHITE);
}
}
public void setSortKeys(List<? extends SortKey> keys) {
sorter.setSortKeys(keys);
refreshGUI(firstB.isEnabled());
}
public List<? extends SortKey> getSortKeys() {
return sorter.getSortKeys();
}
public void modelChanged() {
List<? extends SortKey> sortKeys = sorter.getSortKeys();
sorter = new NTRowSorter<SelectableDataSource>(nt.getRecordset());
sorter.setSortKeys(sortKeys);
refreshGUI(firstB.isEnabled());
}
public void setListeners() {
nt.getRecordset().addSelectionListener(this);
}
public void removeListeners() {
nt.getRecordset().removeSelectionListener(this);
}
// Probably should be removed and use a factory instead
// is duplicated with NavigationHandler
private JButton getNavTableButton(JButton button, String iconName,
String toolTipName) {
JButton but = new JButton(nt.getIcon(iconName));
but.setToolTipText(PluginServices.getText(this, toolTipName));
but.addActionListener(this);
return but;
}
// Probably should be removed and use a factory instead
// is duplicated with NavigationHandler
private JCheckBox getNavTableCheckBox(JCheckBox cb, String toolTipName) {
cb = new JCheckBox(PluginServices.getText(this, toolTipName));
cb.addActionListener(this);
return cb;
}
public void registerNavTableButtonsOnActionToolBarExtensionPoint() {
ExtensionPoints extensionPoints = ExtensionPointsSingleton
.getInstance();
selectionB = getNavTableButton(selectionB, "/Select.png",
"selectionButtonTooltip");
extensionPoints.add(AbstractNavTable.NAVTABLE_ACTIONS_TOOLBAR,
"button-selection", selectionB);
}
@Deprecated
/** fpuga. 19/11/2014. Don't use this method. It's created as a workaround to
* make copyPrevious and copySelected work
*/
public void setPosition(long newPosition, boolean b) {
if (!isValidPosition(newPosition)) {
return;
}
try {
if (newPosition >= nt.getRecordset().getRowCount()) {
newPosition = nt.getRecordset().getRowCount() - 1;
} else if (newPosition < EMPTY_REGISTER) {
newPosition = 0;
}
currentPosition = newPosition;
} catch (ReadDriverException e) {
e.printStackTrace();
}
}
}