/* * Rapid Beans Framework: EditorPropertyList2Swing.java * * Copyright (C) 2009 Martin Bluemel * * Creation Date: 01/30/2006 * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; * either version 3 of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * You should have received a copies of the GNU Lesser General Public License and the * GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ package org.rapidbeans.presentation.swing; import java.awt.BorderLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.LayoutManager; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.Collection; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import org.rapidbeans.core.basic.Property; import org.rapidbeans.core.basic.PropertyChoice; import org.rapidbeans.core.basic.PropertyCollection; import org.rapidbeans.core.basic.RapidBean; import org.rapidbeans.core.basic.RapidEnum; import org.rapidbeans.core.common.RapidBeansLocale; import org.rapidbeans.core.event.PropertyChangeEvent; import org.rapidbeans.core.event.PropertyChangeEventType; import org.rapidbeans.core.exception.RapidBeansRuntimeException; import org.rapidbeans.core.exception.ValidationException; import org.rapidbeans.core.type.TypePropertyChoice; import org.rapidbeans.core.type.TypePropertyCollection; import org.rapidbeans.datasource.event.AddedEvent; import org.rapidbeans.datasource.event.ChangedEvent; import org.rapidbeans.datasource.event.RemovedEvent; import org.rapidbeans.presentation.Application; import org.rapidbeans.presentation.ApplicationManager; import org.rapidbeans.presentation.EditorBean; import org.rapidbeans.presentation.EditorBeanListener; import org.rapidbeans.presentation.ThreadLocalEventLock; /** * the bean editor GUI for big single or multiple RapidEnum or Collection * choices. * * @author Martin Bluemel */ public class EditorPropertyList2Swing extends EditorPropertySwing implements EditorBeanListener { /** * Provide only valid association partners in the Out list. */ private boolean provideOnlyValidInOut = false; /** * the list frame. */ private JDialog listWindow = new JDialog(); /** * the list panel. */ private JPanel listPanel = new JPanel(); /** * the list panel's layout. */ private LayoutManager listPanelLayout = new GridBagLayout(); /** * the tree view's scroll pane. */ private JScrollPane scrollPaneIn = new JScrollPane(); /** * the label for the chosen. */ private JLabel labelIn = new JLabel(); /** * the list. */ private JList listIn = new JList(); /** * the tree view's scroll pane. */ private JScrollPane scrollPaneOut = new JScrollPane(); /** * the label for the choice. */ private JLabel labelOut = new JLabel(); /** * the list. */ private JList listOut = new JList(); /** * the arrow buttons panel. */ private JPanel arrowButtonsPanel = new JPanel(); /** * the OK button. */ private JButton buttonOk = new JButton(); /** * the button panel. */ private JPanel panelButtons = new JPanel(); /** * the button panel's layout. */ private LayoutManager buttonsPanelLayout = new GridBagLayout(); /** * the arrow button panel's layout. */ private LayoutManager arrowButtonsPanelLayout = new GridBagLayout(); /** * the add button. */ private JButton buttonAdd = new JButton(); /** * the remove button. */ private JButton buttonRemove = new JButton(); /** * @return the editor's widget */ public Object getWidget() { return this.listWindow; } /** * @return the editor's left list widget */ public JList getWidgetListIn() { return this.listIn; } /** * @return the editor's right list widget */ public JList getWidgetListOut() { return this.listOut; } /** * the parent editor. */ private EditorPropertyListSwing parentEditor = null; /** * constructor. * * @param prop * the bean property to edit * @param propBak * the bean property backup * @param bizBeanEditor * the parent bean editor * @param client * the client * @param ed * the parent editor * @param provideOnlyValidInOut * switch to provide only valid values in the "out" list */ public EditorPropertyList2Swing(final Application client, final EditorBean bizBeanEditor, final Property prop, final Property propBak, final EditorPropertyListSwing ed, final boolean provideOnlyValidInOut) { super(client, bizBeanEditor, prop, propBak); super.initColors(); if (this.getProperty().getType().getMandatory()) { this.listPanel.setBackground(COLOR_NORMAL); } this.provideOnlyValidInOut = provideOnlyValidInOut; this.parentEditor = ed; final RapidBeansLocale locale = client.getCurrentLocale(); this.labelIn.setText(locale.getStringGui("commongui.text.chosen")); this.labelIn.setHorizontalAlignment(JLabel.CENTER); this.labelOut.setText(locale.getStringGui("commongui.text.choice")); this.labelOut.setHorizontalAlignment(JLabel.CENTER); this.listWindow.setTitle(this.getProperty().getBean().toStringGuiType(bizBeanEditor.getLocale()) + ": " + this.getProperty().getBean().toStringGui(bizBeanEditor.getLocale()) + ", " + this.getProperty().getNameGui(bizBeanEditor.getLocale())); this.listWindow.setSize(600, 300); this.listWindow.addWindowListener(new WindowAdapter() { public void windowClosed(final WindowEvent e) { handleActionWindowClosed(); } }); this.listPanel.setLayout(this.listPanelLayout); if (prop instanceof PropertyChoice) { this.listIn.setModel(new ModelListChoice((PropertyChoice) prop)); this.listIn.setCellRenderer(new RendererListEnum(client.getCurrentLocale(), this)); } else if (prop instanceof PropertyCollection) { this.listIn.setModel(new ModelListCollection((PropertyCollection) prop, this.getBeanEditor() .getDocumentView().getDocument())); this.listIn.setCellRenderer(new RendererListCollection(bizBeanEditor.getDocumentView().getDocument(), this .getLocale())); } else { throw new RapidBeansRuntimeException("Class \"" + EditorPropertyList2Swing.class + "\" does not support properties of class \"" + prop.getClass().getName() + "\"."); } if (prop instanceof PropertyChoice) { this.listOut.setModel(new ModelListChoiceWithout((PropertyChoice) prop, (TypePropertyChoice) (this .getProperty().getType()))); this.listOut.setCellRenderer(new RendererListEnum(client.getCurrentLocale(), this)); } else if (prop instanceof PropertyCollection) { this.listOut.setModel(new ModelListCollectionAllWithout((PropertyCollection) this.getProperty(), ((TypePropertyCollection) (this.getProperty().getType())).getTargetType(), this.getBeanEditor() .getDocumentView().getDocument(), this.provideOnlyValidInOut)); this.listOut.setCellRenderer(new RendererListCollection(bizBeanEditor.getDocumentView().getDocument(), this .getLocale())); } else { throw new RapidBeansRuntimeException("Class \"" + EditorPropertyList2Swing.class + "\" does not support properties of class \"" + prop.getClass().getName() + "\"."); } this.buttonAdd.setIcon(new ImageIcon(Application.class.getResource("pictures/arrowFullRight.gif"))); this.buttonAdd.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { addSelectedBeans(); } }); this.buttonRemove.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { removeSelectedBeans(); } }); this.buttonRemove.setIcon(new ImageIcon(Application.class.getResource("pictures/arrowFullLeft.gif"))); this.arrowButtonsPanel.setLayout(this.arrowButtonsPanelLayout); this.panelButtons.setLayout(buttonsPanelLayout); this.buttonOk.setText(client.getCurrentLocale().getStringGui("commongui.text.ok")); this.buttonOk.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { handleActionOk(); } }); this.buttonOk.setEnabled(true); this.panelButtons.add(this.buttonOk, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); this.arrowButtonsPanel.add(this.buttonAdd, new GridBagConstraints(0, 0, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); this.arrowButtonsPanel.add(this.buttonRemove, new GridBagConstraints(0, 1, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); this.scrollPaneIn.getViewport().add(this.listIn); this.scrollPaneOut.getViewport().add(this.listOut); this.listPanel.add(this.labelOut, new GridBagConstraints(0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); this.listPanel.add(this.scrollPaneOut, new GridBagConstraints(0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); this.listPanel.add(this.arrowButtonsPanel, new GridBagConstraints(1, 0, 1, 2, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.VERTICAL, new Insets(10, 10, 10, 10), 0, 0)); this.listPanel.add(this.labelIn, new GridBagConstraints(2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); this.listPanel.add(this.scrollPaneIn, new GridBagConstraints(2, 1, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); this.listWindow.getContentPane().add(this.listPanel, BorderLayout.CENTER); this.listWindow.getContentPane().add(this.panelButtons, BorderLayout.SOUTH); this.updateUI(); if (!this.getBeanEditor().getDocumentView().getClient().getTestMode()) { this.listWindow.setVisible(true); // does not make the dialog modal // this.listWindow.setModal(true); } } /** * add selected beans. */ public void addSelectedBeans() { boolean addFailed = false; EditorBean ed = null; final Collection<?> oldValue = (Collection<?>) this.getProperty().getValue(); try { ed = this.getBeanEditor(); ThreadLocalEventLock.set(this); ed.setModifies(true); if (this.getProperty() instanceof PropertyCollection) { final PropertyCollection colProp = (PropertyCollection) this.getProperty(); if (this.listOut.getSelectedIndex() > -1) { for (Object o : this.listOut.getSelectedValues()) { try { colProp.addLink((RapidBean) o, true, false, true); } catch (ValidationException e) { this.indicateError(e); break; } } } } else if (this.getProperty() instanceof PropertyChoice) { final PropertyChoice chcProp = (PropertyChoice) this.getProperty(); if (this.listOut.getSelectedIndex() > -1) { try { final ArrayList<RapidEnum> newChoice = new ArrayList<RapidEnum>(); if (chcProp.getValue() != null) { for (Object o1 : chcProp.getValue()) { newChoice.add((RapidEnum) o1); } } for (final Object o : this.listOut.getSelectedValues()) { if (newChoice.contains(o)) { throw new RapidBeansRuntimeException("Assertion failed:" + " new choice unexpectedly already contains the enum to add."); } newChoice.add((RapidEnum) o); } chcProp.setValue(newChoice); } catch (ValidationException e) { this.indicateError(e); } } } else { throw new RapidBeansRuntimeException("Unexpected property class \"" + this.getProperty().getClass().getName() + "\"."); } try { ed.addBeanIfNew(true); } catch (ValidationException e) { assert (ed.getBean().getContainer() == null); addFailed = true; this.indicateError(e); if (this.getProperty() instanceof PropertyCollection) { final PropertyCollection colProp = (PropertyCollection) this.getProperty(); for (Object o : this.listOut.getSelectedValues()) { if (((Collection<?>) colProp.getValue()).contains((RapidBean) o)) { colProp.removeLink((RapidBean) o, false, false, true); } } switch (this.parentEditor.getNullBehaviour()) { case always_null: if (((Collection<?>) colProp.getValue()).size() == 0) { colProp.setValue(null); } break; default: break; } } else if (this.getProperty() instanceof PropertyChoice) { final PropertyChoice chcProp = (PropertyChoice) this.getProperty(); chcProp.setValue(oldValue); switch (this.parentEditor.getNullBehaviour()) { case always_null: if (chcProp.getValue().size() == 0) { chcProp.setValue(null); } break; default: break; } } } if (!addFailed) { final PropertyChangeEvent[] proparray = { new PropertyChangeEvent(this.getProperty(), oldValue, this .getProperty().getValue(), PropertyChangeEventType.set, null) }; this.parentEditor.beanChanged(new ChangedEvent(ed.getBean(), proparray)); if (!ed.isInNewMode()) { this.parentEditor.fireInputFieldChanged(); } } } finally { ed.removeBeanIfNew(false); ed.setModifies(false); ThreadLocalEventLock.release(); } } /** * remove selected beans. */ public void removeSelectedBeans() { EditorBean ed = null; final boolean docChangedBefore = this.parentEditor.getBeanEditor().getDocumentView().getDocument().getChanged(); try { ed = this.getBeanEditor(); ed.setModifies(true); ThreadLocalEventLock.set(this); ed.addBeanIfNew(true); final Collection<?> oldValue = (Collection<?>) this.getProperty().getValue(); if (this.getProperty() instanceof PropertyCollection) { if (this.listIn.getSelectedIndex() > -1) { for (Object o : this.listIn.getSelectedValues()) { try { ((PropertyCollection) this.getProperty()).removeLink((RapidBean) o); } catch (ValidationException e) { this.indicateError(e); break; } } } } else if (this.getProperty() instanceof PropertyChoice) { if (this.listIn.getSelectedIndex() > -1) { final ArrayList<RapidEnum> newValue = new ArrayList<RapidEnum>(); for (Object o : oldValue) { newValue.add((RapidEnum) o); } boolean changed = false; for (Object o : this.listIn.getSelectedValues()) { if (newValue.remove((RapidEnum) o)) { changed = true; } } if (changed) { this.getProperty().setValue(newValue); } } } else { throw new RapidBeansRuntimeException("Unexpected property class \"" + this.getProperty().getClass().getName() + "\"."); } final PropertyChangeEvent[] proparray = { new PropertyChangeEvent(this.getProperty(), oldValue, this .getProperty().getValue(), PropertyChangeEventType.set, null) }; this.parentEditor.beanChanged(new ChangedEvent(ed.getBean(), proparray)); if (!ed.isInNewMode()) { this.parentEditor.fireInputFieldChanged(); } } finally { ed.removeBeanIfNew(docChangedBefore); ThreadLocalEventLock.release(); ed.setModifies(false); } } /** * indicate an error during associating beans through this dialog. * * @param e * the validation exception to indicate */ private void indicateError(final ValidationException e) { final Application client = ApplicationManager.getApplication(); if (!client.getTestMode()) { client.playSoundError(); final RapidBeansLocale locale = this.getLocale(); final String locMessagePre = locale.getStringGui("messagedialog.input.field"); final String locPropname = this.getProperty().getNameGui(locale); final String locTitle = locale.getStringGui("messagedialog.title.input.wrong"); final String locMessage = e.getLocalizedMessage(locale); client.messageError(locMessagePre + " \"" + locPropname + "\":\n" + locMessage, locTitle); this.parentEditor.setFocus(); this.setFocus(); } } /** * updates the check box according to the boolean presented. */ public void updateUI() { if (this.listIn.getModel() instanceof ModelListCollection) { ((ModelListCollection) this.listIn.getModel()).fireColPropChanged((PropertyCollection) this.getProperty()); } else if (this.listIn.getModel() instanceof ModelListChoice) { ((ModelListChoice) this.listIn.getModel()).fireChoicePropChanged((PropertyChoice) this.getProperty()); } else { throw new RapidBeansRuntimeException("Unknown list model class \"" + this.listIn.getModel().getClass().getName() + "\""); } this.listIn.repaint(); if (this.listOut.getModel() instanceof ModelListCollectionAllWithout) { ((ModelListCollectionAllWithout) this.listOut.getModel()).fireColPropChanged((PropertyCollection) this .getProperty()); } else if (this.listOut.getModel() instanceof ModelListChoiceWithout) { ((ModelListChoiceWithout) this.listOut.getModel()).fireChoicePropChanged((PropertyChoice) this .getProperty()); } else { throw new RapidBeansRuntimeException("Unknown list model class \"" + this.listOut.getModel().getClass().getName() + "\""); } this.listOut.repaint(); } /** * Dummy implementation (not used). Data binding with collections should to * the job. * * @return the selected CheckBoxe's names */ public Object getInputFieldValue() { throw new RapidBeansRuntimeException("This method must not be called!"); } /** * Dummy implementation (not used) * * @return the input field value as string. */ public String getInputFieldValueString() { throw new RapidBeansRuntimeException("This method must not be called!"); } /** * handler for added bean. * * @param e * the added event */ public void beanAdded(final AddedEvent e) { if (this.getProperty().getValue() != null) { ((ModelListCollectionAllWithout) this.listOut.getModel()).fireBeanAdded(e.getBean()); } } /** * handler for removed bean. * * @param e * the removed event */ public void beanRemoved(final RemovedEvent e) { if (this.getProperty().getValue() != null) { ((ModelListCollection) this.listIn.getModel()).fireBeanRemoved(e.getBean()); ((ModelListCollectionAllWithout) this.listOut.getModel()).fireBeanRemoved(e.getBean()); // ((PropertyCollection) // this.getPropertyBak()).removeLink(e.getBean(), false, true, // false); } } /** * ovrerrides the EditorProperty method and adds a repaint of the lists. * * bean changed event. * * @param e * changed event */ public void beanChanged(final ChangedEvent e) { boolean interestedForEvent = false; for (PropertyChangeEvent propEv : e.getPropertyEvents()) { final Property prop = propEv.getProperty(); if (prop == this.getProperty()) { interestedForEvent = true; break; } } if (!interestedForEvent) { return; } this.updateUI(); } /** * release the model if the bean editor is closed. * * @param editor * the bean editor */ public void editorClosed(final EditorBean editor) { // final Object model = this.listLeft.getModel(); // if (model instanceof ModelListCollection) { // ((ModelListCollection) this.listLeft.getModel()).release(); // } } /** * action handler for the OK button. */ public void handleActionOk() { listWindow.dispose(); } /** * action handler for the window closed event. Equivalent to OK. */ public void handleActionWindowClosed() { try { closeInternal(); } catch (ValidationException e) { // intentionally do nothing } } /** * close the editor */ private void closeInternal() { try { ThreadLocalEventLock.set(this); // this.parentEditor.getBeanEditor().validateAndUpdateButtons(this.parentEditor); this.parentEditor.fireInputFieldChanged(); } finally { try { parentEditor.resetListEditor(); // parentEditor.updateUI(); } finally { ThreadLocalEventLock.release(); } } } }