/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo 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.
*
* OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.fib.view.widget;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SortOrder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableColumn;
import org.jdesktop.swingx.JXTable;
import org.openflexo.antar.binding.AbstractBinding;
import org.openflexo.fib.controller.FIBController;
import org.openflexo.fib.controller.FIBSelectable;
import org.openflexo.fib.controller.FIBTableDynamicModel;
import org.openflexo.fib.model.FIBTable;
import org.openflexo.fib.model.FIBTableAction;
import org.openflexo.fib.view.FIBWidgetView;
import org.openflexo.fib.view.widget.table.FIBTableActionListener;
import org.openflexo.fib.view.widget.table.FIBTableModel;
import org.openflexo.fib.view.widget.table.FIBTableWidgetFooter;
/**
* Widget allowing to display/edit a list of values
*
* @author sguerin
*/
public class FIBTableWidget extends FIBWidgetView<FIBTable, JTable, Collection<?>> implements TableModelListener, FIBSelectable,
ListSelectionListener {
private static final Logger logger = Logger.getLogger(FIBTableWidget.class.getPackage().getName());
private JXTable _table;
private final JPanel _dynamicComponent;
private final FIBTable _fibTable;
private FIBTableModel _tableModel;
// private ListSelectionModel _listSelectionModel;
private JScrollPane scrollPane;
private FIBTableWidgetFooter footer;
private Vector<Object> selection;
private Object selectedObject;
public FIBTableWidget(FIBTable fibTable, FIBController controller) {
super(fibTable, controller);
_fibTable = fibTable;
_dynamicComponent = new JPanel();
_dynamicComponent.setOpaque(false);
_dynamicComponent.setLayout(new BorderLayout());
footer = new FIBTableWidgetFooter(this);
buildTable();
}
public FIBTable getTable() {
return _fibTable;
}
public FIBTableWidgetFooter getFooter() {
return footer;
}
public FIBTableModel getTableModel() {
if (_tableModel == null) {
_tableModel = new FIBTableModel(_fibTable, this, getController());
}
return _tableModel;
}
/*public JLabel getLabel()
{
if (_label == null) {
_label = new JLabel(_propertyModel.label + " : ", SwingConstants.CENTER);
_label.setText(FlexoLocalization.localizedForKey(_propertyModel.label, _label));
_label.setBackground(InspectorCst.BACK_COLOR);
_label.setFont(DEFAULT_LABEL_FONT);
if (_propertyModel.help != null && !_propertyModel.help.equals(""))
_label.setToolTipText(_propertyModel.help);
}
return _label;
}*/
@Override
public synchronized boolean updateWidgetFromModel() {
List<?> valuesBeforeUpdating = getTableModel().getValues();
Object wasSelected = getSelectedObject();
boolean returned = false;
// logger.info("----------> updateWidgetFromModel() for " + getTable().getName());
if (_fibTable.getEnable().isSet() && _fibTable.getEnable().isValid()) {
Boolean enabledValue = (Boolean) _fibTable.getEnable().getBindingValue(getController());
_table.setEnabled(enabledValue != null && enabledValue);
}
if (notEquals(getValue(), getTableModel().getValues())) {
returned = true;
// boolean debug = false;
// if (getWidget().getName() != null && getWidget().getName().equals("PatternRoleTable")) debug=true;
// if (debug) System.out.println("valuesBeforeUpdating: "+valuesBeforeUpdating);
// if (debug) System.out.println("wasSelected: "+wasSelected);
if (_table.isEditing()) {
if (logger.isLoggable(Level.FINE)) {
logger.fine(getComponent().getName() + " - Table is currently editing at col:" + _table.getEditingColumn() + " row:"
+ _table.getEditingRow());
}
_table.getCellEditor().cancelCellEditing();
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine(getComponent().getName() + " - Table is NOT currently edited ");
}
}
if (logger.isLoggable(Level.FINE)) {
logger.fine(getComponent().getName() + " updateWidgetFromModel() with " + getValue() + " dataObject=" + getDataObject());
}
if (getValue() == null) {
getTableModel().setValues(Collections.emptyList());
}
if (getValue() instanceof List && !getValue().equals(valuesBeforeUpdating)) {
getTableModel().setValues(getValue());
}
footer.setModel(getDataObject());
}
// We restore value if and only if we represent same table
if (equals(getTableModel().getValues(), valuesBeforeUpdating) && wasSelected != null) {
returned = true;
setSelectedObject(wasSelected);
} else if (areSameValuesOrderIndifferent(getTableModel().getValues(), valuesBeforeUpdating)) {
// Same values, only order differs, in this case, still select right object
returned = true;
setSelectedObject(wasSelected);
} else {
if (getComponent().getSelected().isValid() && getComponent().getSelected().getBindingValue(getController()) != null) {
Object newSelectedObject = getComponent().getSelected().getBindingValue(getController());
if (returned = notEquals(newSelectedObject, getSelectedObject())) {
setSelectedObject(newSelectedObject);
}
}
else if (getComponent().getAutoSelectFirstRow()) {
if (getTableModel().getValues() != null && getTableModel().getValues().size() > 0) {
returned = true;
getListSelectionModel().addSelectionInterval(0, 0);
// addToSelection(getTableModel().getValues().get(0));
}
}
}
return returned;
}
public ListSelectionModel getListSelectionModel() {
return _table.getSelectionModel();
}
public void setSelectedObject(Object object/*, boolean notify*/) {
if (getValue() == null) {
return;
}
if (object == getSelectedObject()) {
logger.fine("FIBTableWidget: ignore setSelectedObject");
return;
}
logger.fine("FIBTable: setSelectedObject with object " + object + " current is " + getSelectedObject());
if (object != null) {
int index = getTableModel().getValues().indexOf(object);
if (index > -1) {
index = _table.convertRowIndexToView(index);
// if (!notify) _table.getSelectionModel().removeListSelectionListener(getTableModel());
getListSelectionModel().setSelectionInterval(index, index);
// if (!notify) _table.getSelectionModel().addListSelectionListener(getTableModel());
}
} else {
clearSelection();
}
}
public void clearSelection() {
getListSelectionModel().clearSelection();
}
@Override
public List<AbstractBinding> getDependencyBindings() {
List<AbstractBinding> returned = super.getDependencyBindings();
appendToDependingObjects(getWidget().getSelected(), returned);
return returned;
}
@Override
public synchronized boolean updateModelFromWidget() {
return false;
}
/**
* Implements
*
* @see javax.swing.event.TableModelListener#tableChanged(javax.swing.event.TableModelEvent)
* @see javax.swing.event.TableModelListener#tableChanged(javax.swing.event.TableModelEvent)
*/
@Override
public void tableChanged(TableModelEvent e) {
if (e instanceof FIBTableModel.ModelObjectHasChanged) {
FIBTableModel.ModelObjectHasChanged event = (FIBTableModel.ModelObjectHasChanged) e;
if (logger.isLoggable(Level.FINE)) {
logger.fine("Model has changed from " + event.getOldValues() + " to " + event.getNewValues());
}
} else if (e instanceof FIBTableModel.RowMoveForObjectEvent) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Reselect object, and then the edited cell");
}
FIBTableModel.RowMoveForObjectEvent event = (FIBTableModel.RowMoveForObjectEvent) e;
getListSelectionModel().removeListSelectionListener(this);
getListSelectionModel().addSelectionInterval(event.getNewRow(), event.getNewRow());
getListSelectionModel().addListSelectionListener(this);
_table.setEditingColumn(_table.convertColumnIndexToView(event.getColumn()));
_table.setEditingRow(_table.convertRowIndexToView(event.getNewRow()));
}
}
@Override
public JPanel getJComponent() {
return _dynamicComponent;
}
@Override
public JTable getDynamicJComponent() {
return _table;
}
@Override
public FIBTableDynamicModel createDynamicModel() {
return new FIBTableDynamicModel(null);
}
@Override
public FIBTableDynamicModel getDynamicModel() {
return (FIBTableDynamicModel) super.getDynamicModel();
}
@Override
public void updateLanguage() {
super.updateLanguage();
updateTable();
for (FIBTableAction a : getWidget().getActions()) {
if (getWidget().getLocalize()) {
getLocalized(a.getName());
}
}
}
public void updateTable() {
// logger.info("!!!!!!!! updateTable()");
deleteTable();
if (_tableModel != null) {
_tableModel.removeTableModelListener(this);
_tableModel.delete();
_tableModel = null;
}
buildTable();
/*logger.info("!!!!!!!! getDataObject()="+getDataObject());
logger.info("!!!!!!!! getValue()="+getValue());
logger.info("!!!!!!!! getDynamicModel().data="+getDynamicModel().data);
logger.info("!!!!!!!! getComponent().getData()="+getComponent().getData());*/
updateDataObject(getDataObject());
}
@Override
public synchronized void delete() {
// TODO: re-implement this properly and check that all listeners are properly removed.
getFooter().delete();
deleteTable();
getTableModel().removeTableModelListener(this);
super.delete();
}
private void deleteTable() {
if (_table != null) {
_table.removeFocusListener(this);
}
if (getListSelectionModel() != null) {
getListSelectionModel().removeListSelectionListener(this);
}
if (scrollPane != null && _fibTable.getCreateNewRowOnClick()) {
for (MouseListener l : scrollPane.getMouseListeners()) {
scrollPane.removeMouseListener(l);
}
}
for (MouseListener l : _table.getMouseListeners()) {
_table.removeMouseListener(l);
}
}
private void buildTable() {
getTableModel().addTableModelListener(this);
_table = new JXTable(getTableModel()) {
@Override
protected void resetDefaultTableCellRendererColors(Component renderer, int row, int column) {
}
};
_table.setVisibleRowCount(0);
_table.setSortOrderCycle(SortOrder.ASCENDING, SortOrder.DESCENDING, SortOrder.UNSORTED);
_table.setAutoCreateRowSorter(true);
_table.setFillsViewportHeight(true);
_table.setShowHorizontalLines(false);
_table.setShowVerticalLines(false);
_table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
_table.addFocusListener(this);
for (int i = 0; i < getTableModel().getColumnCount(); i++) {
TableColumn col = _table.getColumnModel().getColumn(i);
// FlexoLocalization.localizedForKey(getController().getLocalizer(),getTableModel().columnAt(i).getTitle());
col.setWidth(getTableModel().getDefaultColumnSize(i));
col.setPreferredWidth(getTableModel().getDefaultColumnSize(i));
if (getTableModel().getColumnResizable(i)) {
col.setResizable(true);
} else {
// L'idee, c'est d'etre vraiment sur ;-) !
col.setWidth(getTableModel().getDefaultColumnSize(i));
col.setMinWidth(getTableModel().getDefaultColumnSize(i));
col.setMaxWidth(getTableModel().getDefaultColumnSize(i));
col.setResizable(false);
}
if (getTableModel().columnAt(i).requireCellRenderer()) {
col.setCellRenderer(getTableModel().columnAt(i).getCellRenderer());
}
if (getTableModel().columnAt(i).requireCellEditor()) {
col.setCellEditor(getTableModel().columnAt(i).getCellEditor());
}
}
if (_fibTable.getRowHeight() != null) {
_table.setRowHeight(_fibTable.getRowHeight());
}
if (getTable().getVisibleRowCount() != null) {
_table.setVisibleRowCount(getTable().getVisibleRowCount());
if (_table.getRowHeight() == 0) {
_table.setRowHeight(18);
}
}
_table.setSelectionMode(_fibTable.getSelectionMode().getMode());
// _table.getTableHeader().setReorderingAllowed(false);
_table.getSelectionModel().addListSelectionListener(this);
// _listSelectionModel = _table.getSelectionModel();
// _listSelectionModel.addListSelectionListener(this);
scrollPane = new JScrollPane(_table);
scrollPane.setOpaque(false);
if (_fibTable.getCreateNewRowOnClick()) {
_table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (_table.getCellEditor() != null) {
_table.getCellEditor().stopCellEditing();
e.consume();
}
if (_fibTable.getCreateNewRowOnClick()) {
if (!e.isConsumed() && e.getClickCount() == 2) {
// System.out.println("OK, on essaie de gerer un new par double click");
Enumeration<FIBTableActionListener> en = getFooter().getAddActionListeners();
while (en.hasMoreElements()) {
FIBTableActionListener action = en.nextElement();
if (action.isAddAction()) {
action.actionPerformed(new ActionEvent(_table, ActionEvent.ACTION_PERFORMED, null, EventQueue
.getMostRecentEventTime(), e.getModifiers()));
break;
}
}
}
}
}
});
}
/*_table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e)
{
getController().fireMouseClicked(getDynamicModel(),e.getClickCount());
}
});*/
_dynamicComponent.removeAll();
_dynamicComponent.add(scrollPane, BorderLayout.CENTER);
if (_fibTable.getShowFooter()) {
_dynamicComponent.add(getFooter(), BorderLayout.SOUTH);
}
_dynamicComponent.revalidate();
_dynamicComponent.repaint();
}
@Override
public boolean synchronizedWithSelection() {
return getWidget().getBoundToSelectionManager();
}
public boolean isLastFocusedSelectable() {
return getController().getLastFocusedSelectable() == this;
}
@Override
public boolean mayRepresent(Object o) {
if (getValue() != null) {
return getValue().contains(o);
}
return false;
}
@Override
public void valueChanged(ListSelectionEvent e) {
// Ignore extra messages.
if (e.getValueIsAdjusting()) {
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("valueChanged() selected index=" + getListSelectionModel().getMinSelectionIndex());
}
int i = getListSelectionModel().getMinSelectionIndex();
int leadIndex = getListSelectionModel().getLeadSelectionIndex();
if (!getListSelectionModel().isSelectedIndex(leadIndex)) {
leadIndex = getListSelectionModel().getAnchorSelectionIndex();
}
while (!getListSelectionModel().isSelectedIndex(leadIndex) && i <= getListSelectionModel().getMaxSelectionIndex()) {
leadIndex = i;
i++;
}
if (leadIndex > -1) {
leadIndex = _table.convertRowIndexToModel(leadIndex);
}
selectedObject = getTableModel().elementAt(leadIndex);
Vector<Object> oldSelection = selection;
selection = new Vector<Object>();
for (i = getListSelectionModel().getMinSelectionIndex(); i <= getListSelectionModel().getMaxSelectionIndex(); i++) {
if (getListSelectionModel().isSelectedIndex(i)) {
selection.add(getTableModel().elementAt(_table.convertRowIndexToModel(i)));
}
}
getDynamicModel().selected = selectedObject;
getDynamicModel().selection = selection;
notifyDynamicModelChanged();
footer.handleSelectionChanged();
if (getComponent().getSelected().isValid()) {
logger.fine("Sets SELECTED binding with " + selectedObject);
getComponent().getSelected().setBindingValue(selectedObject, getController());
}
updateFont();
if (!ignoreNotifications) {
getController().updateSelection(this, oldSelection, selection);
}
/*SwingUtilities.invokeLater(new Runnable() {
public void run()
{
System.out.println((isFocused() ? "LEADER" : "SECONDARY")+" Le grand vainqueur est "+selectedObject);
System.out.println((isFocused() ? "LEADER" : "SECONDARY")+" La selection est "+selection);
}
});*/
}
private boolean ignoreNotifications = false;
@Override
public Object getSelectedObject() {
return selectedObject;
}
@Override
public Vector<Object> getSelection() {
return selection;
}
@Override
public void objectAddedToSelection(Object o) {
int index = getTableModel().getValues().indexOf(o);
if (index > -1) {
ignoreNotifications = true;
index = _table.convertRowIndexToView(index);
getListSelectionModel().addSelectionInterval(index, index);
ignoreNotifications = false;
}
}
@Override
public void objectRemovedFromSelection(Object o) {
int index = getTableModel().getValues().indexOf(o);
if (index > -1) {
ignoreNotifications = true;
index = _table.convertRowIndexToView(index);
getListSelectionModel().removeSelectionInterval(index, index);
ignoreNotifications = false;
}
}
@Override
public void selectionResetted() {
ignoreNotifications = true;
getListSelectionModel().clearSelection();
ignoreNotifications = false;
}
@Override
public void addToSelection(Object o) {
int index = getTableModel().getValues().indexOf(o);
if (index > -1) {
index = _table.convertRowIndexToView(index);
getListSelectionModel().addSelectionInterval(index, index);
}
}
@Override
public void removeFromSelection(Object o) {
int index = getTableModel().getValues().indexOf(o);
if (index > -1) {
index = _table.convertRowIndexToView(index);
getListSelectionModel().removeSelectionInterval(index, index);
}
}
@Override
public void resetSelection() {
getListSelectionModel().clearSelection();
}
private static boolean areSameValuesOrderIndifferent(List<?> l1, List<?> l2) {
if (l1 == null || l2 == null) {
return false;
}
if (l1.size() != l2.size()) {
return false;
}
Comparator<Object> comparator = new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
return o1.hashCode() - o2.hashCode();
}
};
List<Object> sortedL1 = new ArrayList<Object>(l1);
Collections.sort(sortedL1, comparator);
List<Object> sortedL2 = new ArrayList<Object>(l2);
Collections.sort(sortedL2, comparator);
for (int i = 0; i < sortedL1.size(); i++) {
if (!sortedL1.get(i).equals(sortedL2.get(i))) {
return false;
}
}
return true;
}
}