/* * Copyright (c) 2011, Michael Grossmann, Nikolaus Moll * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the jo-widgets.org nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.jowidgets.impl.widgets.composed; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import org.jowidgets.api.convert.IConverter; import org.jowidgets.api.convert.IObjectStringConverter; import org.jowidgets.api.layout.ILayoutFactory; import org.jowidgets.api.toolkit.Toolkit; import org.jowidgets.api.widgets.IButton; import org.jowidgets.api.widgets.IComposite; import org.jowidgets.api.widgets.IContainer; import org.jowidgets.api.widgets.IInputControl; import org.jowidgets.api.widgets.IInputDialog; import org.jowidgets.api.widgets.IInputField; import org.jowidgets.api.widgets.ITextControl; import org.jowidgets.api.widgets.blueprint.IButtonBluePrint; import org.jowidgets.api.widgets.blueprint.ICollectionInputDialogBluePrint; import org.jowidgets.api.widgets.descriptor.ICollectionInputFieldDescriptor; import org.jowidgets.api.widgets.descriptor.setup.ICollectionInputDialogSetup; import org.jowidgets.common.color.IColorConstant; import org.jowidgets.common.types.Dimension; import org.jowidgets.common.types.Markup; import org.jowidgets.common.types.Modifier; import org.jowidgets.common.types.Position; import org.jowidgets.common.types.Rectangle; import org.jowidgets.common.types.VirtualKey; import org.jowidgets.common.widgets.controller.IActionListener; import org.jowidgets.common.widgets.controller.IInputListener; import org.jowidgets.common.widgets.controller.IKeyEvent; import org.jowidgets.common.widgets.controller.IKeyListener; import org.jowidgets.common.widgets.controller.IMouseListener; import org.jowidgets.common.widgets.layout.ILayouter; import org.jowidgets.common.widgets.layout.MigLayoutDescriptor; import org.jowidgets.i18n.api.IMessage; import org.jowidgets.i18n.api.MessageReplacer; import org.jowidgets.tools.controller.InputObservable; import org.jowidgets.tools.controller.KeyAdapter; import org.jowidgets.tools.validation.ValidationCache; import org.jowidgets.tools.validation.ValidationCache.IValidationResultCreator; import org.jowidgets.tools.widgets.blueprint.BPF; import org.jowidgets.tools.widgets.wrapper.ControlWrapper; import org.jowidgets.util.Assert; import org.jowidgets.util.EmptyCheck; import org.jowidgets.validation.IValidationConditionListener; import org.jowidgets.validation.IValidationResult; import org.jowidgets.validation.IValidationResultBuilder; import org.jowidgets.validation.IValidator; import org.jowidgets.validation.ValidationResult; import org.jowidgets.validation.tools.CompoundValidator; public class CollectionInputFieldImpl<ELEMENT_TYPE> extends ControlWrapper implements IInputControl<Collection<ELEMENT_TYPE>>, ITextControl { private static final IMessage ELEMENT = Messages.getMessage("CollectionInputFieldImpl.element"); //$NON-NLS-1$ private static final IMessage EDIT = Messages.getMessage("CollectionInputFieldImpl.edit"); //$NON-NLS-1$ private static final Character DEFAULT_SEPARATOR = Character.valueOf(','); private final boolean filterEmptyValues; private final boolean dublicatesAllowed; private final IInputField<String> textField; private final IButton editButton; private final IComposite editButtonContainer; private final IConverter<ELEMENT_TYPE> converter; private final IObjectStringConverter<ELEMENT_TYPE> objectStringConverter; private final Character separator; private final Character maskingCharacter; private final ValidationCache validationCache; private final CompoundValidator<Collection<ELEMENT_TYPE>> compoundValidator; private final InputObservable inputObservable; private final ICollectionInputDialogBluePrint<ELEMENT_TYPE> inputDialogBp; private Dimension lastDialogSize; private final Collection<String> value; private final Collection<ELEMENT_TYPE> elementTypeValue; private boolean editable; // TODO MG replace by getter when implemented @SuppressWarnings("unchecked") public CollectionInputFieldImpl(final IComposite composite, final ICollectionInputFieldDescriptor<ELEMENT_TYPE> setup) { super(composite); this.filterEmptyValues = setup.isFilterEmptyValues(); this.dublicatesAllowed = setup.getDublicatesAllowed(); Assert.paramNotNull(setup.getConverter(), "setup.getConverter()"); if (setup.getConverter() instanceof IConverter<?>) { this.converter = (IConverter<ELEMENT_TYPE>) setup.getConverter(); this.objectStringConverter = converter; } else if (setup.getConverter() instanceof IObjectStringConverter<?>) { this.converter = null; this.objectStringConverter = (IObjectStringConverter<ELEMENT_TYPE>) setup.getConverter(); } else { throw new IllegalArgumentException("Converter type'" + setup.getConverter().getClass() + "' is not supported."); } if (setup.getSeparator() != null) { this.separator = setup.getSeparator(); } else { this.separator = DEFAULT_SEPARATOR; } this.maskingCharacter = setup.getMaskingCharacter(); final ICollectionInputDialogSetup<ELEMENT_TYPE> inputDialogSetup = setup.getCollectionInputDialogSetup(); this.value = new LinkedList<String>(); this.elementTypeValue = new LinkedList<ELEMENT_TYPE>(); this.inputObservable = new InputObservable(); this.compoundValidator = new CompoundValidator<Collection<ELEMENT_TYPE>>(); this.textField = composite.add(BPF.inputFieldString(), "grow, w 0::, sgy hg"); textField.addInputListener(new IInputListener() { @Override public void inputChanged() { inputChangedListener(); } }); final IValidator<ELEMENT_TYPE> elementValidator = setup.getElementValidator(); this.validationCache = new ValidationCache(new IValidationResultCreator() { @Override public IValidationResult createValidationResult() { final IValidationResultBuilder builder = ValidationResult.builder(); int index = 1; for (final String element : value) { boolean invalid = false; final String context = MessageReplacer.replace(ELEMENT.get(), String.valueOf(index)); if (converter != null && converter.getStringValidator() != null) { final IValidator<String> stringValidator = converter.getStringValidator(); final IValidationResult stringResult = stringValidator.validate(element).withContext(context); if (!stringResult.isValid()) { invalid = true; } builder.addResult(stringResult); } if (converter != null && !invalid) { final ELEMENT_TYPE convertedElement = converter.convertToObject(element); final IValidationResult elementResult = elementValidator.validate(convertedElement).withContext(context); builder.addResult(elementResult); } index++; } final Collection<ELEMENT_TYPE> currentValue = getValue(); builder.addResult(compoundValidator.validate(currentValue)); if (!dublicatesAllowed && currentValue != null && currentValue.size() != new HashSet<ELEMENT_TYPE>(currentValue).size()) { builder.addError(Messages.getString("CollectionInputFieldImpl.contains_dublicates")); } return builder.build(); } }); if (inputDialogSetup != null) { inputDialogBp = BPF.collectionInputDialog(inputDialogSetup.getCollectionInputControlSetup()); inputDialogBp.setSetup(inputDialogSetup); inputDialogBp.setValidator(setup.getValidator()); final IButtonBluePrint buttonBp = BPF.button(); if (setup.getEditButtonIcon() != null) { buttonBp.setIcon(setup.getEditButtonIcon()); } else { buttonBp.setText(EDIT.get()); } editButtonContainer = composite.add(BPF.composite()); this.editButton = editButtonContainer.add(buttonBp); editButtonContainer.setLayout(new EditButtonConatinerLayouterFactory()); this.editButton.addActionListener(new IActionListener() { @Override public void actionPerformed() { openDialog(); } }); textField.addKeyListener(new KeyAdapter() { @Override public void keyPressed(final IKeyEvent event) { if (event.getModifier().contains(Modifier.ALT) && VirtualKey.ENTER.equals(event.getVirtualKey())) { openDialog(); } } }); composite.setLayout(new TextFieldWithButtonLayouterFactory()); } else { composite.setLayout(new MigLayoutDescriptor("0[grow, 0::]0", "0[grow]0")); this.inputDialogBp = null; this.editButtonContainer = null; this.editButton = null; } if (setup.getValidator() != null) { compoundValidator.addValidator(setup.getValidator()); } if (converter == null) { textField.setEditable(false); } this.editable = setup.isEditable(); if (!setup.isEditable()) { setEditable(false); } } @SuppressWarnings("unchecked") private void openDialog() { final Position buttonPos = Toolkit.toScreen(editButton.getPosition(), getWidget()); inputDialogBp.setPosition(buttonPos); if (lastDialogSize != null) { inputDialogBp.setSize(lastDialogSize); } else { inputDialogBp.setSize(new Dimension(300, 270)); } final IInputDialog<Collection<ELEMENT_TYPE>> dialog = Toolkit.getActiveWindow().createChildWindow(inputDialogBp); if (EmptyCheck.isEmpty(value)) { dialog.setValue((Collection<ELEMENT_TYPE>) Collections.singleton(null)); } else { dialog.setValue(getValue()); } dialog.setVisible(true); if (dialog.isOkPressed()) { lastDialogSize = dialog.getSize(); setValue(dialog.getValue()); } dialog.dispose(); } @Override protected IComposite getWidget() { return (IComposite) super.getWidget(); } @Override public void addValidator(final IValidator<Collection<ELEMENT_TYPE>> validator) { compoundValidator.addValidator(validator); } @Override public boolean hasModifications() { return textField.hasModifications(); } @Override public void resetModificationState() { textField.resetModificationState(); } @Override public void setValue(final Collection<ELEMENT_TYPE> value) { this.value.clear(); this.elementTypeValue.clear(); if (EmptyCheck.isEmpty(value)) { textField.setValue(null); } else { this.elementTypeValue.addAll(value); final String maskingString = String.valueOf(maskingCharacter.charValue()); final String separatorString = String.valueOf(separator.charValue()); final StringBuilder valueString = new StringBuilder(); for (final ELEMENT_TYPE element : value) { final String converted = objectStringConverter.convertToString(element); this.value.add(converted); if (converted != null) { final String masked = converted.replace(maskingString, maskingString + maskingString); if (converted.contains(separatorString) || converted.startsWith(" ")) { valueString.append(maskingString + masked + maskingString); } else { valueString.append(masked); } } valueString.append(separator + " "); } valueString.replace(valueString.length() - 2, valueString.length(), ""); textField.setValue(valueString.toString()); } } @Override public Collection<ELEMENT_TYPE> getValue() { final List<ELEMENT_TYPE> result = new LinkedList<ELEMENT_TYPE>(); if (converter != null) { for (final String element : value) { final ELEMENT_TYPE converted = converter.convertToObject(element); if (!filterEmptyValues || (!EmptyCheck.isEmpty(converted) && !EmptyCheck.isEmpty(element))) { result.add(converted); } } } else { result.addAll(elementTypeValue); } return result; } @Override public IValidationResult validate() { return validationCache.validate(); } @Override public void addValidationConditionListener(final IValidationConditionListener listener) { validationCache.addValidationConditionListener(listener); } @Override public void removeValidationConditionListener(final IValidationConditionListener listener) { validationCache.removeValidationConditionListener(listener); } @Override public void setEditable(final boolean editable) { this.editable = editable; if (converter != null) { textField.setEditable(editable); } if (editButton != null) { editButton.setEnabled(editable && textField.isEnabled()); } } @Override public void setEnabled(final boolean enabled) { textField.setEnabled(enabled); if (editButton != null) { editButton.setEnabled(enabled && editable); } } @Override public boolean isEditable() { return editable; } @Override public boolean requestFocus() { return textField.requestFocus(); } @Override public void addKeyListener(final IKeyListener listener) { textField.addKeyListener(listener); } @Override public void removeKeyListener(final IKeyListener listener) { textField.removeKeyListener(listener); } @Override public void addMouseListener(final IMouseListener listener) { textField.addMouseListener(listener); } @Override public void removeMouseListener(final IMouseListener listener) { textField.removeMouseListener(listener); } @Override public void addInputListener(final IInputListener listener) { inputObservable.addInputListener(listener); } @Override public void removeInputListener(final IInputListener listener) { inputObservable.removeInputListener(listener); } @Override public void setForegroundColor(final IColorConstant colorValue) { textField.setForegroundColor(colorValue); } @Override public void setBackgroundColor(final IColorConstant colorValue) { textField.setBackgroundColor(colorValue); } @Override public IColorConstant getForegroundColor() { return textField.getForegroundColor(); } @Override public IColorConstant getBackgroundColor() { return textField.getBackgroundColor(); } @Override public String getText() { return textField.getText(); } @Override public void setText(final String text) { textField.setValue(text); } @Override public void setFontSize(final int size) {} @Override public void setFontName(final String fontName) {} @Override public void setMarkup(final Markup markup) {} @Override public void setSelection(final int start, final int end) { textField.setSelection(start, end); } @Override public void setCaretPosition(final int pos) { textField.setCaretPosition(pos); } @Override public int getCaretPosition() { return textField.getCaretPosition(); } @Override public void selectAll() { textField.selectAll(); } @Override public void select() { selectAll(); } private void inputChangedListener() { final String maskingString = String.valueOf(maskingCharacter.charValue()); final String separatorString = String.valueOf(separator.charValue()); value.clear(); final String input = textField.getValue(); boolean isQuoted = false; int pos = 0; final StringBuilder currentElement = new StringBuilder(); while (pos < input.length()) { if (equalsStringPart(input, maskingString, pos)) { pos = pos + maskingString.length(); if (equalsStringPart(input, maskingString, pos)) { currentElement.append(maskingString); pos = pos + maskingString.length(); } else { isQuoted = !isQuoted; } } else if (equalsStringPart(input, separatorString, pos)) { pos = pos + separatorString.length(); if (isQuoted) { currentElement.append(separatorString); } else { value.add(currentElement.toString()); currentElement.setLength(0); pos = skipSpaces(input, pos); } } else { currentElement.append(input.charAt(pos)); pos++; } } if (input.length() > 0) { value.add(currentElement.toString()); } inputObservable.fireInputChanged(); validationCache.setDirty(); } private static boolean equalsStringPart(final String text, final String search, final int pos) { if (pos + search.length() > text.length()) { return false; } int index = 0; while (pos + index < text.length() && index < search.length()) { if (text.charAt(pos + index) != search.charAt(index)) { return false; } index++; } return true; } private static int skipSpaces(final String text, final int pos) { int result = pos; while (result < text.length() && text.charAt(result) == ' ') { result++; } return result; } private final class TextFieldWithButtonLayouterFactory implements ILayoutFactory<ILayouter> { @Override public ILayouter create(final IContainer container) { return new TextFieldWithButtonLayouter(container); } } private final class TextFieldWithButtonLayouter implements ILayouter { private final IContainer container; private final Dimension preferredSize; private final Dimension minSize; private final Dimension maxSize; private final int buttonWidth; private TextFieldWithButtonLayouter(final IContainer container) { this.container = container; final Dimension fieldSize = textField.getPreferredSize(); if (editButton.getIcon() != null) { this.buttonWidth = fieldSize.getHeight(); } else { this.buttonWidth = editButtonContainer.getPreferredSize().getWidth(); } this.preferredSize = new Dimension(fieldSize.getWidth() + buttonWidth, fieldSize.getHeight()); this.minSize = new Dimension(buttonWidth + 4, fieldSize.getHeight()); this.maxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } @Override public void layout() { final Rectangle clientArea = container.getClientArea(); final Dimension clientSize = clientArea.getSize(); final int clientWidht = clientSize.getWidth(); final int clientHeight = clientSize.getHeight(); final int newButtonWidth; if (editButton.getIcon() != null) { newButtonWidth = clientHeight; } else { newButtonWidth = buttonWidth; } final int textFieldWidth = clientWidht - newButtonWidth; editButtonContainer.setPosition(clientArea.getX() + textFieldWidth, clientArea.getY()); editButtonContainer.setSize(new Dimension(newButtonWidth, clientHeight)); textField.setPosition(clientArea.getX(), clientArea.getY()); textField.setSize(new Dimension(textFieldWidth, clientHeight)); } @Override public void invalidate() {} @Override public Dimension getPreferredSize() { return preferredSize; } @Override public Dimension getMinSize() { return minSize; } @Override public Dimension getMaxSize() { return maxSize; } } private final class EditButtonConatinerLayouterFactory implements ILayoutFactory<ILayouter> { @Override public ILayouter create(final IContainer container) { return new EditButtonConatinerLayouter(container); } } private final class EditButtonConatinerLayouter implements ILayouter { private final IContainer container; private final Dimension preferredSize; private EditButtonConatinerLayouter(final IContainer container) { this.container = container; this.preferredSize = editButton.getPreferredSize(); } @Override public void layout() { final Rectangle clientArea = container.getClientArea(); final Dimension clientSize = clientArea.getSize(); final int clientWidht = clientSize.getWidth(); final int clientHeight = clientSize.getHeight(); editButton.setPosition(clientArea.getX() - 1, clientArea.getY() - 1); editButton.setSize(new Dimension(clientWidht + 2, clientHeight + 2)); } @Override public void invalidate() {} @Override public Dimension getPreferredSize() { return preferredSize; } @Override public Dimension getMinSize() { return preferredSize; } @Override public Dimension getMaxSize() { return preferredSize; } } }