/*
GanttProject is an opensource project management tool.
Copyright (C) 2010-2011 Dmitry Barashev
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package net.sourceforge.ganttproject.gui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn;
import net.sourceforge.ganttproject.language.GanttLanguage;
public abstract class EditableList<T> {
private final Object UNDEFINED_VALUE = new Object() {
@Override
public String toString() {
return myUndefinedValueLabel;
}
};
private final List<T> myValues;
private final TableModelImpl myTableModel;
private JTable resourcesTable;
private AbstractTableAndActionsComponent<T> myTableAndActions;
private JScrollPane resourcesScrollPane;
private int[] mySelectedRows;
private JComboBox myComboBox;
private final List<T> myPossibleValues;
private String myTitle;
private String myUndefinedValueLabel = GanttLanguage.getInstance().getText("editableList.undefinedValueLabel");
public EditableList(List<T> assigned_values, List<T> possibleValues) {
myValues = assigned_values;
myPossibleValues = possibleValues;
myTableModel = new TableModelImpl();
}
public void setUndefinedValueLabel(String label) {
myUndefinedValueLabel = label;
}
public void setTitle(String title) {
myTitle = title;
}
public String getTitle() {
return myTitle;
}
public JComponent getTableComponent() {
initComponent();
return resourcesScrollPane;
}
public JComponent getActionsComponent() {
initComponent();
return myTableAndActions.getActionsComponent();
}
public void stopEditing() {
if (resourcesTable.isEditing()) {
resourcesTable.getCellEditor().stopCellEditing();
}
}
public JComponent createDefaultComponent() {
JPanel result = new JPanel(new BorderLayout());
result.add(getTableComponent(), BorderLayout.CENTER);
JComponent actionsComponent = getActionsComponent();
actionsComponent.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 0));
result.add(actionsComponent, BorderLayout.NORTH);
result.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
return result;
}
public AbstractTableAndActionsComponent<T> getTableAndActions() {
initComponent();
return myTableAndActions;
}
private void initComponent() {
if (myTableAndActions == null) {
// JXTable jnTable = new JNTable(myTableModel);
resourcesTable = new JTable(myTableModel) {
@Override
public String getToolTipText(MouseEvent event) {
try {
return super.getToolTipText(event);
} catch (NullPointerException e) {
return null;
}
}
};
UIUtil.setupTableUI(resourcesTable);
resourcesTable.setTableHeader(null);
resourcesTable.getColumnModel().getColumn(0).setPreferredWidth(240);
resourcesTable.getColumnModel().getColumn(0).setCellRenderer(new DefaultTableCellRenderer() {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
assert column == 0;
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (row >= myValues.size()) {
return this;
}
T typedValue = EditableList.this.myValues.get(row);
return EditableList.this.getTableCellRendererComponent(this, typedValue, isSelected, hasFocus, row);
}
});
JTextField editorField = new JTextField();
resourcesTable.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(editorField) {
private boolean isCanceled;
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
isCanceled = false;
JTextField result = (JTextField) super.getTableCellEditorComponent(table, value, isSelected, row, column);
if (UNDEFINED_VALUE == value) {
result.setText("");
}
return result;
}
@Override
public void cancelCellEditing() {
super.cancelCellEditing();
isCanceled = true;
}
@Override
public Object getCellEditorValue() {
if (isCanceled) {
return UNDEFINED_VALUE;
}
return super.getCellEditorValue();
}
});
if (!myPossibleValues.isEmpty()) {
setupEditor(myPossibleValues, resourcesTable);
}
// resourcesTable.setHighlighters(HighlighterFactory.createSimpleStriping());
resourcesScrollPane = new JScrollPane(resourcesTable);
// resourcesScrollPane.setPreferredSize(new Dimension(300, 130));
myTableAndActions = new TableAndActionsImpl();
}
}
protected Component getTableCellRendererComponent(DefaultTableCellRenderer defaultRenderer, T typedValue,
boolean isSelected, boolean hasFocus, int row) {
defaultRenderer.setText(getStringValue(typedValue));
return defaultRenderer;
}
List<T> getSelectedObjects() {
int[] selectedRows = resourcesTable.getSelectedRows();
if (selectedRows.length == 0) {
return Collections.emptyList();
}
ArrayList<T> result = new ArrayList<T>();
for (int nextRow : selectedRows) {
if (nextRow >= 0 && nextRow < myValues.size()) {
result.add(myValues.get(nextRow));
}
}
return result;
}
public T getSelectedObject() {
int selIndex = resourcesTable.getSelectedRow();
return selIndex >= 0 && selIndex < myValues.size() ? myValues.get(selIndex) : null;
}
class TableModelImpl extends AbstractTableModel {
@Override
public int getColumnCount() {
return 1;
}
@Override
public int getRowCount() {
return myValues.size() + 1;
}
@Override
public Object getValueAt(int row, int col) {
if (row >= 0 && row < myValues.size()) {
return new ComboItem(myValues.get(row));
}
if (row == myValues.size()) {
return UNDEFINED_VALUE;
}
throw new IllegalArgumentException("I can't return data in row=" + row);
}
@Override
public boolean isCellEditable(int row, int col) {
if (row == myValues.size()) {
return true;
}
return EditableList.this.isEditable(myValues.get(row));
}
@Override
public void setValueAt(Object value, int row, int col) {
assert col == 0;
if (value == null) {
deleteValue(myValues.get(row));
myValues.remove(row);
fireTableRowsDeleted(row, row);
return;
}
T prototype = createPrototype(value);
if (row >= myValues.size()) {
if (prototype != null) {
T newValue = createValue(prototype);
if (newValue != null) {
myValues.add(newValue);
fireTableRowsInserted(myValues.size(), myValues.size());
}
}
} else if (row >= 0) {
if (prototype != myValues.get(row)) {
T updatedValue = updateValue(prototype, myValues.get(row));
myValues.set(row, updatedValue);
fireTableRowsUpdated(row, row);
}
} else {
throw new IllegalArgumentException("I can't set data in row=" + row);
}
}
}
class ComboItem {
final String myText;
final T myObject;
ComboItem(T t) {
myObject = t;
myText = getStringValue(t);
}
@Override
public String toString() {
return myText;
}
}
protected T createPrototype(Object editValue) {
ComboItem setItem = null;
if (ComboItem.class.equals(editValue.getClass())) {
setItem = (ComboItem) editValue;
} else {
for (int i = 0; i < myComboBox.getModel().getSize(); i++) {
if (((ComboItem) myComboBox.getModel().getElementAt(i)).myText.equals(editValue)) {
setItem = (ComboItem) myComboBox.getModel().getElementAt(i);
break;
}
}
}
return setItem == null ? null : setItem.myObject;
}
private void setupEditor(List<T> possibleValues, final JTable table) {
myComboBox = new JComboBox();
for (T value : possibleValues) {
myComboBox.addItem(new ComboItem(value));
}
myComboBox.setEditable(true);
// AutoCompleteDecorator.decorate(myComboBox);
TableColumn column = table.getColumnModel().getColumn(0);
column.setCellEditor(new DefaultCellEditor(myComboBox));
}
protected String getStringValue(T t) {
return String.valueOf(t);
}
protected boolean isEditable(T t) {
return true;
}
protected abstract T updateValue(T newValue, T curValue);
protected abstract T createValue(T prototype);
protected abstract void deleteValue(T value);
protected void reloadValues() {
}
protected void applyValues() {
}
class TableAndActionsImpl extends AbstractTableAndActionsComponent<T> {
TableAndActionsImpl() {
super(resourcesTable);
}
@Override
protected void onAddEvent() {
int lastRow = resourcesTable.getRowCount() - 1;
if (myComboBox != null) {
resourcesTable.setValueAt(myComboBox.getItemAt(0), lastRow, 0);
myComboBox.requestFocus();
} else {
// resourcesTable.setValueAt("<column name>", lastRow, 0);
}
Rectangle cellRect = resourcesTable.getCellRect(lastRow, 0, true);
resourcesTable.scrollRectToVisible(cellRect);
resourcesTable.getSelectionModel().setSelectionInterval(lastRow, lastRow);
resourcesTable.editCellAt(lastRow, 0);
resourcesTable.getEditorComponent().requestFocus();
}
@Override
protected void onDeleteEvent() {
for (int selectedRow : mySelectedRows) {
resourcesTable.getModel().setValueAt(null, selectedRow, 0);
}
}
@Override
protected void onSelectionChanged() {
mySelectedRows = resourcesTable.getSelectedRows();
List<T> selectedObjects = getSelectedObjects();
fireSelectionChanged(selectedObjects);
}
}
}