/* 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 com.google.common.base.Function; import com.google.common.collect.Lists; import net.sourceforge.ganttproject.action.GPAction; import javax.swing.*; import javax.swing.border.Border; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import java.awt.*; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.List; /** * It is a UI component consisting of a table and a set of actions. Default * actions are "add" and "delete" which add a new row to and delete a row the * table. One may add other actions. * * Actions are represented as buttons and this class creates a UI panel holding * all actions. * * A few abstract methods are called when some events happen. * * @author dbarashev (Dmitry Barashev) * * @param <T> * type of objects stored in the table */ public abstract class AbstractTableAndActionsComponent<T> { private static final int ENABLED_WITH_EMPTY_SELECTION = 1; private static final int DISABLED_WITH_MULTI_SELECTION = 2; public static final String PROPERTY_IS_ENABLED_FUNCTION = AbstractTableAndActionsComponent.class.getName() + ".isEnabledFunction"; private Function<List<T>, Boolean> createIsEnabledFunction(final int flags) { return new Function<List<T>, Boolean>() { @Override public Boolean apply(List<T> input) { if (input.isEmpty()) { return (0 != (flags & AbstractTableAndActionsComponent.ENABLED_WITH_EMPTY_SELECTION)); } else if (input.size() > 1) { return (0 == (flags & AbstractTableAndActionsComponent.DISABLED_WITH_MULTI_SELECTION)); } else { return true; } } }; } private int myActionOrientation = SwingConstants.HORIZONTAL; private final List<Action> myAdditionalActions = new ArrayList<>(); private final List<SelectionListener<T>> myListeners = new ArrayList<>(); private final JTable myTable; private JPanel buttonBox; private final Action myDeleteAction = new GPAction("delete") { @Override public void actionPerformed(ActionEvent e) { int selectedRow = myTable.getSelectedRow(); onDeleteEvent(); if (selectedRow >= myTable.getRowCount()) { selectedRow = myTable.getRowCount() - 1; } myTable.getSelectionModel().setSelectionInterval(selectedRow, selectedRow); } }; protected AbstractTableAndActionsComponent(JTable table) { myTable = table; addAction(getAddResourceAction(), AbstractTableAndActionsComponent.ENABLED_WITH_EMPTY_SELECTION); addAction(getDeleteItemAction()); myTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { onSelectionChanged(); } }); myTable.getModel().addTableModelListener(new TableModelListener() { @Override public void tableChanged(TableModelEvent e) { onSelectionChanged(); } }); } public void addAction(Action action) { addAction(action, 0); } private void addAction(Action action, int flags) { if (action.getValue(PROPERTY_IS_ENABLED_FUNCTION) == null) { action.putValue(PROPERTY_IS_ENABLED_FUNCTION, createIsEnabledFunction(flags)); } myAdditionalActions.add(action); if (action instanceof SelectionListener) { addSelectionListener((SelectionListener<T>) action); } } public void setActionOrientation(int orientation) { assert orientation == SwingConstants.VERTICAL || orientation == SwingConstants.HORIZONTAL; myActionOrientation = orientation; } public void setSelectionMode(int selectionMode) { myTable.getSelectionModel().setSelectionMode(selectionMode); } private Action getAddResourceAction() { return new GPAction("add") { @Override public void actionPerformed(ActionEvent e) { onAddEvent(); } }; } public Action getDeleteItemAction() { return myDeleteAction; } public void setSelection(int index) { if (index == -1) { myTable.getSelectionModel().clearSelection(); } else { myTable.getSelectionModel().setSelectionInterval(index, index); } } protected void fireSelectionChanged(List<T> selectedObjects) { for (Action action : myAdditionalActions) { Function<List<T>, Boolean> isEnabled = (Function<List<T>, Boolean>) action.getValue(PROPERTY_IS_ENABLED_FUNCTION); action.setEnabled(isEnabled.apply(selectedObjects)); } for (SelectionListener<T> l : myListeners) { l.selectionChanged(selectedObjects); } } protected void onSelectionChanged() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int[] selectedRows = myTable.getSelectedRows(); List<T> result = Lists.newArrayList(); for (int row : selectedRows) { T value = getValue(row); if (value != null) { result.add(getValue(row)); } } fireSelectionChanged(result); } }); } public JComponent getActionsComponent() { if (buttonBox == null) { buttonBox = myActionOrientation == SwingConstants.HORIZONTAL ? new JPanel(new GridLayout(1, myAdditionalActions.size(), 5, 0)) : new JPanel(new GridLayout(myAdditionalActions.size(), 1, 0, 5)); for (Action action : myAdditionalActions) { buttonBox.add(new JButton(action)); } Border emptyBorder = BorderFactory.createEmptyBorder(0, 0, 0, 0); buttonBox.setBorder(emptyBorder); } onSelectionChanged(); return buttonBox; } public interface SelectionListener<T> { void selectionChanged(List<T> selection); } public void addSelectionListener(SelectionListener<T> listener) { myListeners.add(listener); } protected abstract void onAddEvent(); protected abstract void onDeleteEvent(); protected T getValue(int row) { return null; } public static JPanel createDefaultTableAndActions(JComponent table, JComponent actionsComponent) { JPanel result = new JPanel(new BorderLayout()); actionsComponent.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 0)); JPanel actionsWrapper = new JPanel(new BorderLayout()); actionsWrapper.add(actionsComponent, BorderLayout.WEST); result.add(actionsWrapper, BorderLayout.NORTH); JScrollPane scrollPane = new JScrollPane(table); result.add(scrollPane, BorderLayout.CENTER); return result; } public static <T> JPanel createDefaultTableAndActions(JTable table, TableModelExt<T> modelExt) { AbstractTableAndActionsComponent<T> component = new AbstractTableAndActionsComponent<T>(table) { @Override protected void onAddEvent() { getTable().editCellAt(modelExt.getRowCount() - 1, 1); } @Override protected void onDeleteEvent() { if (getTable().isEditing() && getTable().getCellEditor() != null) { getTable().getCellEditor().stopCellEditing(); } modelExt.delete(getTable().getSelectedRows()); } @Override protected T getValue(int row) { List<T> values = modelExt.getAllValues(); return (row >= 0 && row < values.size()) ? values.get(row) : null; } }; return createDefaultTableAndActions(table, component.getActionsComponent()); } public JTable getTable() { return myTable; } }