Java tutorial
//Copyright (C) 2010 Novabit Informationssysteme GmbH // //This file is part of Nuclos. // //Nuclos is free software: you can redistribute it and/or modify //it under the terms of the GNU Affero General Public License as published by //the Free Software Foundation, either version 3 of the License, or //(at your option) any later version. // //Nuclos 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 Affero General Public License for more details. // //You should have received a copy of the GNU Affero General Public License //along with Nuclos. If not, see <http://www.gnu.org/licenses/>. package org.nuclos.client.ui.collect; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InvocationEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.io.Closeable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.EventListener; import java.util.EventObject; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.prefs.Preferences; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.JViewport; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.SwingUtilities; import javax.swing.event.CellEditorListener; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import javax.swing.table.TableRowSorter; import org.apache.commons.lang.NullArgumentException; import org.apache.log4j.Logger; import org.jdesktop.jxlayer.JXLayer; import org.jdesktop.jxlayer.plaf.ext.LockableUI; import org.jdesktop.swingx.event.TableColumnModelExtListener; import org.nuclos.api.context.ScriptContext; import org.nuclos.client.common.FocusActionListener; import org.nuclos.client.common.NuclosCollectableTextArea; import org.nuclos.client.common.SearchConditionSubFormController.SearchConditionTableModel; import org.nuclos.client.common.SubFormController.FocusListSelectionListener; import org.nuclos.client.common.Utils; import org.nuclos.client.scripting.ScriptEvaluator; import org.nuclos.client.ui.Icons; import org.nuclos.client.ui.SizeKnownListener; import org.nuclos.client.ui.UIUtils; import org.nuclos.client.ui.URIMouseAdapter; import org.nuclos.client.ui.collect.FixedColumnRowHeader.HeaderTable; import org.nuclos.client.ui.collect.component.CollectableCheckBox; import org.nuclos.client.ui.collect.component.CollectableComponent; import org.nuclos.client.ui.collect.component.CollectableComponentFactory; import org.nuclos.client.ui.collect.component.CollectableComponentTableCellEditor; import org.nuclos.client.ui.collect.component.CollectableComponentType; import org.nuclos.client.ui.collect.component.CollectableListOfValues; import org.nuclos.client.ui.collect.component.CollectableOptionGroup; import org.nuclos.client.ui.collect.component.DefaultCollectableComponentFactory; import org.nuclos.client.ui.collect.component.LabeledCollectableComponentWithVLP; import org.nuclos.client.ui.collect.component.LookupEvent; import org.nuclos.client.ui.collect.component.LookupListener; import org.nuclos.client.ui.collect.component.model.CollectableComponentModelAdapter; import org.nuclos.client.ui.collect.component.model.CollectableComponentModelEvent; import org.nuclos.client.ui.collect.component.model.CollectableComponentModelListener; import org.nuclos.client.ui.collect.component.model.DetailsComponentModelEvent; import org.nuclos.client.ui.collect.component.model.SearchComponentModelEvent; import org.nuclos.client.ui.collect.model.CollectableEntityFieldBasedTableModel; import org.nuclos.client.ui.event.PopupMenuMouseAdapter; import org.nuclos.client.ui.event.TableColumnModelAdapter; import org.nuclos.client.ui.labeled.LabeledComponent; import org.nuclos.client.ui.popupmenu.JPopupMenuFactory; import org.nuclos.client.ui.table.CommonJTable; import org.nuclos.client.ui.table.SortableTableModelEvent; import org.nuclos.client.ui.table.TableCellEditorProvider; import org.nuclos.client.ui.table.TableCellRendererProvider; import org.nuclos.client.ui.table.TableUtils; import org.nuclos.common.NuclosEOField; import org.nuclos.common.NuclosFieldNotInModelException; import org.nuclos.common.NuclosScript; import org.nuclos.common.collect.collectable.Collectable; import org.nuclos.common.collect.collectable.CollectableComponentTypes; import org.nuclos.common.collect.collectable.CollectableEntity; import org.nuclos.common.collect.collectable.CollectableEntityField; import org.nuclos.common.collect.collectable.CollectableField; import org.nuclos.common.collect.collectable.CollectableFieldsProvider; import org.nuclos.common.collect.collectable.CollectableFieldsProviderFactory; import org.nuclos.common.collect.collectable.CollectableUtils; import org.nuclos.common.collect.collectable.DefaultCollectableEntityField; import org.nuclos.common.collect.collectable.searchcondition.CollectableComparison; import org.nuclos.common.collect.collectable.searchcondition.ComparisonOperator; import org.nuclos.common.collect.exception.CollectableFieldFormatException; import org.nuclos.common.collection.Pair; import org.nuclos.common2.LangUtils; import org.nuclos.common2.SpringLocaleDelegate; import org.nuclos.common2.StringUtils; import org.nuclos.common2.exception.CommonBusinessException; import org.nuclos.common2.exception.CommonFatalException; /** * Subform for displaying/editing dependant <code>Collectable</code>s. * * Changes as of 2010-08-12: The subform's toolbar is no longer publicly * accessible! If your client-code needs toolbar-events, add a SubFormToolListener * on the subForm itself, which will be notifies about ALL toolbar events at once. * * Toolbar button states can be set via setToolbarFunctionState. * * If you need to add another toolbar button, pass the button along with its * action command to addToolbarFunction. The given action command will be propagated * the same way as the internal default commands. * * <br>Created by Novabit Informationssysteme GmbH * <br>Please visit <a href="http://www.novabit.de">www.novabit.de</a> */ @SuppressWarnings("serial") public class SubForm extends JPanel implements TableCellRendererProvider, ActionListener, Closeable, DynamicRowHeightChangeListener { private static final Logger LOG = Logger.getLogger(SubForm.class); public static interface SubFormToolListener extends EventListener { void toolbarAction(String actionCommand); } public static enum ToolbarFunction { NEW { @Override public AbstractButton createButton() { JButton res = new JButton(Icons.getInstance().getIconNew16()); res.setToolTipText( SpringLocaleDelegate.getInstance().getMessage("SubForm.7", "Neuen Datensatz anlegen")); res.setActionCommand(name()); return res; } @Override public JMenuItem createMenuItem() { JMenuItem res = new JMenuItem( SpringLocaleDelegate.getInstance().getMessage("SubForm.7", "Neuen Datensatz anlegen"), Icons.getInstance().getIconNew16()); res.setActionCommand(name()); return res; } }, REMOVE { @Override public AbstractButton createButton() { JButton res = new JButton(Icons.getInstance().getIconDelete16()); res.setToolTipText(SpringLocaleDelegate.getInstance().getMessage("SubForm.1", "Ausgew\u00e4hlten Datensatz l\u00f6schen")); res.setActionCommand(name()); return res; } @Override public JMenuItem createMenuItem() { JMenuItem res = new JMenuItem(SpringLocaleDelegate.getInstance().getMessage("SubForm.1", "Ausgew\u00e4hlten Datensatz l\u00f6schen"), Icons.getInstance().getIconDelete16()); res.setActionCommand(name()); return res; } }, MULTIEDIT { @Override public AbstractButton createButton() { JButton res = new JButton(Icons.getInstance().getIconMultiEdit16()); res.setToolTipText(SpringLocaleDelegate.getInstance().getMessage("SubForm.6", "Mehrere Datens\u00e4tze hinzuf\u00fcgen/l\u00f6schen")); res.setActionCommand(name()); return res; } @Override public JMenuItem createMenuItem() { JMenuItem res = new JMenuItem( SpringLocaleDelegate.getInstance().getMessage("SubForm.6", "Mehrere Datens\u00e4tze hinzuf\u00fcgen/l\u00f6schen"), Icons.getInstance().getIconMultiEdit16()); res.setActionCommand(name()); return res; } }, FILTER { @Override public AbstractButton createButton() { JToggleButton res = new JToggleButton(Icons.getInstance().getIconFilter16()); res.setSize(16, 16); res.setToolTipText( SpringLocaleDelegate.getInstance().getMessage("SubForm.5", "Datens\u00e4tze filtern")); res.setActionCommand(name()); return res; } @Override public JMenuItem createMenuItem() { JCheckBoxMenuItem res = new JCheckBoxMenuItem( SpringLocaleDelegate.getInstance().getMessage("SubForm.5", "Datens\u00e4tze filtern"), Icons.getInstance().getIconFilter16()); res.setActionCommand(name()); return res; } }, TRANSFER { @Override public AbstractButton createButton() { JToggleButton res = new JToggleButton(Icons.getInstance().getIconCopy16()); res.setSize(16, 16); res.setToolTipText(SpringLocaleDelegate.getInstance().getMessage("SubForm.ToolbarFunction.TRANSFER", "Datensatz fr alle Entit\u00e4ten \u00fcbernehmen")); res.setActionCommand(name()); return res; } @Override public JMenuItem createMenuItem() { JCheckBoxMenuItem res = new JCheckBoxMenuItem( SpringLocaleDelegate.getInstance().getMessage("SubForm.ToolbarFunction.TRANSFER", "Datensatz fr alle Entit\u00e4ten \u00fcbernehmen"), Icons.getInstance().getIconCopy16()); res.setActionCommand(name()); return res; } }, DOCUMENTIMPORT { @Override public AbstractButton createButton() { JToggleButton res = new JToggleButton(Icons.getInstance().getIconTextFieldButtonFile()); res.setSize(16, 16); res.setToolTipText(SpringLocaleDelegate.getInstance() .getMessage("SubForm.ToolbarFunction.DOCUMENTIMPORT", "Insert document(s)")); res.setActionCommand(name()); return res; } @Override public JMenuItem createMenuItem() { JCheckBoxMenuItem res = new JCheckBoxMenuItem(SpringLocaleDelegate.getInstance() .getMessage("SubForm.ToolbarFunction.DOCUMENTIMPORT", "Insert document(s)"), Icons.getInstance().getIconTextFieldButtonFile()); res.setActionCommand(name()); return res; } }, PRINTREPORT { @Override public AbstractButton createButton() { JButton res = new JButton(Icons.getInstance().getIconPrintReport16()); res.setToolTipText(SpringLocaleDelegate.getInstance().getMessage("SubForm.8", "Daten exportieren")); res.setActionCommand(name()); return res; } @Override public JMenuItem createMenuItem() { JMenuItem res = new JMenuItem( SpringLocaleDelegate.getInstance().getMessage("SubForm.8", "Daten exportieren"), Icons.getInstance().getIconPrintReport16()); res.setActionCommand(name()); return res; } }; public abstract AbstractButton createButton(); public abstract JMenuItem createMenuItem(); public static ToolbarFunction fromCommandString(String actionCommand) { try { return ToolbarFunction.valueOf(actionCommand); } catch (Exception e) { // ignore here. LOG.debug("fromCommandString failed on " + actionCommand); return null; } } } public static enum ToolbarFunctionState { ACTIVE(true, true), DISABLED(false, true), HIDDEN(false, false); private boolean isEnabled; private boolean isVisible; private ToolbarFunctionState(boolean isEnabled, boolean isVisible) { this.isEnabled = isEnabled; this.isVisible = isVisible; } public void set(AbstractButton a) { a.setEnabled(isEnabled); a.setVisible(isVisible); } public void set(JMenuItem mi) { mi.setEnabled(isEnabled); mi.setVisible(isVisible); } } /* the minimum row height for the table(s). */ public static final int MIN_ROWHEIGHT = 20; public static final int MAX_DYNAMIC_ROWHEIGHT = 200; public static final int DYNAMIC_ROW_HEIGHTS = -1; private boolean dynamicRowHeights = false; private boolean dynamicRowHeightsDefault = false; private static final Color LAYER_BUSY_COLOR = new Color(128, 128, 128, 128); private static final Logger log = Logger.getLogger(SubForm.class); // private final CollectableComponentModelAdapter editorChangeListener = new CollectableComponentModelAdapter() { @Override public void collectableFieldChangedInModel(CollectableComponentModelEvent ev) { if (ev.collectableFieldHasChanged()) { fireStateChanged(); } } @Override public void searchConditionChangedInModel(SearchComponentModelEvent ev) { fireStateChanged(); } }; private JXLayer<JComponent> layer; private AtomicInteger lockCount = new AtomicInteger(0); private HashMap<String, AbstractButton> toolbarButtons; private List<String> toolbarOrder; private HashMap<String, JMenuItem> toolbarMenuItems; /** * @see #isDetailsChangedIgnored() */ private boolean bDetailsChangedIgnored; /** * Can't be final because it must be set to null in close() to avoid memeory leaks. (tp) */ private JToolBar toolbar; /** * Can't be final because it must be set to null in close() to avoid memeory leaks. (tp) */ private JPanel contentPane = new JPanel(new BorderLayout()); private JScrollPane scrollPane = new JScrollPane(); private SubFormTable subformtbl; private SubFormFilter subformfilter; private final String entityName; private final String foreignKeyFieldToParent; private final List<LookupListener> lookupListener = new ArrayList<LookupListener>(); protected final List<FocusActionListener> lstFocusActionListener = new ArrayList<FocusActionListener>(); /** * NUCLOSINT-63: To display the size of subform list in the corresponding tab. */ private SizeKnownListener sizeKnownListener; /** * maps column names to columns */ private final Map<String, Column> mpColumns = new LinkedHashMap<String, Column>(); private final Map<CollectableEntityField, TableCellRenderer> mpColumnRenderer = new HashMap<CollectableEntityField, TableCellRenderer>(); private final Map<CollectableEntityField, CollectableComponentTableCellEditor> mpStaticColumnEditors = new HashMap<CollectableEntityField, CollectableComponentTableCellEditor>(); private List<ChangeListener> lstchangelistener = new LinkedList<ChangeListener>(); private String uniqueMasterColumnName; private String sSubFormParent; private String sControllerType; private String sInitialSortingColumn; private String sInitialSortingOrder; private Map<String, Object> mpParams = new HashMap<String, Object>(); public static interface ParameterChangeListener extends ChangeListener { @Override public void stateChanged(ChangeEvent e); } private final List<ParameterChangeListener> parameterListener = new ArrayList<ParameterChangeListener>(); /** * Use custom column widths? This will always be true as soon as the user changed one or more column width * the first time. */ private boolean useCustomColumnWidths; private TableModelListener tblmdllistener; private TableColumnModelListener columnmodellistener; private SubformRowHeader rowHeader; private CollectableComponentFactory collectableComponentFactory; private List<SubFormToolListener> listeners; private PopupMenuMouseAdapter popupMenuAdapter; private final List<Pair<JComponent, MouseListener>> myMouseListener = new ArrayList<Pair<JComponent, MouseListener>>(); private boolean closed = false; private boolean enabled = true; private boolean enabledByLayout = true; private boolean readonly = false; private NuclosScript newEnabledScript; private NuclosScript editEnabledScript; private NuclosScript deleteEnabledScript; private NuclosScript cloneEnabledScript; private final boolean bLayout; private final RowHeightController rowHeightCtrl; /** * @param entityName * @param iToolBarOrientation @see JToolbar#setOrientation * @precondition entityName != null * @postcondition this.getForeignKeyFieldToParent() == null */ public SubForm(String sEntityName, int iToolBarOrientation) { this(sEntityName, iToolBarOrientation, null); assert this.getForeignKeyFieldToParent() == null; } /** * @param entityName * @param toolBarOrientation @see JToolbar#setOrientation * @param foreignKeyFieldToParent Needs only be specified if not unique. @see #getForeignKeyFieldToParent() * @precondition entityName != null * @postcondition this.getForeignKeyFieldToParent() == foreignKeyFieldToParent */ public SubForm(String entityName, int toolBarOrientation, String foreignKeyFieldToParent) { this(entityName, toolBarOrientation, foreignKeyFieldToParent, false); } /** * @param entityName * @param toolBarOrientation @see JToolbar#setOrientation * @param foreignKeyFieldToParent Needs only be specified if not unique. @see #getForeignKeyFieldToParent() * @precondition entityName != null * @postcondition this.getForeignKeyFieldToParent() == foreignKeyFieldToParent */ public SubForm(String entityName, int toolBarOrientation, String foreignKeyFieldToParent, boolean bLayout) { super(new GridLayout(1, 1)); this.rowHeightCtrl = new RowHeightController(this); this.bLayout = bLayout; this.toolbar = UIUtils.createNonFloatableToolBar(toolBarOrientation); this.listeners = new ArrayList<SubFormToolListener>(); subformtbl = new SubFormTable(this) { protected void configureEnclosingScrollPane() { super.configureEnclosingScrollPane(); if (getSubFormFilter() != null) { getSubFormFilter().setupTableHeaderForScrollPane(scrollPane); } } }; subformtbl.addMouseListener(new SubFormPopupMenuMouseAdapter(subformtbl)); subformtbl.addMouseListener(new DoubleClickMouseAdapter()); subformtbl.addMouseMotionListener(new URIMouseAdapter()); subformtbl.addMouseListener(new URIMouseAdapter()); contentPane.add(scrollPane, BorderLayout.CENTER); if (entityName == null) { throw new NullArgumentException("entityName"); } this.entityName = entityName; if (toolBarOrientation == -1) { this.toolbar.setVisible(false); } else { this.toolbar.setOrientation(toolBarOrientation); } this.foreignKeyFieldToParent = foreignKeyFieldToParent; this.collectableComponentFactory = CollectableComponentFactory.getInstance(); layer = new JXLayer<JComponent>(contentPane, new TranslucentLockableUI()); layer.setName("JXLayerGlasspane"); add(layer); toolbarButtons = new HashMap<String, AbstractButton>(); toolbarMenuItems = new HashMap<String, JMenuItem>(); toolbarOrder = new ArrayList<String>(); for (ToolbarFunction func : ToolbarFunction.values()) { AbstractButton button = func.createButton(); JMenuItem mi = func.createMenuItem(); toolbarButtons.put(func.name(), button); toolbarMenuItems.put(func.name(), mi); toolbar.add(button); button.addActionListener(this); mi.addActionListener(this); toolbarOrder.add(func.name()); } setToolbarFunctionState(ToolbarFunction.REMOVE, ToolbarFunctionState.DISABLED); setToolbarFunctionState(ToolbarFunction.MULTIEDIT, ToolbarFunctionState.HIDDEN); setToolbarFunctionState(ToolbarFunction.DOCUMENTIMPORT, ToolbarFunctionState.HIDDEN); setToolbarFunctionState(ToolbarFunction.FILTER, ToolbarFunctionState.HIDDEN); this.init(); assert this.getForeignKeyFieldToParent() == foreignKeyFieldToParent; } @Override public final void close() { // Close is needed for avoiding memory leaks // If you want to change something here, please consult me (tp). if (!closed) { LOG.debug("close(): " + this); if (rowHeader != null) { rowHeader.close(); } rowHeader = null; if (subformtbl != null) { subformtbl.close(); } subformtbl = null; if (subformfilter != null) { subformfilter.close(); } subformfilter = null; for (Pair<JComponent, MouseListener> p : myMouseListener) { p.getX().removeMouseListener(p.getY()); } myMouseListener.clear(); // Partial fix for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7079260 popupMenuAdapter = null; scrollPane = null; contentPane = null; toolbar = null; mpColumnRenderer.clear(); mpColumns.clear(); mpStaticColumnEditors.clear(); parameterListener.clear(); lstchangelistener.clear(); lstFocusActionListener.clear(); listeners.clear(); myMouseListener.clear(); mpParams.clear(); closed = true; } } public void addColumnModelListener(TableColumnModelListener tblcolumnlistener) { subformtbl.getColumnModel().addColumnModelListener(tblcolumnlistener); } public void addSubFormToolListener(SubFormToolListener l) { listeners.add(l); } public void removeSubFormToolListener(SubFormToolListener l) { listeners.remove(l); } public void removeAllSubFormToolListeners() { listeners.clear(); } public JScrollPane getSubformScrollPane() { return this.scrollPane; } public void actionPerformed(String actionCommand) { if (actionCommand != null) for (SubFormToolListener l : new ArrayList<SubFormToolListener>(listeners)) l.toolbarAction(actionCommand); } @Override public void actionPerformed(ActionEvent e) { String actionCommand = StringUtils.nullIfEmpty(e.getActionCommand()); actionPerformed(actionCommand); } public void setToolbarFunctionState(ToolbarFunction func, ToolbarFunctionState state) { setToolbarFunctionState(func.name(), state); } public void setToolbarFunctionState(String toolbarActionCommand, ToolbarFunctionState state) { AbstractButton button = toolbarButtons.get(toolbarActionCommand); if (button != null) state.set(button); JMenuItem mi = toolbarMenuItems.get(toolbarActionCommand); if (mi != null) state.set(mi); } public void addToolbarFunction(String actionCommand, AbstractButton button, JMenuItem mi, int pos) { if (toolbarButtons.containsKey(actionCommand)) toolbar.remove(toolbarButtons.get(actionCommand)); toolbarButtons.put(actionCommand, button); button.setActionCommand(actionCommand); button.addActionListener(this); toolbar.add(button, pos); toolbar.validate(); if (toolbarMenuItems.containsKey(actionCommand)) toolbarOrder.remove(actionCommand); toolbarMenuItems.put(actionCommand, mi); mi.setActionCommand(actionCommand); mi.addActionListener(this); toolbarOrder.add(pos, actionCommand); } //--- needed by the wysiwyg editor only ----------------------------------- public void addToolbarButtonMouseListener(MouseListener l) { for (AbstractButton b : toolbarButtons.values()) b.addMouseListener(l); } public void removeToolbarButtonMouseListener(MouseListener l) { for (AbstractButton b : toolbarButtons.values()) b.removeMouseListener(l); } public JToolBar getToolbar() { return toolbar; } public int getToolbarOrientation() { return toolbar.getOrientation(); } public Rectangle getToolbarBounds() { return toolbar.getBounds(); } public Collection<Column> getColumns() { return this.mpColumns.values(); } public Collection<String> getColumnNames() { return this.mpColumns.keySet(); } public Set<String> getToolbarFunctions() { return toolbarButtons.keySet(); } public AbstractButton getToolbarButton(String function) { return toolbarButtons.get(function); } //--- end needed by the wysiwyg editor only -------------------------------- // class TranslucentLockableUI ---> private class TranslucentLockableUI extends LockableUI { @Override protected void paintLayer(Graphics2D g2, JXLayer<? extends JComponent> l) { super.paintLayer(g2, l); if (isLocked()) { g2.setColor(LAYER_BUSY_COLOR); g2.fillRect(0, 0, l.getWidth(), l.getHeight()); } } } // class TranslucentLockableUI public void setLockedLayer() { if (lockCount.incrementAndGet() == 1) if (layer != null && !((LockableUI) layer.getUI()).isLocked()) ((LockableUI) layer.getUI()).setLocked(true); } public void restoreUnLockedLayer() { if (lockCount.decrementAndGet() == 0) if (layer != null && ((LockableUI) layer.getUI()).isLocked()) ((LockableUI) layer.getUI()).setLocked(false); } public void forceUnlockFrame() { lockCount.set(0); ((LockableUI) layer.getUI()).setLocked(false); } private void init() { contentPane.add(toolbar, toolbar.getOrientation() == JToolBar.HORIZONTAL ? BorderLayout.NORTH : BorderLayout.WEST); // Configure table scrollPane.getViewport().setBackground(subformtbl.getBackground()); subformtbl.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); subformtbl.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); // subformtbl.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); scrollPane.getViewport().setView(subformtbl); JLabel labCorner = new JLabel(); labCorner.setEnabled(false); labCorner.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 1, Color.GRAY)); labCorner.setBackground(Color.LIGHT_GRAY); scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, labCorner); rowHeader = createTableRowHeader(subformtbl, scrollPane); subformtbl.setRowHeaderTable(rowHeader); // subformtbl.addMouseListener(newToolbarContextMenuListener(subformtbl, subformtbl)); addToolbarMouseListener(subformtbl, subformtbl, subformtbl); // scrollPane.getViewport().addMouseListener(newToolbarContextMenuListener(scrollPane.getViewport(), subformtbl)); addToolbarMouseListener(scrollPane.getViewport(), scrollPane.getViewport(), subformtbl); } private void addToolbarMouseListener(JComponent src, JComponent parent, JTable table) { final MouseListener ml = newToolbarContextMenuListener(parent, table); src.addMouseListener(ml); myMouseListener.add(new Pair<JComponent, MouseListener>(src, ml)); } /** * @Deprecated Never use this directly, instead use {@link #addToolbarMenuItems(List)}. */ private MouseListener newToolbarContextMenuListener(final JComponent parent, final JTable table) { MouseListener res = new MouseAdapter() { @Override public void mouseClicked(MouseEvent mev) { if (SwingUtilities.isRightMouseButton(mev)) { List<JComponent> items = new ArrayList<JComponent>(); addToolbarMenuItems(items); if (items.isEmpty()) return; JPopupMenu popup = new JPopupMenu(); for (JComponent c : items) popup.add(c); popup.show(parent, mev.getX(), mev.getY()); } if (SwingUtilities.isLeftMouseButton(mev) && mev.getClickCount() == 2) { int row = table.rowAtPoint(mev.getPoint()); int column = table.columnAtPoint(mev.getPoint()); LOG.info(StringUtils.concat("Doubleclick on subform: column=", column, ",row=", row)); if (row == -1 || column == -1) { if (toolbarMenuItems.get(ToolbarFunction.NEW.name()).isEnabled()) { actionPerformed(ToolbarFunction.NEW.name()); } } } } }; return res; } public final void setupTableFilter(CollectableFieldsProviderFactory collectableFieldsProviderFactory) { // get columnmodels of fixed and external tables TableColumnModel fixedColumnModel = getSubformRowHeader().getHeaderTable().getColumnModel(); TableColumnModel externalColumnModel = getSubformRowHeader().getExternalTable().getColumnModel(); // get fixed and external tables JTable fixedTable = getSubformRowHeader().getHeaderTable(); SubFormTable externalTable = getSubformRowHeader().getExternalTable(); // setup subform filter subformfilter = new SubFormFilter(this, fixedTable, fixedColumnModel, externalTable, externalColumnModel, (JToggleButton) toolbarButtons.get(ToolbarFunction.FILTER.name()), (JCheckBoxMenuItem) toolbarMenuItems.get(ToolbarFunction.FILTER.name()), collectableFieldsProviderFactory); subformfilter.setupTableHeaderForScrollPane(scrollPane); } public void setSubFormParameterProviderForSubFormFilter(SubFormParameterProvider parameterProvider) { this.subformfilter.setSubFormParameterProvider(parameterProvider); } /** * do not store items permanent! * @param result */ public void addToolbarMenuItems(List<JComponent> result) { for (String actionCommand : toolbarOrder) { result.add(toolbarMenuItems.get(actionCommand)); } } boolean isLayout() { return bLayout; } public SubFormFilter getSubFormFilter() { return this.subformfilter; } public void storeTableFilter(String parentEntityName) { getSubFormFilter().storeTableFilter(parentEntityName); } public void loadTableFilter(String parentEntityName) { getSubFormFilter().loadTableFilter(parentEntityName); } /** * @param tbl the table to add the header to * @param scrlpnTable @todo what is this? */ protected SubformRowHeader createTableRowHeader(final SubFormTable tbl, JScrollPane scrlpnTable) { final SubformRowHeader res = new SubformRowHeader(tbl, scrlpnTable); return res; } public void setTableRowHeader(SubformRowHeader newTableRowHeader) { this.rowHeader = newTableRowHeader; this.subformtbl.setRowHeaderTable(rowHeader); newTableRowHeader.setExternalTable(subformtbl, scrollPane); // rowHeader.getHeaderTable().addMouseListener(newToolbarContextMenuListener(rowHeader.getHeaderTable(), rowHeader.getHeaderTable())); addToolbarMouseListener(rowHeader.getHeaderTable(), rowHeader.getHeaderTable(), rowHeader.getHeaderTable()); // scrollPane.getRowHeader().addMouseListener(newToolbarContextMenuListener(scrollPane.getRowHeader(), rowHeader.getHeaderTable())); addToolbarMouseListener(scrollPane.getRowHeader(), scrollPane.getRowHeader(), rowHeader.getHeaderTable()); } public SubformRowHeader getSubformRowHeader() { return this.rowHeader; } public JTable getJTable() { return this.subformtbl; } public void setRowHeight(int iHeight) { if (DYNAMIC_ROW_HEIGHTS == iHeight) { this.subformtbl.setRowHeight(getMinRowHeight()); this.dynamicRowHeights = true; this.rowHeightCtrl.clear(); this.subformtbl.updateRowHeights(); } else { this.dynamicRowHeights = false; this.subformtbl.setRowHeight(iHeight); } } @Override public Dimension getMinimumSize() { Dimension result = super.getMinimumSize(); result.height = Math.max(result.height, 100); return result; } /** * @return the name of this subform's entity. * @postcondition result != null */ public String getEntityName() { return this.entityName; } /** * @return the foreign key field that references the parent object. May be null for convenience. In that case, * the SubFormController will try to find the field from the field meta information. This is only possible if * there is only one field in the subform's entity that references the parent entity. */ public final String getForeignKeyFieldToParent() { return this.foreignKeyFieldToParent; } /** * Method needed for WYSIWYG Editor * NUCLEUSINT-265 * @return */ public SubFormTable getSubformTable() { return this.subformtbl; } public TableCellEditorProvider getTableCellEditorProvider() { return this.subformtbl.getTableCellEditorProvider(); } public void setTableCellEditorProvider(TableCellEditorProvider celleditorprovider) { this.subformtbl.setTableCellEditorProvider(celleditorprovider); } public TableCellRendererProvider getTableCellRendererProvider() { return this.subformtbl.getTableCellRendererProvider(); } public void setTableCellRendererProvider(TableCellRendererProvider cellrendererprovider) { this.subformtbl.setTableCellRendererProvider(cellrendererprovider); } public void endEditing() { if (subformtbl.getCellEditor() != null) { subformtbl.getCellEditor().stopCellEditing(); } this.rowHeader.endEditing(); this.rowHeightCtrl.clearEditorHeight(); } @Override public void setEnabled(boolean enabled) { this.enabled = enabled; setEnabledState(enabled && !readonly && enabledByLayout); } public void setEnabledByLayout(boolean enabled) { this.enabledByLayout = enabled; } private void setEnabledState(boolean enabled) { super.setEnabled(enabled); setToolbarFunctionState(ToolbarFunction.NEW, enabled ? ToolbarFunctionState.ACTIVE : ToolbarFunctionState.DISABLED); setToolbarFunctionState(ToolbarFunction.MULTIEDIT, uniqueMasterColumnName != null ? (enabled ? ToolbarFunctionState.ACTIVE : ToolbarFunctionState.DISABLED) : ToolbarFunctionState.HIDDEN); setToolbarFunctionState(ToolbarFunction.TRANSFER, toolbarButtons.get(ToolbarFunction.TRANSFER.name()).isVisible() ? (enabled ? ToolbarFunctionState.ACTIVE : ToolbarFunctionState.DISABLED) : ToolbarFunctionState.HIDDEN); setToolbarFunctionState(ToolbarFunction.DOCUMENTIMPORT, toolbarButtons.get(ToolbarFunction.DOCUMENTIMPORT.name()).isVisible() ? (enabled ? ToolbarFunctionState.ACTIVE : ToolbarFunctionState.DISABLED) : ToolbarFunctionState.HIDDEN); } public void setNewEnabled(ScriptContext sc) { boolean enabled = this.enabled && !readonly && enabledByLayout; if (enabled && getNewEnabledScript() != null) { Object o = ScriptEvaluator.getInstance().eval(getNewEnabledScript(), sc, enabled); if (o instanceof Boolean) { enabled = (Boolean) o; } } setToolbarFunctionState(ToolbarFunction.NEW, enabled ? ToolbarFunctionState.ACTIVE : ToolbarFunctionState.DISABLED); boolean documents = getToolbarButton(ToolbarFunction.DOCUMENTIMPORT.name()).isVisible(); if (documents) { setToolbarFunctionState(ToolbarFunction.DOCUMENTIMPORT, enabled ? ToolbarFunctionState.ACTIVE : ToolbarFunctionState.DISABLED); } } public void setReadOnly(boolean readonly) { this.readonly = readonly; setEnabledState(enabled && !readonly); } public boolean isReadOnly() { return readonly; } public void setCollectableComponentFactory(CollectableComponentFactory collectableComponentFactory) { this.collectableComponentFactory = collectableComponentFactory; } /** * @param listener */ public synchronized void addChangeListener(ChangeListener listener) { this.lstchangelistener.add(listener); } /** * @param listener */ public synchronized void removeChangeListener(ChangeListener listener) { this.lstchangelistener.remove(listener); } /** * sets the detailsChangedIgnored property. * @param bDetailsChangedIgnored * @postcondition isDetailsChangedIgnored() == bDetailsChangedIgnored * @see #isDetailsChangedIgnored() * TODO move to DetailsController */ public final void setDetailsChangedIgnored(boolean bDetailsChangedIgnored) { this.bDetailsChangedIgnored = bDetailsChangedIgnored; } /** * @return Is detailsChanged() ignored? If so, detailsChanged() won't be called when the values of * <code>CollectableComponent</code>s change. * TODO move to DetailsController */ public final boolean isDetailsChangedIgnored() { return this.bDetailsChangedIgnored; } /** * fires a <code>ChangeEvent</code> whenever the model of this <code>SubForm</code> changes. */ public synchronized void fireStateChanged() { if (layer == null || (layer != null && !((LockableUI) layer.getUI()).isLocked())) { final ChangeEvent ev = new ChangeEvent(this); if (!isDetailsChangedIgnored()) { for (ChangeListener changelistener : lstchangelistener) { changelistener.stateChanged(ev); } } } } public void fireFocusGained() { AWTEvent event = EventQueue.getCurrentEvent(); if (event instanceof KeyEvent) { if (getJTable().getModel().getRowCount() > 0) { getJTable().editCellAt(0, 0); getSubformTable().changeSelection(0, 0, false, false); } else if (getJTable().getModel().getRowCount() == 0) { for (FocusActionListener fal : getFocusActionLister()) { fal.focusAction(new EventObject(this)); if (getJTable().editCellAt(0, 0)) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Component editor = getJTable().getEditorComponent(); if (editor != null) editor.requestFocusInWindow(); } }); } } } } } /** * @param column */ public void addColumn(Column column) { this.mpColumns.put(column.getName(), column); } public Column getColumn(String sColumnName) { return this.mpColumns.get(sColumnName); } /** * Handling of intial sorting order (from layoutml). * * @deprecated Sorting order is persisted into user preferences, so why is this? (tp) */ public String getInitialSortingColumn() { return this.sInitialSortingColumn; } public String getInitialSortingOrder() { return this.sInitialSortingOrder; } public void setInitialSortingOrder(String sColumnName, String sInitialSortingOrder) { this.sInitialSortingColumn = sColumnName; this.sInitialSortingOrder = sInitialSortingOrder; } private Font font; public void setFont(Font font) { this.font = font; } public Font getFont() { return font != null ? font : super.getFont(); } public int getMinRowHeight() { if (font == null) return MIN_ROWHEIGHT; int result = 7; result += getFontMetrics(font).getHeight(); if (subformtbl != null) { result += subformtbl.getRowMargin(); } else { LOG.info("getMinRowHeight: subformtbl is null"); } return Math.max(result, MIN_ROWHEIGHT); } /** * @param sColumnName * @return the <code>CollectableComponentType</code> of the column with the given name (default: null). */ public CollectableComponentType getCollectableComponentType(String sColumnName, boolean bSearchable) { final Column column = this.getColumn(sColumnName); CollectableComponentType result = (column != null) ? column.getCollectableComponentType() : null; return result; } /** * @param sColumnName * @return Is the column with the given name visible? (default: true) */ public boolean isColumnVisible(String sColumnName) { final Column column = this.getColumn(sColumnName); return (column == null) || column.isVisible(); } /** * @param sColumnName * @return Is the column with the given name enabled? (default: true) */ public boolean isColumnEnabled(String sColumnName) { final Column column = this.getColumn(sColumnName); return (column == null) ? NuclosEOField.getByField(sColumnName) == null : column.isEnabled(); } /** * @param sColumnName * @return Is the column with the given name insertable? (default: false) */ public boolean isColumnInsertable(String sColumnName) { final Column column = this.getColumn(sColumnName); return (column != null) && column.isInsertable(); } public String getColumnLabel(String sColumnName) { final Column column = this.getColumn(sColumnName); return (column != null) ? column.getLabel() : null; } /** * Returns the column width from the layout, or null if unspecified. */ public Integer getColumnWidth(String cColumnName) { final Column column = this.getColumn(cColumnName); return (column != null) ? column.getWidth() : null; } /** * Returns the column nextfocus component from the layout, or null if unspecified. */ public String getColumnNextFocusComponent(String cColumnName) { final Column column = this.getColumn(cColumnName); return (column != null) ? column.getNextFocusComponent() : null; } /** * @param sColumnName * @return Collection<TransferLookedUpValueAction> the TransferLookedUpValueActions defined for the column with the given name (default: empty collection). */ public Collection<TransferLookedUpValueAction> getTransferLookedUpValueActions(String sColumnName) { // look-up actions belong to the column that perform the look-up (i.e. the LOV/combobox) final Column column = this.getColumn(sColumnName); return (column != null) ? column.getTransferLookedUpValueActions() : Collections.<TransferLookedUpValueAction>emptySet(); } /** * @param sColumnName * @return Collection<ClearAction> the ClearActions defined for the column with the given name (default: empty collection). */ public Collection<ClearAction> getClearActions(String sColumnName) { // clear actions belong to the column that perform the look-up (i.e. the LOV/combobox) final Column column = this.getColumn(sColumnName); return (column != null) ? column.getClearActions() : Collections.<ClearAction>emptySet(); } /** * @param sColumnName * @return Collection<RefreshValueListAction> the RefreshValueListActions defined for the column with the given name (default: empty collection). */ public Collection<RefreshValueListAction> getRefreshValueListActions(String sColumnName) { // refresh actions belong to the column that needs the refresh (_not_ to the column that triggers the refresh) final Column column = this.getColumn(sColumnName); return (column != null) ? column.getRefreshValueListActions() : Collections.<RefreshValueListAction>emptySet(); } public CollectableFieldsProvider getValueListProvider(String sColumnName) { final Column column = this.getColumn(sColumnName); return (column != null) ? column.getValueListProvider() : null; } public SizeKnownListener getSizeKnownListener() { return sizeKnownListener; } public void setSizeKnownListener(SizeKnownListener skl) { sizeKnownListener = skl; } /** * @see #getUniqueMasterColumnName() */ public void setUniqueMasterColumnName(String uniqueMasterColumnName) { this.uniqueMasterColumnName = uniqueMasterColumnName; if (uniqueMasterColumnName != null) setToolbarFunctionState(ToolbarFunction.MULTIEDIT, isEnabled() ? ToolbarFunctionState.ACTIVE : ToolbarFunctionState.DISABLED); else setToolbarFunctionState(ToolbarFunction.MULTIEDIT, ToolbarFunctionState.HIDDEN); } /** * @return the name of the unique master column, if any. This is used for the multi edit facility. */ public String getUniqueMasterColumnName() { return this.uniqueMasterColumnName; } /** * set the parent subform for this subform, if the data of this subform depends * on the selected row of the parent subform instead on the curent collectable * @param sSubFormParent */ public void setParentSubForm(String sSubFormParent) { this.sSubFormParent = sSubFormParent; } /** * @return parent subform for this subform */ public String getParentSubForm() { return this.sSubFormParent; } /** * sets the controller type for this subform. This is used to create an appropriate controller for this subform. * @param sControllerType */ public void setControllerType(String sControllerType) { this.sControllerType = sControllerType; } /** * @return the controller type for this subform. This is used to create an appropriate controller for this subform. * null == "default": Use default controller. Otherwise use a special controller. */ public String getControllerType() { return this.sControllerType; } /** * @return this subform's toolbar containing the buttons for adding or deleting rows. The toolbar may be customized * for specific needs. */ // public JToolBar getToolBar() { // return this.toolbar; // } public PopupMenuMouseAdapter getPopupMenuAdapter() { return popupMenuAdapter; } public void setPopupMenuAdapter(PopupMenuMouseAdapter popupMenuAdapter) { this.popupMenuAdapter = popupMenuAdapter; } @Override public final TableCellRenderer getTableCellRenderer(CollectableEntityField clctef) { return this.mpColumnRenderer.get(clctef); } public final void setupTableCellRenderers(CollectableEntity clcte, CollectableEntityFieldBasedTableModel subformtblmdl, CollectableFieldsProviderFactory clctfproviderfactory, boolean bSearchable) { // setup a table cell renderer for each column: for (int iColumnNr = 0; iColumnNr < subformtblmdl.getColumnCount(); iColumnNr++) { final CollectableEntityField clctef = subformtblmdl.getCollectableEntityField(iColumnNr); final String sColumnName = clctef.getName(); final CollectableComponentType clctcomptype; if (sColumnName.equals(this.getForeignKeyFieldToParent())) { // in some cases a column for this foreign key field is created. But if it is a ComboBox, refreshValueList() later will load all values from server... bad thing... clctcomptype = new CollectableComponentType(CollectableComponentTypes.TYPE_LISTOFVALUES, null); } else { clctcomptype = this.getCollectableComponentType(sColumnName, bSearchable); } final CollectableComponent clctcomp = CollectableComponentFactory.getInstance() .newCollectableComponent(clcte, sColumnName, clctcomptype, bSearchable); if (/*clctef.isIdField() && clctef.isReferencing()&&*/clctcomp instanceof LabeledCollectableComponentWithVLP && !isDynamicTableCellEditorNeeded(clctef.getName())) { LabeledCollectableComponentWithVLP clctWithVLP = (LabeledCollectableComponentWithVLP) clctcomp; CollectableFieldsProvider valuelistprovider = getValueListProvider(sColumnName); if (valuelistprovider == null) { valuelistprovider = clctfproviderfactory.newDefaultCollectableFieldsProvider(clcte.getName(), clctWithVLP.getFieldName()); } clctWithVLP.setValueListProvider(valuelistprovider); if (!bLayout) clctWithVLP.refreshValueList(true); } final Column subformcolumn = this.getColumn(sColumnName); if (subformcolumn != null) { final Integer iRows = subformcolumn.getRows(); if (iRows != null) { clctcomp.setRows(iRows); } final Integer iColumns = subformcolumn.getColumns(); if (iColumns != null) { clctcomp.setColumns(iColumns); } final Map<String, Object> properties = subformcolumn.getProperties(); for (String property : properties.keySet()) { clctcomp.setProperty(property, properties.get(property)); } } clctcomp.setToolTipText(clctef.getDescription()); final JComponent c = clctcomp.getJComponent(); if (c instanceof LabeledComponent) { ((LabeledComponent) c).getLabeledComponentSupport().setColorProvider(null); } mpColumnRenderer.put(clctef, clctcomp.getTableCellRenderer(true)); } } private CollectableComponentType getTypeFromClassField(SubFormTableModel tableModel, String fieldname, int rowIndex) { int typeId = CollectableComponentTypes.TYPE_TEXTFIELD; JTable table = getJTable(); Object oValue = table.getModel().getValueAt(rowIndex, tableModel.findColumnByFieldName(fieldname)); try { typeId = CollectableUtils.getCollectableComponentTypeForClass(Class.forName(oValue.toString())); } catch (ClassNotFoundException e) { throw new CommonFatalException(e); } return new CollectableComponentType(typeId, null); } private Class<?> getClassFromFieldname(SubFormTableModel tableModel, String fieldName, int rowIndex) throws ClassNotFoundException { JTable table = getJTable(); Object oValue = table.getModel().getValueAt(rowIndex, tableModel.findColumnByFieldName(fieldName)); return Class.forName(oValue.toString()); } /** * sets all column widths to user preferences; set optimal width if no preferences yet saved * @param tableColumnWidthsFromPreferences */ public final void setColumnWidths(List<Integer> tableColumnWidthsFromPreferences) { useCustomColumnWidths = !tableColumnWidthsFromPreferences.isEmpty() && tableColumnWidthsFromPreferences.size() <= subformtbl.getColumnCount(); if (useCustomColumnWidths) { assert (tableColumnWidthsFromPreferences.size() <= subformtbl.getColumnCount()); final Enumeration<TableColumn> enumeration = subformtbl.getColumnModel().getColumns(); int iColumn = 0; while (enumeration.hasMoreElements()) { final TableColumn column = enumeration.nextElement(); final int iPreferredCellWidth; if (iColumn < tableColumnWidthsFromPreferences.size()) { // known column iPreferredCellWidth = tableColumnWidthsFromPreferences.get(iColumn++); } else { // new column iPreferredCellWidth = getDefaultColumnWidth(column, iColumn++); } column.setPreferredWidth(iPreferredCellWidth); column.setWidth(iPreferredCellWidth); } } else { resetDefaultColumnWidths(); } } public final void resetDefaultColumnWidths() { if (subformtbl != null) { for (int iColumn = 0; iColumn < subformtbl.getColumnCount(); iColumn++) { final TableColumn tableColumn = subformtbl.getColumnModel().getColumn(iColumn); final int width = getDefaultColumnWidth(tableColumn, iColumn); tableColumn.setPreferredWidth(width); tableColumn.setWidth(width); } useCustomColumnWidths = false; subformtbl.revalidate(); } } private int getDefaultColumnWidth(TableColumn tc, int iColumn) { final Integer preferredCellWidth = getColumnWidth("" + tc.getIdentifier()); final int width = (preferredCellWidth != null) ? preferredCellWidth : Math.max(TableUtils.getPreferredColumnWidth(subformtbl, iColumn, 50, TableUtils.TABLE_INSETS), subformtbl.getSubFormModel().getMinimumColumnWidth(iColumn)); return width; } public void setupTableModelListener() { this.tblmdllistener = new TableModelListener() { @Override public void tableChanged(TableModelEvent ev) { // recalculate the optimal column widths when custom column widths are not used yet and // a row is inserted (the first time): //@todo: this here is entered not only the first time a row is entered but also on startup (sort fires a tableDataChanged) if (!useCustomColumnWidths) { // Now this is an example for an API that sucks :( final boolean bRowsInserted = (ev.getType() == TableModelEvent.INSERT) || // @todo: The first condition (INSERT) is clear, but what for the second (complete UPDATE)? (ev.getType() == TableModelEvent.UPDATE && ev.getColumn() == TableModelEvent.ALL_COLUMNS && ev.getLastRow() == Integer.MAX_VALUE); if (bRowsInserted) { resetDefaultColumnWidths(); LOG.debug("Custom column widths should be used here."); // Setting them manually."); } } // TableModelEvents caused by sorting don't change the subform state: if (!(ev instanceof SortableTableModelEvent)) { fireStateChanged(); } } }; subformtbl.getModel().addTableModelListener(this.tblmdllistener); } public void removeTableModelListener() { subformtbl.getModel().removeTableModelListener(this.tblmdllistener); this.tblmdllistener = null; } public final void setupColumnModelListener() { this.columnmodellistener = new TableColumnModelAdapter() { @Override public void columnMoved(TableColumnModelEvent ev) { // workaround for JTable flaw: if (subformtbl.isEditing()) subformtbl.getCellEditor().stopCellEditing(); } @Override public void columnMarginChanged(ChangeEvent e) { useCustomColumnWidths = true; } }; subformtbl.getColumnModel().addColumnModelListener(this.columnmodellistener); } public void removeColumnModelListener() { subformtbl.getColumnModel().removeColumnModelListener(this.columnmodellistener); this.columnmodellistener = null; } /** * Implementation of <code>TableCellEditorProvider</code>. * @param tbl * @param iRow row of the table (not the table model) * @param clcte * @param clctefTarget * @param subformtblmdl * @param bSearchable * @param prefs * @param getCollectableFieldsProviderFactory * @param parameterProvider * @return a <code>TableCellEditor</code> for columns that need a dynamic <code>TableCellEditor</code>. * <code>null</code> for all other columns. * @todo move back to SubFormController */ public TableCellEditor getTableCellEditor(JTable tbl, int iRow, CollectableEntity clcte, CollectableEntityField clctefTarget, final SubFormTableModel subformtblmdl, boolean bSearchable, Preferences prefs, CollectableFieldsProviderFactory getCollectableFieldsProviderFactory, SubFormParameterProvider parameterProvider) { CollectableComponentTableCellEditor result; final String sColumnNameTarget = clctefTarget.getName(); if (sColumnNameTarget.equals("entityfieldDefault")) { try { CollectableEntityField newEntityField = new DefaultCollectableEntityField(clctefTarget.getName(), getClassFromFieldname(subformtblmdl, "datatype", iRow), clctefTarget.getLabel(), clctefTarget.getDescription(), clctefTarget.getMaxLength(), clctefTarget.getPrecision(), clctefTarget.isNullable(), clctefTarget.getFieldType(), clctefTarget.getFormatInput(), clctefTarget.getFormatOutput(), clcte.getName(), clctefTarget.getDefaultComponentType()); CollectableComponentType type = getTypeFromClassField(subformtblmdl, "datatype", iRow); CollectableComponent clctcomp = DefaultCollectableComponentFactory.getInstance() .newCollectableComponent(newEntityField, type, bSearchable); result = createTableCellEditor(clctcomp); } catch (ClassNotFoundException ex) { result = this.mpStaticColumnEditors.get(clctefTarget); } } else if (!this.isDynamicTableCellEditorNeeded(sColumnNameTarget)) { result = this.mpStaticColumnEditors.get(clctefTarget); } else { // We need a dynamic CellEditor: result = this.newTableCellEditor(clcte, sColumnNameTarget, bSearchable, prefs, subformtblmdl); final CollectableComponent ccmp = result.getCollectableComponent(); // CollectableComponent may be LOV when validity tolerance mode is activated if (ccmp instanceof LabeledCollectableComponentWithVLP) { final LabeledCollectableComponentWithVLP clctWithVLP = (LabeledCollectableComponentWithVLP) ccmp; final Collection<RefreshValueListAction> collRefreshValueListActions = getRefreshValueListActions( sColumnNameTarget); assert !collRefreshValueListActions.isEmpty(); // check that the value list provider was not set: assert clctWithVLP.getValueListProvider() == null; // set the value list provider (dynamically): CollectableFieldsProvider valuelistprovider = this.getValueListProvider(sColumnNameTarget); if (valuelistprovider == null) { // If no provider was set, use the dependant provider for dynamic cell editors by default: valuelistprovider = getCollectableFieldsProviderFactory.newDependantCollectableFieldsProvider( this.getEntityName(), clctWithVLP.getEntityField().getName()); } clctWithVLP.setValueListProvider(valuelistprovider); // set parameters: for (RefreshValueListAction rvlact : collRefreshValueListActions) { setParameterForRefreshValueListAction(rvlact, iRow, clctWithVLP, subformtblmdl, parameterProvider); } // refresh value list: if (!bLayout) clctWithVLP.refreshValueList(false); } } return result; } public static void setParameterForRefreshValueListAction(RefreshValueListAction rvlact, int iRow, LabeledCollectableComponentWithVLP clctWithVLP, SubFormTableModel subformtblmdl, SubFormParameterProvider parameterProvider) { final String sParentComponentName = rvlact.getParentComponentName(); final String sParentComponentEntityName = rvlact.getParentComponentEntityName(); final CollectableField clctfParent = parameterProvider.getParameterForRefreshValueList(subformtblmdl, iRow, sParentComponentName, sParentComponentEntityName); Object oValue = (clctfParent.getFieldType() == CollectableField.TYPE_VALUEIDFIELD) ? clctfParent.getValueId() : clctfParent.getValue(); clctWithVLP.getValueListProvider().setParameter(rvlact.getParameterNameForSourceComponent(), oValue); } private static class LookupValuesListener implements LookupListener { private final SubFormTableModel subformtblmdl; private final boolean bSearchable; private final SubFormTable subformtbl; private final Collection<TransferLookedUpValueAction> collTransferValueActions; private LookupValuesListener(SubFormTableModel subformtblmdl, boolean bSearchable, SubFormTable subformtbl, Collection<TransferLookedUpValueAction> collTransferValueActions) { this.subformtblmdl = subformtblmdl; this.bSearchable = bSearchable; this.subformtbl = subformtbl; this.collTransferValueActions = collTransferValueActions; } @Override public void lookupSuccessful(LookupEvent ev) { transferLookedUpValues(ev.getSelectedCollectable(), subformtbl, bSearchable, subformtbl.getEditingRow(), collTransferValueActions, true); } @Override public int getPriority() { return 1; } } private static class LookupClearListener implements LookupListener { private final SubFormTableModel subformtblmdl; private final SubFormTable subformtbl; final Collection<ClearAction> collClearActions; private LookupClearListener(SubFormTableModel subformtblmdl, SubFormTable subformtbl, Collection<ClearAction> collClearActions) { this.subformtblmdl = subformtblmdl; this.subformtbl = subformtbl; this.collClearActions = collClearActions; } @Override public void lookupSuccessful(LookupEvent ev) { clearValues(subformtblmdl, subformtbl.getEditingRow(), collClearActions); } @Override public int getPriority() { return 1; } } private class SubformModelListener implements CollectableComponentModelListener { private final SubFormTableModel subformtblmdl; private final boolean bSearchable; private final Collection<TransferLookedUpValueAction> collTransferValueActions; private final CollectableComponent clctcomp; private final Collection<ClearAction> collClearActions; private final CollectableComponentTableCellEditor result; private SubformModelListener(SubFormTableModel subformtblmdl, boolean bSearchable, SubFormTable subformtbl, Collection<TransferLookedUpValueAction> collTransferValueActions, CollectableComponent clctcomp, Collection<ClearAction> collClearActions, CollectableComponentTableCellEditor result) { this.subformtblmdl = subformtblmdl; this.bSearchable = bSearchable; this.collTransferValueActions = collTransferValueActions; this.clctcomp = clctcomp; this.collClearActions = collClearActions; this.result = result; } @Override public void valueToBeChanged(DetailsComponentModelEvent ev) { } @Override public void searchConditionChangedInModel(SearchComponentModelEvent ev) { } @Override public void collectableFieldChangedInModel(CollectableComponentModelEvent ev) { if (!collClearActions.isEmpty()) { clearValues(subformtblmdl, result.getLastEditingRow(), collClearActions); } if (!collTransferValueActions.isEmpty()) { Object id = null; try { CollectableField value = clctcomp.getField(); if (value.getFieldType() == CollectableField.TYPE_VALUEIDFIELD) { id = value.getValueId(); } } catch (CollectableFieldFormatException e1) { LOG.warn("collectableFieldChangedInModel failed: " + e1, e1); } Collectable clct = null; String referencedEntity = clctcomp.getEntityField().getReferencedEntityName(); if (referencedEntity != null) { try { clct = Utils.getReferencedCollectable(getEntityName(), clctcomp.getFieldName(), id); } catch (CommonBusinessException ex) { log.error(ex); } } transferLookedUpValues(clct, subformtbl, bSearchable, result.getLastEditingRow(), collTransferValueActions); } } } /** * create a Teblecelleditor for the given CollectableEntityField * @param clcte * @param sColumnName * @param bSearchable * @param prefs * @param subformtblmdl * @return the cell editor */ protected final CollectableComponentTableCellEditor newTableCellEditor(CollectableEntity clcte, String sColumnName, final boolean bSearchable, Preferences prefs, final SubFormTableModel subformtblmdl) { final CollectableComponent clctcomp = newCollectableComponent(clcte, sColumnName, bSearchable, prefs); final CollectableComponentTableCellEditor result = createTableCellEditor(clctcomp); // implement TransferLookedUpValueActions for LOVs: if (clctcomp instanceof CollectableListOfValues) { final CollectableListOfValues clctlov = (CollectableListOfValues) clctcomp; for (LookupListener lookup : lookupListener) { clctlov.addLookupListener(lookup); } final Collection<TransferLookedUpValueAction> collTransferValueActions = getTransferLookedUpValueActions( sColumnName); if (!collTransferValueActions.isEmpty()) { clctlov.addLookupListener( new LookupValuesListener(subformtblmdl, bSearchable, subformtbl, collTransferValueActions)); } final Collection<ClearAction> collClearActions = getClearActions(sColumnName); if (!collClearActions.isEmpty()) { clctlov.addLookupListener(new LookupClearListener(subformtblmdl, subformtbl, collClearActions)); } //} else if (clctcomp instanceof CollectableComboBox) { } else { final Collection<ClearAction> collClearActions = getClearActions(sColumnName); final Collection<TransferLookedUpValueAction> collTransferValueActions = getTransferLookedUpValueActions( sColumnName); if (!collClearActions.isEmpty() || !collTransferValueActions.isEmpty()) { // Better alternative: result.addCellEditorListener(new CellEditorListener()) with overridden editingStopped(ChangeEvent e) // However, that solution had some issues with the save action and checkbox values which are not resolved... result.addCollectableComponentModelListener(new SubformModelListener(subformtblmdl, bSearchable, subformtbl, collTransferValueActions, clctcomp, collClearActions, result)); } } return result; } /** * create a CollectableComponent for the given EntityField * @param clcte * @param sColumnName * @param bSearchable * @param prefs * @return the newly created collectable component */ private CollectableComponent newCollectableComponent(CollectableEntity clcte, String sColumnName, boolean bSearchable, Preferences prefs) { final CollectableComponentType clctcomptype = getCollectableComponentType(sColumnName, bSearchable); final CollectableComponent result = collectableComponentFactory.newCollectableComponent(clcte, sColumnName, clctcomptype, bSearchable); result.setInsertable(bSearchable || isColumnInsertable(sColumnName)); result.setPreferences(prefs); return result; } /** * create an Tablecelleditor for the given component * @param clctcomp * @return the newly created table cell editor */ private CollectableComponentTableCellEditor createTableCellEditor(final CollectableComponent clctcomp) { if (getColumn(clctcomp.getFieldName()) != null) { final Map<String, Object> properties = getColumn(clctcomp.getFieldName()).getProperties(); for (String property : properties.keySet()) { clctcomp.setProperty(property, properties.get(property)); } } final CollectableComponentTableCellEditor result = new CollectableComponentTableCellEditor(clctcomp, clctcomp.isSearchComponent()); result.addCollectableComponentModelListener(getCollectableTableCellEditorChangeListener()); // @see NUCLOS-603. checkboxes and options should setvalue directly. if (clctcomp instanceof CollectableCheckBox) { ((CollectableCheckBox) clctcomp).getJCheckBox().addItemListener(new ItemListener() { @Override public void itemStateChanged(ItemEvent e) { SwingUtilities.invokeLater(new Runnable() { //@see NUCLOSINT-1635 public void run() { try { if (getSubformTable().getModel() instanceof SubFormTableModel) { int row = getSubformTable().getSelectedRow(); int column = ((SubFormTableModel) getSubformTable().getModel()) .findColumnByFieldName(clctcomp.getFieldName()); if (row != -1 && column != -1) { if (getSubformTable().getModel() instanceof SearchConditionTableModel) getSubformTable().setValueAt(clctcomp.getSearchCondition(), row, column); else getSubformTable().setValueAt(clctcomp.getField(), row, column); } } } catch (CollectableFieldFormatException e1) { LOG.warn("could not set value for " + clctcomp.getFieldName(), e1); } } }); } }); } if (clctcomp instanceof CollectableOptionGroup) { ((CollectableOptionGroup) clctcomp).getOptionGroup().addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { try { if (getSubformTable().getModel() instanceof SubFormTableModel) { int row = getSubformTable().getSelectedRow(); int column = ((SubFormTableModel) getSubformTable().getModel()) .findColumnByFieldName(clctcomp.getFieldName()); if (row != -1 && column != -1) getSubformTable().setValueAt(clctcomp.getField(), row, column); } } catch (CollectableFieldFormatException e1) { LOG.warn("could not set value for " + clctcomp.getFieldName(), e1); } } }); } // textarea have to handle Tab in an subform differently. if (clctcomp instanceof NuclosCollectableTextArea) { ((NuclosCollectableTextArea) clctcomp).overrideActionMap(new AbstractAction() { @Override public void actionPerformed(ActionEvent evt) { Component c = (Component) ((NuclosCollectableTextArea) clctcomp).getJTextArea().getParent(); c.dispatchEvent( new KeyEvent(c, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, KeyEvent.VK_TAB)); } }, new AbstractAction() { @Override public void actionPerformed(ActionEvent evt) { Component c = (Component) ((NuclosCollectableTextArea) clctcomp).getJTextArea().getParent(); c.dispatchEvent(new KeyEvent(c, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), KeyEvent.SHIFT_MASK, KeyEvent.VK_TAB)); } }); } if (clctcomp instanceof DynamicRowHeightChangeProvider) { ((DynamicRowHeightChangeProvider) clctcomp).addDynamicRowHeightChangeListener(this); } return result; } public CollectableComponentModelAdapter getCollectableTableCellEditorChangeListener() { return editorChangeListener; } public final void setupStaticTableCellEditors(final JTable tbl, boolean bSearchable, Preferences prefs, final SubFormTableModel subformtblmdl, CollectableFieldsProviderFactory clctfproviderfactory, String sParentEntityName, CollectableEntity clcte) { // setup a table cell editor for each column: // if commented out for NUCLOSINT-1425 (ts, tp): // if (this.isEnabled()) { final String sForeignKeyFieldName = this.getForeignKeyFieldName(sParentEntityName, clcte); for (int iColumnNr = 0; iColumnNr < subformtblmdl.getColumnCount(); iColumnNr++) { final CollectableEntityField clctef = subformtblmdl.getCollectableEntityField(iColumnNr); final String sColumnName = clctef.getName(); if (!this.isDynamicTableCellEditorNeeded(sColumnName)) { // It may be wrong not to set a cell editor if the subform is invisible or disabled. // If the subform is shown or enabled later, the cell editor may be missing. // Note that we do not set a cell editor jfor the foreign key field name, as this is never shown. assert this.isColumnVisible(sColumnName); assert !sColumnName.equals(sForeignKeyFieldName); // A static CellEditor is sufficient and can be set right here: final CollectableComponentTableCellEditor clctcompcelleditor = this.newTableCellEditor(clcte, sColumnName, bSearchable, prefs, subformtblmdl); // set a (static) value list provider for ComboBoxes: final CollectableComponent clctcomp = clctcompcelleditor.getCollectableComponent(); clctcomp.setEnabled(isColumnEnabled(sColumnName)); clctcomp.setToolTipText(clctef.getDescription()); if (clctcomp instanceof LabeledCollectableComponentWithVLP) { final LabeledCollectableComponentWithVLP clctWithVLP = (LabeledCollectableComponentWithVLP) clctcomp; assert clctWithVLP.getValueListProvider() == null; CollectableFieldsProvider valuelistprovider = getValueListProvider(sColumnName); if (valuelistprovider == null) { // If no provider was set, use the default provider for static cell editors by default: valuelistprovider = clctfproviderfactory.newDefaultCollectableFieldsProvider( clcte.getName(), clctWithVLP.getFieldName()); } clctWithVLP.setValueListProvider(valuelistprovider); if (!bLayout) clctWithVLP.refreshValueList(true); } mpStaticColumnEditors.put(clctef, clctcompcelleditor); } } } } /** * Transfers the looked-up values. */ public static void transferLookedUpValues(Collectable clctSelected, SubFormTable subformtbl, boolean isSearchable, int iRow, Collection<TransferLookedUpValueAction> collTransferValueActions) { transferLookedUpValues(clctSelected, subformtbl, isSearchable, iRow, collTransferValueActions, false); } /** * Transfers the looked-up values. */ public static void transferLookedUpValues(Collectable clctSelected, final SubFormTable subformtbl, final boolean isSearchable, int iRow, Collection<TransferLookedUpValueAction> collTransferValueActions, final boolean bSetInModel) { if (isSearchable) return; // do not transfer lookedUp values in search fields. // transfer the looked up values: for (TransferLookedUpValueAction act : collTransferValueActions) { final String sSourceFieldName = act.getSourceFieldName(); final SubForm.SubFormTableModel subformtblmdl = subformtbl.getSubFormModel(); final int iTargetColumn = subformtblmdl.findColumnByFieldName(act.getTargetComponentName()); if (iTargetColumn == -1) { throw new CommonFatalException(SpringLocaleDelegate.getInstance().getMessage("SubForm.2", "Das Unterformular enth\u00e4lt keine Spalte namens {0}", act.getTargetComponentName())); } final CollectableEntityField clctefTarget = subformtblmdl.getCollectableEntityField(iTargetColumn); final Object oValue; if (clctSelected == null) { oValue = subformtblmdl.getNullValue(clctefTarget); } else { final CollectableField clctfValue = clctSelected.getField(sSourceFieldName); assert clctfValue != null; oValue = isSearchable ? getSearchConditionForValue(clctefTarget, clctfValue) : clctfValue; } //if (iRow < 0) iRow = subformtbl.getSelectedRow(); if (iRow >= 0) { if (bSetInModel && !isSearchable) { int iCol = subformtbl.convertColumnIndexToView(iTargetColumn); CollectableComponentTableCellEditor editor = null; if (iCol != -1) editor = (CollectableComponentTableCellEditor) subformtbl.getCellEditor(iRow, iCol); else editor = (CollectableComponentTableCellEditor) subformtbl.getCellEditor(iRow, clctefTarget); if (editor != null) { try { CollectableField oldValue = editor.getCollectableComponent().getField(); editor.getCollectableComponent().setField((CollectableField) oValue); boolean bChanged = true; if (oldValue == null) { bChanged = oValue != null; } else { bChanged = !oldValue.equals((CollectableField) oValue, false); } if (!bChanged) { // always invoke collectableFieldChangedInModel even if oldvalue equals newvalue. CollectableComponent comp = editor.getCollectableComponent(); editor.collectableFieldChangedInModel( new CollectableComponentModelEvent(comp.getModel(), (CollectableField) subformtblmdl.getValueAt(iRow, iTargetColumn), (CollectableField) oValue)); } } catch (CollectableFieldFormatException e) { // ignore this here. } } } subformtblmdl.setValueAt(oValue, iRow, iTargetColumn); } } } /** * Clears the given values. */ private static void clearValues(SubFormTableModel subformtblmdl, int iRow, Collection<ClearAction> collClearActions) { // transfer the looked up values: for (ClearAction act : collClearActions) { final int iTargetColumn = subformtblmdl.findColumnByFieldName(act.getTargetComponentName()); if (iTargetColumn == -1) { throw new CommonFatalException(SpringLocaleDelegate.getInstance().getMessage("SubForm.2", "Das Unterformular enth\u00e4lt keine Spalte namens {0}", act.getTargetComponentName())); } final CollectableEntityField clctefTarget = subformtblmdl.getCollectableEntityField(iTargetColumn); final Object oValue = subformtblmdl.getNullValue(clctefTarget); if (iRow >= 0) { subformtblmdl.setValueAt(oValue, iRow, iTargetColumn); } } } /** * @param clctef * @param clctfValue * @return * @precondition clctfValue != null * @postcondition clctfValue.isNull() --> result == null */ private static CollectableComparison getSearchConditionForValue(CollectableEntityField clctef, CollectableField clctfValue) { if (clctfValue == null) { throw new NullArgumentException("clctfValue"); } return clctfValue.isNull() ? null : new CollectableComparison(clctef, ComparisonOperator.EQUAL, clctfValue); } /** * @param sColumnName * @return Is a dynamic TableCellEditor needed for the column with the given name? * A dynamic TableCellEditor is needed if there is a refresh-possible-values action for this column. */ public final boolean isDynamicTableCellEditorNeeded(String sColumnName) { return !getRefreshValueListActions(sColumnName).isEmpty(); } /** * @return the name of the foreign key field referencing the parent entity * @postcondition result != null */ public final String getForeignKeyFieldName(String sParentEntityName, CollectableEntity clcte) { // Use the field referencing the parent entity from the subform, if any: String result = this.getForeignKeyFieldToParent(); if (result == null) { // Default: Find the field referencing the parent entity from the meta data. // If more than one field applies, throw an exception: for (String sFieldName : clcte.getFieldNames()) { if (sParentEntityName.equals(clcte.getEntityField(sFieldName).getReferencedEntityName())) { if (result == null) { // this is the foreign key field: result = sFieldName; } else { final String sMessage = SpringLocaleDelegate.getInstance().getMessage("SubForm.4", "Das Unterformular f\u00fcr die Entit\u00e4t \"{0}\" enth\u00e4lt mehr als ein Fremdschl\u00fcsselfeld, das die \u00fcbergeordnete Entit\u00e4t \"{1}\" referenziert:\n\t{2}\n\t{3}\nBitte geben Sie das Feld im Layout explizit an.", clcte.getName(), sParentEntityName, result, sFieldName); throw new CommonFatalException(sMessage); } } } } if (result == null) { throw new CommonFatalException(SpringLocaleDelegate.getInstance().getMessage("SubForm.3", "Das Unterformular f\u00fcr die Entit\u00e4t \"{0}\" enth\u00e4lt kein Fremdschl\u00fcsselfeld, das die \u00fcbergeordnete Entit\u00e4t \"{1}\" referenziert.\nBitte geben Sie das Feld im Layout explizit an.", clcte.getName(), sParentEntityName)); } assert result != null; return result; } public static class DynamicRowHeightCellRenderer implements TableCellRenderer { private final TableCellRenderer mainRenderer; DynamicRowHeightSupport support; private final int col; private final RowHeightController ctrl; public DynamicRowHeightCellRenderer(TableCellRenderer mainRenderer, DynamicRowHeightSupport support, int col, RowHeightController ctrl) { super(); this.mainRenderer = mainRenderer; this.support = support; this.col = col; this.ctrl = ctrl; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = mainRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); ctrl.setHeight(col, row, support.getHeight(c)); return c; } } /** * inner class SubForm.Table. */ public static class SubFormTable extends CommonJTable implements Closeable { @Override public boolean isRequestFocusEnabled() { return true; } public static class SubFormTableRowSorter extends TableRowSorter<TableModel> { SubFormTableRowSorter(TableModel model) { super(model); } @Override public boolean isSortable(int column) { // At the moment, sorting still is performed via the SortableTableModel return false; } } /** * TODO: It would be very nice to have a static inner class here. However, * when I tried this (with a reference to the TableModel in the constructor) * I trashed the WYSIWYG subform editor (e.g. properties is complex Subform * cases (Accelingua)). (tp) */ private class SubformTableColumnModel extends DefaultTableColumnModel implements Closeable { private boolean closed = false; private SubformTableColumnModel(TableModel model) { } @Override public void close() { // Close is needed for avoiding memory leaks // If you want to change something here, please consult me (tp). if (!closed) { LOG.debug("close() SubformTableColumnModel: " + this); tableColumns.clear(); closed = true; } } @Override public void addColumn(TableColumn column) { if (getModel() instanceof SubFormTableModel) { // NUCLEUSINT-742: the identifier of the column is now the entity field name // (instead of the localized label) String fieldName = ((SubFormTableModel) getModel()).getColumnFieldName(column.getModelIndex()); column.setIdentifier(fieldName); } super.addColumn(column); } @Override public void propertyChange(PropertyChangeEvent evt) { super.propertyChange(evt); fireColumnPropertyChange(evt); } protected void fireColumnPropertyChange(PropertyChangeEvent evt) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TableColumnModelListener.class && listeners[i + 1] instanceof TableColumnModelExtListener) { ((TableColumnModelExtListener) listeners[i + 1]).columnPropertyChange(evt); } } } } private TableCellEditorProvider celleditorprovider; private TableCellRendererProvider cellrendererprovider; private SubForm subform; private SubformRowHeader rowheader; private boolean newRowOnNext = false; // private SubformTableColumnModel myTableColumnModel; private boolean closed = false; public SubFormTable() { setCellSelectionEnabled(true); } private SubFormTable(SubForm sub) { this(); this.subform = sub; getColumnModel().addColumnModelListener(new TableColumnModelAdapter() { @Override public void columnMarginChanged(ChangeEvent e) { super.columnMarginChanged(e); // updateRowHeights(); } }); } public SubForm getSubForm() { return subform; } private void updateRowHeights() { for (int iRow = 0; iRow < getRowCount(); iRow++) { final int iHeight = subform.rowHeightCtrl.getMaxRowHeight(iRow); if (iHeight > 0) { setRowHeightStrict(iRow, subform.getValidRowHeight(iHeight)); } else { setRowHeight(iRow, subform.getMinRowHeight()); } } } @Override public void close() { // Close is needed for avoiding memory leaks // If you want to change something here, please consult me (tp). if (!closed) { LOG.debug("close() SubFormTable: " + this); if (getModel() instanceof SubformTableColumnModel) { ((SubformTableColumnModel) getModel()).close(); } if (getColumnModel() instanceof SubformTableColumnModel) { ((SubformTableColumnModel) getColumnModel()).close(); } // not allowed by swing // setModel(null); if (rowheader != null) { rowheader.close(); } rowheader = null; closed = true; } } private FocusListSelectionListener focusListSelectionListener; public void setFocusListSelectionListener(FocusListSelectionListener focusListSelectionListener) { this.focusListSelectionListener = focusListSelectionListener; } private String prevComponent = null; private String getComponentBefore(String identifier) { String result = null; for (Column column : this.getSubForm().getColumns()) { if (column.getNextFocusComponent() != null && column.getNextFocusComponent().equals(identifier)) { result = column.getName(); if (prevComponent == null || prevComponent.equals(result)) return result; } } return result; } private void setPrevComponent(String prevComponent) { this.prevComponent = prevComponent; SubformRowHeader rowHeader = getSubForm().getSubformRowHeader(); if ((rowHeader.getHeaderTable() instanceof HeaderTable)) { ((HeaderTable) rowHeader.getHeaderTable()).setPreviousComponent(prevComponent); } } public void setPreviousComponent(String prevComponent) { this.prevComponent = prevComponent; } @Override public void changeSelection(int rowIndex, final int columnIndex, boolean toggle, boolean extend) { final AWTEvent event = EventQueue.getCurrentEvent(); if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (ke.isShiftDown() || ke.isControlDown()) { newRowOnNext = false; SubformRowHeader rowHeader = getSubForm().getSubformRowHeader(); boolean blnHasFixedRows = (rowHeader != null && rowHeader.getHeaderTable().getColumnCount() > 1); if (blnHasFixedRows && columnIndex == getColumnCount() - 1) rowIndex = rowIndex + 1 == getRowCount() ? 0 : rowIndex + 1; } } if (event instanceof MouseEvent) { super.changeSelection(rowIndex, columnIndex, toggle, extend); } else changeSelection(rowIndex, columnIndex, toggle, extend, false); int colCount = getColumnCount(); if (columnIndex == colCount - 1) { if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (!ke.isShiftDown() && !ke.isControlDown()) newRowOnNext = true; else newRowOnNext = false; } else newRowOnNext = true; } else if (event instanceof MouseEvent) newRowOnNext = false; if (getSubForm() == null) { super.changeSelection(rowIndex, columnIndex, toggle, extend); return; } String sNextColumn = getSelectedColumn() == -1 ? null : getSubForm().getColumnNextFocusComponent( (String) getColumnModel().getColumn(getSelectedColumn()).getIdentifier()); if (sNextColumn != null) { int colIndex = -1; try { colIndex = getColumnModel().getColumnIndex(sNextColumn); } catch (Exception e) { // ignore. } if (colIndex != -1) { if (colIndex <= getSelectedColumn() && rowIndex == getRowCount() - 1) newRowOnNext = true; else newRowOnNext = false; } else newRowOnNext = false; } } public void changeSelection(final int rwIndex, final int clIndex, boolean toggle, boolean extend, final boolean fixed) { boolean bChange = true; if (getColumnCount() <= getSelectedColumn()) return; String sNextColumn = getSelectedColumn() == -1 || fixed ? null : getSubForm().getColumnNextFocusComponent( (String) getColumnModel().getColumn(getSelectedColumn()).getIdentifier()); final AWTEvent event = EventQueue.getCurrentEvent(); int rowIndex = rwIndex; int colIndex; try { if (event instanceof KeyEvent && !fixed) { final KeyEvent ke = (KeyEvent) event; if (ke.isShiftDown() || ke.isControlDown()) { sNextColumn = getSelectedColumn() == -1 ? null : getComponentBefore( (String) getColumnModel().getColumn(getSelectedColumn()).getIdentifier()); } } setPrevComponent(null); colIndex = sNextColumn == null || fixed ? clIndex : getColumnModel().getColumnIndex(sNextColumn); if (sNextColumn != null) { String colIdentifier = (String) getColumnModel().getColumn(getSelectedColumn()).getIdentifier(); if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (ke.isShiftDown() || ke.isControlDown()) { ;//sNextColumn = null; } else setPrevComponent(colIdentifier); } else setPrevComponent(colIdentifier); } } catch (IllegalArgumentException e) { final SubformRowHeader rowHeader = getSubForm().getSubformRowHeader(); boolean blnHasFixedRows = (rowHeader != null && rowHeader.getHeaderTable().getColumnCount() > 1); if (blnHasFixedRows) { colIndex = rowHeader.getHeaderTable().getColumnModel().getColumnIndex(sNextColumn); if (newRowOnNext && getSelectedRow() == getRowCount() - 1) { final int col = colIndex; final int row = getRowCount(); for (FocusActionListener fal : subform.getFocusActionLister()) { fal.focusAction(new EventObject(this)); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (!(rowHeader.getHeaderTable() instanceof HeaderTable)) rowHeader.getHeaderTable().changeSelection(row, col, false, false); else ((HeaderTable) rowHeader.getHeaderTable()).changeSelection(row, col, false, false, true); } }); } } else if (!(rowHeader.getHeaderTable() instanceof HeaderTable)) rowHeader.getHeaderTable().changeSelection(rowIndex, colIndex, false, false); else ((HeaderTable) rowHeader.getHeaderTable()).changeSelection(rowIndex, colIndex, false, false, true); return; } else colIndex = clIndex; } final int columnIndex = colIndex; if (fixed) newRowOnNext = false; if (newRowOnNext) { if (focusListSelectionListener != null) { int cIndex = getSelectedColumn() == getColumnCount() - 1 ? 0 : getSelectedColumn(); if (sNextColumn != null || (((rowIndex == 1 && cIndex == -1) || (rowIndex == 0 && cIndex == 0)) && getSelectedRow() > 0)) { if (sNextColumn != null || (rowIndex == 0 && cIndex == 0 && getSelectedRow() > 0)) { focusListSelectionListener .valueChanged(new ListSelectionEvent(this, rowIndex, getSelectedRow(), false)); bChange = !(event instanceof KeyEvent || event instanceof InvocationEvent) ? true : false; } } } } if (bChange) { if (sNextColumn != null) { int cIndex = sNextColumn == null ? -1 : getColumnModel().getColumnIndex(sNextColumn); if (cIndex != -1) { if (cIndex <= getSelectedColumn()) rowIndex = rowIndex + 1; } } super.changeSelection(rowIndex, columnIndex, toggle, extend); } if (event instanceof KeyEvent || event instanceof InvocationEvent) { if (newRowOnNext) { if (getRowCount() == 1 && !(event instanceof InvocationEvent)) { final KeyEvent ke = (KeyEvent) event; if (!ke.isShiftDown() && !ke.isControlDown()) { for (FocusActionListener fal : subform.getFocusActionLister()) { fal.focusAction(new EventObject(this)); final int row = rowIndex; final int col[] = getNextEditableCell(this, rowIndex, columnIndex, false); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (editCellAt(row + 1, col[1])) { Component editor = getEditorComponent(); if (editor != null) editor.requestFocusInWindow(); changeSelection(row + 1, col[1], false, false); } } }); } } } newRowOnNext = false; if (rowIndex == 0) return; } int colCount = getColumnCount(); if (columnIndex == colCount - 1) { if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (!ke.isShiftDown() && !ke.isControlDown()) newRowOnNext = true; else { newRowOnNext = rowIndex == getRowCount() - 1; } } else newRowOnNext = true; } boolean bShift = false; if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (ke.isShiftDown() || ke.isControlDown()) { bShift = true; } } SubformRowHeader rowHeader = getSubForm().getSubformRowHeader(); boolean blnHasFixedRows = (rowHeader != null && rowHeader.getHeaderTable().getColumnCount() > 1); if (!fixed && ((columnIndex == 0 && !bShift) || (columnIndex == 0 && blnHasFixedRows) || (columnIndex == getColumnCount() - 1 && bShift))) { if (blnHasFixedRows) { if (!(rowHeader.getHeaderTable() instanceof HeaderTable)) if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (!ke.isShiftDown() && !ke.isControlDown()) { rowHeader.getHeaderTable().changeSelection(rowIndex, 0, false, false); } else { rowHeader.getHeaderTable().changeSelection(rowIndex, rowHeader.getHeaderTable().getColumnCount() - 1, false, false); } } else rowHeader.getHeaderTable().changeSelection(rowIndex, 0, false, false); else if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (!ke.isShiftDown() && !ke.isControlDown()) { ((HeaderTable) rowHeader.getHeaderTable()).changeSelection(rowIndex, 0, false, false, true); } else { ((HeaderTable) rowHeader.getHeaderTable()).changeSelection(rowIndex, rowHeader.getHeaderTable().getColumnCount() - 1, false, false, true); } } else ((HeaderTable) rowHeader.getHeaderTable()).changeSelection(rowIndex, 0, false, false, true); return; } } final int row = rowIndex; final boolean bHasNextComponent = sNextColumn != null; if (isCellEditable(rowIndex, columnIndex)) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int rIndex = row; if (event instanceof KeyEvent) { final KeyEvent ke = (KeyEvent) event; if (!ke.isShiftDown() && !ke.isControlDown()) { if (row == 0 && columnIndex == 0 && !fixed) return; } else { if (getColumnCount() - 1 == columnIndex && bHasNextComponent) { rIndex = row - 1 == -1 ? 0 : row - 1; } } } else { if (row == 0 && columnIndex == 0 && !fixed) return; } SubFormTable.super.changeSelection(rIndex, columnIndex, false, false); editCellAt(rIndex, columnIndex, null); } }); } else { final int rowCol[] = getNextEditableCell(this, rowIndex, columnIndex, bShift); if (isCellEditable(rowCol[0], rowCol[1])) { if (!fixed && event instanceof KeyEvent && ((KeyEvent) event).isConsumed()) return; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (editCellAt(rowCol[0], rowCol[1])) { Component editor = getEditorComponent(); if (editor != null) { editor.requestFocusInWindow(); if (rowCol[0] < getRowCount()) changeSelection(rowCol[0], rowCol[1], false, false); } } } }); } else { if (newRowOnNext) { for (FocusActionListener fal : subform.getFocusActionLister()) { fal.focusAction(new EventObject(this)); final int col[] = getNextEditableCell(this, rowIndex + 1, 0, bShift); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (editCellAt(col[0], col[1])) { Component editor = getEditorComponent(); if (editor != null) editor.requestFocusInWindow(); changeSelection(col[0], col[1], false, false); } else if (col[0] >= getRowCount()) { changeSelection(0, col[1], false, false); } } }); } newRowOnNext = false; } else { changeSelection(rowIndex, columnIndex + 1, toggle, extend); } } } } } private int[] getNextEditableCell(JTable table, int row, int col, boolean bReverse) { int rowCol[] = { row, col }; int colCount = getColumnCount(); boolean colFound = false; if (!bReverse) { for (int i = col; i < colCount; i++) { if (table.isCellEditable(row, i)) { colFound = true; rowCol[1] = i; break; } } if (!colFound) { row++; if (row >= getRowCount()) return rowCol; for (int i = 0; i < col; i++) { if (table.isCellEditable(row, i)) { rowCol[0] = row; rowCol[1] = i; break; } } } } else { for (int i = col; i >= 0; i--) { if (table.isCellEditable(row, i)) { colFound = true; rowCol[1] = i; break; } } if (!colFound) { row--; if (row <= 0) return rowCol; for (int i = colCount - 1; i >= 0; i--) { if (table.isCellEditable(row, i)) { rowCol[0] = row; rowCol[1] = i; break; } } } } return rowCol; } @Override protected SubformTableColumnModel createDefaultColumnModel() { return new SubformTableColumnModel(getModel()); } public void setRowHeaderTable(SubformRowHeader rowheader) { this.rowheader = rowheader; if (!(rowheader instanceof FixedColumnRowHeader)) { // NUCLOSINT-491 // NUCLEUSINT-299: focus mysteriously remained in the // toolbar or wherever. Force to the table, when the // row header gets a mouse-press. rowheader.getHeaderTable().addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { requestFocusInWindow(); } }); } } public final SubFormTableModel getSubFormModel() { return (SubFormTableModel) super.getModel(); } @Override public void setBackground(Color color) { super.setBackground(color); if (rowheader != null && rowheader.getHeaderTable() != null) { this.rowheader.getHeaderTable().setBackground(color); final Container parent = rowheader.getHeaderTable().getParent(); if (parent instanceof JViewport) { parent.setBackground(color); } } } @Override public void setTableHeader(JTableHeader tblheader) { super.setTableHeader(tblheader); configureEnclosingScrollPane(); } @Override public boolean isCellEditable(int row, int column) { if ((row > -1 && row < getRowCount()) && (column > -1 && column < getColumnCount())) { if (getModel() instanceof SubFormTableModel) { final int iModelColumn = getColumnModel().getColumn(column).getModelIndex(); final CollectableEntityField clctefTarget = ((SubFormTableModel) getModel()) .getCollectableEntityField(iModelColumn); if (clctefTarget.getName().equals("entityfieldDefault")) { String datatype = getSubFormModel() .getValueAt(row, getSubFormModel().findColumnByFieldName("datatype")).toString(); return !StringUtils.isNullOrEmpty(datatype) && datatype.startsWith("java"); } } return super.isCellEditable(row, column); } return false; } @Override public TableCellRenderer getCellRenderer(int iRow, int iColumn) { TableCellRenderer result = null; if (cellrendererprovider != null && getModel() instanceof SubFormTableModel) { final int iModelColumn = getColumnModel().getColumn(iColumn).getModelIndex(); final CollectableEntityField clctefTarget = ((SubFormTableModel) getModel()) .getCollectableEntityField(iModelColumn); if (clctefTarget.getName().equals("entityfieldDefault")) { String datatype = getSubFormModel() .getValueAt(iRow, getSubFormModel().findColumnByFieldName("datatype")).toString(); if (!StringUtils.isNullOrEmpty(datatype)) { try { final Class<?> clazz = Class.forName(getSubFormModel() .getValueAt(iRow, getSubFormModel().findColumnByFieldName("datatype")) .toString()); final CollectableEntityField newEntityField = new DefaultCollectableEntityField( clctefTarget.getName(), clazz, clctefTarget.getLabel(), clctefTarget.getDescription(), clctefTarget.getMaxLength(), clctefTarget.getPrecision(), clctefTarget.isNullable(), clctefTarget.getFieldType(), clctefTarget.getFormatInput(), clctefTarget.getFormatOutput(), subform.entityName, clctefTarget.getDefaultComponentType()); result = getCellRendererFromClassField(newEntityField, "datatype", iRow); } catch (ClassNotFoundException e) { throw new CommonFatalException(e); } } } result = cellrendererprovider.getTableCellRenderer(clctefTarget); } if (result == null) { result = super.getCellRenderer(iRow, iColumn); } if (subform != null && result instanceof DynamicRowHeightSupport) { result = new DynamicRowHeightCellRenderer(result, (DynamicRowHeightSupport) result, iColumn, subform.rowHeightCtrl); } final TableCellRenderer cellRender = result; return new TableCellRenderer() { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = cellRender.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (c != null && c.getFont() != null && subform != null) c.setFont(c.getFont().deriveFont(subform.getFont().getSize2D())); return c; } }; } private TableCellRenderer getCellRendererFromClassField(CollectableEntityField entityField, String classFieldname, int iRow) { int typeId = CollectableComponentTypes.TYPE_TEXTFIELD; Object oValue = getSubFormModel().getValueAt(iRow, getSubFormModel().findColumnByFieldName(classFieldname)); try { typeId = CollectableUtils.getCollectableComponentTypeForClass(Class.forName(oValue.toString())); } catch (ClassNotFoundException e) { throw new CommonFatalException(e); } CollectableComponent comp = CollectableComponentFactory.getInstance() .newCollectableComponent(entityField, new CollectableComponentType(typeId, null), false); return comp.getTableCellRenderer(true); } public TableCellEditorProvider getTableCellEditorProvider() { return this.celleditorprovider; } public TableCellRendererProvider getTableCellRendererProvider() { return this.cellrendererprovider; } public void setTableCellEditorProvider(TableCellEditorProvider celleditorprovider) { this.celleditorprovider = celleditorprovider; } public void setTableCellRendererProvider(TableCellRendererProvider cellrendererprovider) { this.cellrendererprovider = cellrendererprovider; } public TableCellEditor getCellEditor(int iRow, CollectableEntityField clctefTarget) { TableCellEditor result = null; if (celleditorprovider != null && getModel() instanceof SubFormTableModel) { try { result = celleditorprovider.getTableCellEditor(this, iRow, clctefTarget); } catch (NuclosFieldNotInModelException e) { // expected exception LOG.info("getCellEditor: " + e); result = null; } } if (result == null) { result = getCellEditor(iRow, ((SubFormTableModel) getModel()).getColumn(clctefTarget)); } if (result instanceof CollectableComponentTableCellEditor) { final CollectableComponentTableCellEditor cellEditor = (CollectableComponentTableCellEditor) result; return new CollectableComponentTableCellEditor(cellEditor.getCollectableComponent(), cellEditor.isSearchable()) { @Override public boolean stopCellEditing() { return cellEditor.stopCellEditing(); } @Override public boolean shouldSelectCell(EventObject anEvent) { return cellEditor.shouldSelectCell(anEvent); } @Override public void removeCellEditorListener(CellEditorListener l) { cellEditor.removeCellEditorListener(l); } @Override public boolean isCellEditable(EventObject anEvent) { return cellEditor.isCellEditable(anEvent); } @Override public Object getCellEditorValue() { return cellEditor.getCellEditorValue(); } @Override public void cancelCellEditing() { cellEditor.cancelCellEditing(); } @Override public void addCellEditorListener(CellEditorListener l) { cellEditor.addCellEditorListener(l); } @Override public CollectableComponent getCollectableComponent() { return cellEditor.getCollectableComponent(); } @Override public CellEditorListener[] getCellEditorListeners() { return cellEditor.getCellEditorListeners(); } @Override public void collectableFieldChangedInModel(CollectableComponentModelEvent ev) { cellEditor.collectableFieldChangedInModel(ev); } @Override public void addCollectableComponentModelListener(CollectableComponentModelListener listener) { cellEditor.addCollectableComponentModelListener(listener); } @Override public int getLastEditingRow() { return cellEditor.getLastEditingRow(); } @Override public boolean isSearchable() { return cellEditor.isSearchable(); } @Override public void removeCollectableComponentModelListener( CollectableComponentModelListener listener) { cellEditor.removeCollectableComponentModelListener(listener); } @Override public void searchConditionChangedInModel(SearchComponentModelEvent ev) { cellEditor.searchConditionChangedInModel(ev); } @Override public void valueToBeChanged(DetailsComponentModelEvent ev) { cellEditor.valueToBeChanged(ev); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { boolean bWasDetailsChangeIgnored = subform.isDetailsChangedIgnored(); subform.setDetailsChangedIgnored(true); Component c = cellEditor.getTableCellEditorComponent(table, value, isSelected, row, column); if (c != null && c.getFont() != null && subform != null) c.setFont(c.getFont().deriveFont(subform.getFont().getSize2D())); subform.setDetailsChangedIgnored(bWasDetailsChangeIgnored); return c; } }; } final TableCellEditor cellEditor = result; return new TableCellEditor() { @Override public boolean stopCellEditing() { return cellEditor.stopCellEditing(); } @Override public boolean shouldSelectCell(EventObject anEvent) { return cellEditor.shouldSelectCell(anEvent); } @Override public void removeCellEditorListener(CellEditorListener l) { cellEditor.removeCellEditorListener(l); } @Override public boolean isCellEditable(EventObject anEvent) { return cellEditor.isCellEditable(anEvent); } @Override public Object getCellEditorValue() { return cellEditor.getCellEditorValue(); } @Override public void cancelCellEditing() { cellEditor.cancelCellEditing(); } @Override public void addCellEditorListener(CellEditorListener l) { cellEditor.addCellEditorListener(l); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { boolean bWasDetailsChangeIgnored = subform.isDetailsChangedIgnored(); subform.setDetailsChangedIgnored(true); Component c = cellEditor.getTableCellEditorComponent(table, value, isSelected, row, column); if (c != null && c.getFont() != null && subform != null) c.setFont(c.getFont().deriveFont(subform.getFont().getSize2D())); subform.setDetailsChangedIgnored(bWasDetailsChangeIgnored); return c; } }; } @Override public TableCellEditor getCellEditor(int iRow, int iColumn) { TableCellEditor result = null; if (celleditorprovider != null && getModel() instanceof SubFormTableModel) { final int iModelColumn = getColumnModel().getColumn(iColumn).getModelIndex(); final CollectableEntityField clctefTarget = ((SubFormTableModel) getModel()) .getCollectableEntityField(iModelColumn); try { result = celleditorprovider.getTableCellEditor(this, iRow, clctefTarget); } catch (NuclosFieldNotInModelException e) { // expected exception LOG.info("getCellEditor: " + e); result = null; } } if (result == null) { result = super.getCellEditor(iRow, iColumn); } if (result instanceof CollectableComponentTableCellEditor) { final CollectableComponentTableCellEditor cellEditor = (CollectableComponentTableCellEditor) result; return new CollectableComponentTableCellEditor(cellEditor.getCollectableComponent(), cellEditor.isSearchable()) { @Override public boolean stopCellEditing() { return cellEditor.stopCellEditing(); } @Override public boolean shouldSelectCell(EventObject anEvent) { return cellEditor.shouldSelectCell(anEvent); } @Override public void removeCellEditorListener(CellEditorListener l) { cellEditor.removeCellEditorListener(l); } @Override public boolean isCellEditable(EventObject anEvent) { return cellEditor.isCellEditable(anEvent); } @Override public Object getCellEditorValue() { return cellEditor.getCellEditorValue(); } @Override public void cancelCellEditing() { cellEditor.cancelCellEditing(); } @Override public void addCellEditorListener(CellEditorListener l) { cellEditor.addCellEditorListener(l); } @Override public CollectableComponent getCollectableComponent() { return cellEditor.getCollectableComponent(); } @Override public CellEditorListener[] getCellEditorListeners() { return cellEditor.getCellEditorListeners(); } @Override public void collectableFieldChangedInModel(CollectableComponentModelEvent ev) { cellEditor.collectableFieldChangedInModel(ev); } @Override public void addCollectableComponentModelListener(CollectableComponentModelListener listener) { cellEditor.addCollectableComponentModelListener(listener); } @Override public int getLastEditingRow() { return cellEditor.getLastEditingRow(); } @Override public boolean isSearchable() { return cellEditor.isSearchable(); } @Override public void removeCollectableComponentModelListener( CollectableComponentModelListener listener) { cellEditor.removeCollectableComponentModelListener(listener); } @Override public void searchConditionChangedInModel(SearchComponentModelEvent ev) { cellEditor.searchConditionChangedInModel(ev); } @Override public void valueToBeChanged(DetailsComponentModelEvent ev) { cellEditor.valueToBeChanged(ev); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { boolean bWasDetailsChangeIgnored = subform.isDetailsChangedIgnored(); subform.setDetailsChangedIgnored(true); Component c = cellEditor.getTableCellEditorComponent(table, value, isSelected, row, column); if (c != null && c.getFont() != null && subform != null) c.setFont(c.getFont().deriveFont(subform.getFont().getSize2D())); subform.setDetailsChangedIgnored(bWasDetailsChangeIgnored); return c; } }; } final TableCellEditor cellEditor = result; return new TableCellEditor() { @Override public boolean stopCellEditing() { return cellEditor.stopCellEditing(); } @Override public boolean shouldSelectCell(EventObject anEvent) { return cellEditor.shouldSelectCell(anEvent); } @Override public void removeCellEditorListener(CellEditorListener l) { cellEditor.removeCellEditorListener(l); } @Override public boolean isCellEditable(EventObject anEvent) { return cellEditor.isCellEditable(anEvent); } @Override public Object getCellEditorValue() { return cellEditor.getCellEditorValue(); } @Override public void cancelCellEditing() { cellEditor.cancelCellEditing(); } @Override public void addCellEditorListener(CellEditorListener l) { cellEditor.addCellEditorListener(l); } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { Component c = cellEditor.getTableCellEditorComponent(table, value, isSelected, row, column); if (c != null && c.getFont() != null && subform != null) c.setFont(c.getFont().deriveFont(subform.getFont().getSize2D())); return c; } }; } /** * ensures that this table always fills its enclosing viewport (if any) vertically. * This is useful default behavior (and thus should be defined in JTable already) because of the following reasons: * <ul> * <li>allows dropping into the empty space of a table.</li> * <li>allows opening a popup menu in the empty space of a table.</li> * <li>paints the background color of the table in the empty space, rather than that of the viewport.</li> * </ul> * The alternative each time is to define a transfer handler, a popup menu listener and the background of the enclosing * viewport separately, but that is somewhat awkward. * Code copied from JList. * @todo if this behavior is needed in other places (which it is likely), move to CommonJTable (as optional behavior) */ @Override public boolean getScrollableTracksViewportHeight() { if (getParent() instanceof JViewport) { return (getParent().getHeight() > getPreferredSize().height); } return false; } @Override public void setModel(TableModel dataModel) { super.setModel(dataModel); setRowSorter(new SubFormTableRowSorter(dataModel)); } @Override public void setColumnModel(TableColumnModel columnModel) { final TableColumnModel old = getColumnModel(); if (old instanceof SubformTableColumnModel) { ((SubformTableColumnModel) old).close(); } super.setColumnModel(columnModel); } public void setRowHeightStrict(int iRow, int iHeight) { setRowHeight(iRow, iHeight + getRowMargin()); } @Override public void setRowHeight(int row, int rowHeight) { super.setRowHeight(row, rowHeight); if (rowheader != null) rowheader.setRowHeightInRow(row, rowHeight); } @Override public void setRowHeight(int rowHeight) { super.setRowHeight(rowHeight); if (rowheader != null) rowheader.setRowHeight(rowHeight); } @Override public void columnAdded(TableColumnModelEvent e) { super.columnAdded(e); if (subform != null) { subform.rowHeightCtrl.clear(); invalidateRowHeights(); } } @Override public void columnRemoved(TableColumnModelEvent e) { super.columnRemoved(e); if (subform != null) { subform.rowHeightCtrl.clear(); invalidateRowHeights(); } } @Override public void tableChanged(TableModelEvent e) { super.tableChanged(e); if (subform != null && e.getFirstRow() != TableModelEvent.HEADER_ROW) { if (e.getFirstRow() == 0 && e.getLastRow() == Integer.MAX_VALUE) { subform.rowHeightCtrl.clear(); } else { for (int iRow = e.getFirstRow(); iRow <= e.getLastRow(); iRow++) { if (e.getColumn() == TableModelEvent.ALL_COLUMNS) { subform.rowHeightCtrl.clear(iRow); } else { subform.rowHeightCtrl.clear(e.getColumn(), iRow); } } } } invalidateRowHeights(); } boolean invalidateRowHeights = false; private void invalidateRowHeights() { if (subform != null && !invalidateRowHeights) { invalidateRowHeights = true; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateRowHeights(); invalidateRowHeights = false; } }); } } @Override public boolean editCellAt(int row, int column, EventObject e) { final boolean result = super.editCellAt(row, column, e); if (result) { Component editor = getEditorComponent(); if (editor != null) editor.requestFocusInWindow(); } return result; } public int getRowHeightWithMargin(int row) { return getRowHeight(row) + getRowMargin(); } } public boolean isUseCustomColumnWidths() { return this.useCustomColumnWidths; } /** * @param lookupListener the LookupListener to remove */ public void removeLookupListener(LookupListener lookupListener) { this.lookupListener.remove(lookupListener); } /** * @param lookupListener the LookupListener to add */ public void addLookupListener(LookupListener lookupListener) { this.lookupListener.add(lookupListener); } public void addFocusActionListener(FocusActionListener fal) { lstFocusActionListener.add(fal); } public void removeFocusActionListener(FocusActionListener fal) { lstFocusActionListener.remove(fal); } public List<FocusActionListener> getFocusActionLister() { return lstFocusActionListener; } public void addParameterListener(ParameterChangeListener pl) { parameterListener.add(pl); } public void removeParameterListener(ParameterChangeListener pl) { parameterListener.remove(pl); } /** * fires a <code>ChangeEvent</code> whenever the model of this <code>SubForm</code> changes. */ private synchronized void fireParameterChanged() { if (layer == null || (layer != null && !((LockableUI) layer.getUI()).isLocked())) { final ChangeEvent ev = new ChangeEvent(this); for (ChangeListener changelistener : parameterListener) { changelistener.stateChanged(ev); } } } public NuclosScript getNewEnabledScript() { return newEnabledScript; } public void setNewEnabledScript(NuclosScript newEnabledScript) { this.newEnabledScript = newEnabledScript; } public NuclosScript getEditEnabledScript() { return editEnabledScript; } public void setEditEnabledScript(NuclosScript editEnabledScript) { this.editEnabledScript = editEnabledScript; } public NuclosScript getDeleteEnabledScript() { return deleteEnabledScript; } public void setDeleteEnabledScript(NuclosScript deleteEnabledScript) { this.deleteEnabledScript = deleteEnabledScript; } public NuclosScript getCloneEnabledScript() { return cloneEnabledScript; } public void setCloneEnabledScript(NuclosScript cloneEnabledScript) { this.cloneEnabledScript = cloneEnabledScript; } /** * <code>TableModel</code> that can be used in a <code>SubForm</code>. */ public static interface SubFormTableModel extends CollectableEntityFieldBasedTableModel { /** * @param clctef * @return a null value that can be set in a table cell for the given entity field. */ Object getNullValue(CollectableEntityField clctef); /** * determines the (visible) columns of this model. * Each column is represented by a <code>CollectableEntityField</code>. * @param lstclctefColumns List<CollectableEntityField> */ void setColumns(List<? extends CollectableEntityField> lstclctefColumns); /** * @param columnIndex * @return an unique identifier for the column (usually the name of the entity field). */ public String getColumnFieldName(int columnIndex); /** * @param columnIndex * @return minimum column width based on class */ public int getMinimumColumnWidth(int columnIndex); /** * @param sFieldName * @return the index of the column with the given fieldname. -1 if none was found. */ int findColumnByFieldName(String sFieldName); void remove(int iRow); void remove(int[] rows); } // interface SubFormTableModel /** * inner class TransferLookedUpValueAction. * @todo it might be better to define all of these actions in collect.action. */ public static class TransferLookedUpValueAction { private final String sTargetComponentName; private final String sSourceFieldName; public TransferLookedUpValueAction(String sTargetComponentName, String sSourceFieldName) { this.sTargetComponentName = sTargetComponentName; this.sSourceFieldName = sSourceFieldName; } public String getTargetComponentName() { return this.sTargetComponentName; } public String getSourceFieldName() { return this.sSourceFieldName; } } // inner class TransferLookedUpValueAction /** * inner class ClearAction. */ public static class ClearAction { private final String sTargetComponentName; public ClearAction(String sTargetComponentName) { this.sTargetComponentName = sTargetComponentName; } public String getTargetComponentName() { return this.sTargetComponentName; } } public static class RefreshValueListAction { private final String sTargetComponentName; private final String sParentComponentEntityName; private final String sParentComponentName; private final String sParameterNameForSourceComponent; /** * @param sTargetComponentName * @param sParentComponentEntityName the entity name of the subform (shared by target component and parent component) * or the entity name of the form (in which case the parent component lies in the form, outside the subform). * @param sParentComponentName * @param sParameterNameForSourceComponent the name of the parameter in the valuelistprovider for the source component. */ public RefreshValueListAction(String sTargetComponentName, String sParentComponentEntityName, String sParentComponentName, String sParameterNameForSourceComponent) { this.sTargetComponentName = sTargetComponentName; this.sParentComponentEntityName = sParentComponentEntityName; this.sParentComponentName = sParentComponentName; this.sParameterNameForSourceComponent = sParameterNameForSourceComponent; } public String getTargetComponentName() { return this.sTargetComponentName; } public String getParentComponentEntityName() { return this.sParentComponentEntityName; } public String getParentComponentName() { return this.sParentComponentName; } public String getParameterNameForSourceComponent() { return this.sParameterNameForSourceComponent; } } // inner class RefreshValueListAction /** * A column in a <code>SubForm</code>. */ public static class Column { private final String sName; private String sLabel; private final CollectableComponentType clctcomptype; private final boolean bVisible; private final boolean bEnabled; private final boolean bInsertable; private final Integer iRows; private final Integer iColumns; private final Integer width; private final Integer initialPosition; private final String sNextFocusComponent; private Map<String, Object> mpProperties; /** * Collection<TransferLookedUpValueAction> */ private Collection<TransferLookedUpValueAction> collTransferLookedUpValueActions; /** * Collection<ClearAction> */ private Collection<ClearAction> collClearActions; /** * Collection<RefreshValueListAction> */ private Collection<RefreshValueListAction> collRefreshValueListActions; private CollectableFieldsProvider valuelistprovider; public Column(String sName) { this(sName, null, new CollectableComponentType(null, null), true, true, false, null, null); } public Column(String sName, String sLabel, CollectableComponentType clctcomptype, boolean bVisible, boolean bEnabled, boolean bInsertable, Integer iRows, Integer iColumns) { this(sName, sLabel, clctcomptype, bVisible, bEnabled, bInsertable, iRows, iColumns, null, null); } public Column(String sName, String sLabel, CollectableComponentType clctcomptype, boolean bVisible, boolean bEnabled, boolean bInsertable, Integer iRows, Integer iColumns, Integer width, String sNextFocusComponent) { this.sName = sName; this.sLabel = sLabel; this.clctcomptype = clctcomptype; this.bVisible = bVisible; this.bEnabled = bEnabled; this.bInsertable = bInsertable; this.iRows = iRows; this.iColumns = iColumns; this.width = width; this.initialPosition = null; this.sNextFocusComponent = sNextFocusComponent; } public String getName() { return this.sName; } public String getLabel() { return this.sLabel; } public void setLabel(String label) { this.sLabel = label; } public CollectableComponentType getCollectableComponentType() { return this.clctcomptype; } public boolean isVisible() { return this.bVisible; } public boolean isEnabled() { return this.bEnabled; } public boolean isInsertable() { return this.bInsertable; } /** * @return the number of rows used in the renderer and editor */ public Integer getRows() { return this.iRows; } /** * @return the number of columns used in the renderer and editor */ public Integer getColumns() { return this.iColumns; } /** * Returns the preferred width of this column. */ public Integer getWidth() { return width; } /** * Returns the nextfocuscomponent of this column. */ public String getNextFocusComponent() { return sNextFocusComponent; } /** * Returns the initial position of this column (relative to the other columns). */ public Integer getInitialPosition() { return initialPosition; } @Override public boolean equals(Object oValue) { if (this == oValue) { return true; } if (!(oValue instanceof Column)) { return false; } return LangUtils.equals(getName(), ((Column) oValue).getName()); } @Override public int hashCode() { return LangUtils.hashCode(getName()); } public Collection<TransferLookedUpValueAction> getTransferLookedUpValueActions() { return (collTransferLookedUpValueActions != null) ? Collections.unmodifiableCollection(collTransferLookedUpValueActions) : Collections.<TransferLookedUpValueAction>emptyList(); } public void addTransferLookedUpValueAction(TransferLookedUpValueAction act) { if (collTransferLookedUpValueActions == null) { collTransferLookedUpValueActions = new LinkedList<TransferLookedUpValueAction>(); } collTransferLookedUpValueActions.add(act); } public Collection<ClearAction> getClearActions() { return collClearActions != null ? Collections.unmodifiableCollection(collClearActions) : Collections.<ClearAction>emptyList(); } public void addClearAction(ClearAction act) { if (collClearActions == null) { collClearActions = new LinkedList<ClearAction>(); } collClearActions.add(act); } public Collection<RefreshValueListAction> getRefreshValueListActions() { return collRefreshValueListActions != null ? Collections.unmodifiableCollection(collRefreshValueListActions) : Collections.<RefreshValueListAction>emptyList(); } public void addRefreshValueListAction(RefreshValueListAction act) { if (collRefreshValueListActions == null) { collRefreshValueListActions = new LinkedList<RefreshValueListAction>(); } collRefreshValueListActions.add(act); } public void setValueListProvider(CollectableFieldsProvider valuelistprovider) { this.valuelistprovider = valuelistprovider; } public CollectableFieldsProvider getValueListProvider() { return this.valuelistprovider; } public Object getProperty(String sName) { return getProperties().get(sName); } public void setProperty(String sName, Object oValue) { synchronized (this) { if (mpProperties == null) { mpProperties = new TreeMap<String, Object>(); } } mpProperties.put(sName, oValue); assert LangUtils.equals(getProperty(sName), oValue); } public synchronized Map<String, Object> getProperties() { final Map<String, Object> result = (mpProperties == null) ? Collections.<String, Object>emptyMap() : Collections.unmodifiableMap(mpProperties); assert result != null; return result; } } // inner class Column private class SubFormPopupMenuMouseAdapter extends PopupMenuMouseAdapter { public SubFormPopupMenuMouseAdapter(JTable table) { super(table); } @Override public void doPopup(MouseEvent e) { JTable table = getJTable(); int col = table.columnAtPoint(e.getPoint()); int row = table.rowAtPoint(e.getPoint()); if (col != -1 && row != -1) { TableCellEditor tce = table.getCellEditor(row, col); JPopupMenu menu = null; if (tce instanceof CollectableComponentTableCellEditor) { CollectableComponent clctcmp = ((CollectableComponentTableCellEditor) tce) .getCollectableComponent(); if (clctcmp instanceof JPopupMenuFactory) { Object value = table.getModel().getValueAt(row, table.convertColumnIndexToModel(col)); tce.getTableCellEditorComponent(table, value, false, row, col); // contained field is set without triggering listeners clctcmp.getJComponent().setEnabled(table.isCellEditable(row, col)); menu = ((JPopupMenuFactory) clctcmp).newJPopupMenu(); } } if (menu != null) { table.editCellAt(row, col, e); menu.show(table, e.getX(), e.getY()); } else if (getPopupMenuAdapter() != null) { getPopupMenuAdapter().doPopup(e); } } } } private class DoubleClickMouseAdapter extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { if (e.getClickCount() == 2) { JTable table = getJTable(); int col = table.columnAtPoint(e.getPoint()); int row = table.rowAtPoint(e.getPoint()); if (col != -1 && row != -1) { TableCellEditor tce = table.getCellEditor(row, col); if (tce instanceof CollectableComponentTableCellEditor) { CollectableComponent clctcmp = ((CollectableComponentTableCellEditor) tce) .getCollectableComponent(); if (clctcmp instanceof MouseListener) { Object value = table.getModel().getValueAt(row, table.convertColumnIndexToModel(col)); tce.getTableCellEditorComponent(table, value, false, row, col); // contained field is set without triggering listeners ((MouseListener) clctcmp).mouseClicked(e); } } } } } } } private static class RowHeightController { private Map<Integer, Map<Integer, Integer>> cache = new HashMap<Integer, Map<Integer, Integer>>(); private int iEditorCol = -1; private int iEditorRow = -1; private int iEditorHeight = -1; private final SubForm subform; public RowHeightController(SubForm subform) { this.subform = subform; } public void clear(int iCol, int iRow) { final Map<Integer, Integer> colCache = cache.get(iRow); if (colCache != null) { colCache.remove(iCol); // System.out.println("clearing row=" +iRow + " col=" + iCol); } } public void clear(int iRow) { cache.remove(iRow); // System.out.println("clearing row " +iRow); } public void clear() { cache.clear(); } public void clearEditorHeight() { if (iEditorRow != -1) { subform.getSubformTable().setRowHeightStrict(iEditorRow, subform.getValidRowHeight(getMaxRowHeightCacheOnly(iEditorRow))); } iEditorCol = -1; iEditorRow = -1; iEditorHeight = -1; } public void setEditorHeight(int iCol, int iRow, int iHeight) { if (!subform.isDynamicRowHeights()) return; final int iOldEditorCol = iEditorCol; final int iOldEditorRow = iEditorRow; final int iOldEditorHeight = iEditorHeight; iEditorCol = iCol; iEditorRow = iRow; iEditorHeight = iHeight; if (iOldEditorRow != iEditorRow || iOldEditorCol != iEditorCol) { if (iOldEditorRow != -1) { subform.getSubformTable().setRowHeightStrict(iOldEditorRow, subform.getValidRowHeight(getMaxRowHeightCacheOnly(iOldEditorRow))); } } if (iOldEditorRow != iEditorRow || iOldEditorCol != iEditorCol || iOldEditorHeight != iEditorHeight) { subform.getSubformTable().setRowHeightStrict(iEditorRow, subform.getValidRowHeight(Math.max(iEditorHeight, getMaxRowHeightCacheOnly(iEditorRow)))); final Rectangle r = subform.getSubformTable().getCellRect(iEditorRow, iEditorCol, false); r.y = r.y + r.height - 1; r.height = 1; if (!subform.getSubformScrollPane().getViewport().getViewRect().contains(r)) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { subform.getSubformScrollPane().getViewport().scrollRectToVisible(r); } }); } } } public void setHeight(int iCol, int iRow, int iHeight) { if (!subform.isDynamicRowHeights()) return; Map<Integer, Integer> colCache = cache.get(iRow); if (colCache == null) { colCache = new HashMap<Integer, Integer>(); cache.put(iRow, colCache); } final Integer iOldHeight = colCache.put(iCol, iHeight); if (iOldHeight == null || iOldHeight.intValue() != iHeight) { final int iNewHeight = subform.getValidRowHeight(getMaxRowHeight(colCache)); subform.getSubformTable().setRowHeightStrict(iRow, iNewHeight); // System.out.println("row=" + iRow + " col=" + iCol + " height=" + iHeight + " oldHeight=" + iOldHeight + " newHeight=" + iNewHeight + " heights=" + colCache); } } public int getMaxRowHeight(int iRow) { if (iRow == iEditorRow) { return Math.max(iEditorHeight, getMaxRowHeightCacheOnly(iRow)); } else { return getMaxRowHeightCacheOnly(iRow); } } private int getMaxRowHeightCacheOnly(int iRow) { return getMaxRowHeight(cache.get(iRow)); } private static int getMaxRowHeight(final Map<Integer, Integer> columnHeights) { if (columnHeights != null) { int result = 0; for (Integer iHeight : columnHeights.values()) { result = Math.max(result, iHeight); } return result; } else { return -1; } } } @Override public void heightChanged(int height) { final int iCol = subformtbl.getSelectedColumn(); final int iRow = subformtbl.getSelectedRow(); rowHeightCtrl.setEditorHeight(iCol, iRow, height); } public int getValidRowHeight(int iHeight) { return Math.max(getMinRowHeight() - subformtbl.getRowMargin(), Math.min(MAX_DYNAMIC_ROWHEIGHT, iHeight)); } public boolean isDynamicRowHeights() { return dynamicRowHeights; } public void setDynamicRowHeightsDefault() { dynamicRowHeightsDefault = true; } public boolean isDynamicRowHeightsDefault() { return dynamicRowHeightsDefault; } /** * @return param map to the subform's parent entity. May be <code>null</code>. */ public Map<String, Object> getMapParams() { return Collections.unmodifiableMap(this.mpParams); } public void setMapParams(Map<String, Object> mpParams) { this.mpParams.clear(); this.mpParams.putAll(mpParams); fireParameterChanged(); } public void addToMapParams(String param, Object value) { this.mpParams.put(param, value); fireParameterChanged(); } } // class SubForm