/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.gnu.org/licenses/agpl.txt * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package se.streamsource.streamflow.client.ui.workspace.cases.general.forms; import static org.qi4j.api.util.Iterables.filter; import static org.qi4j.api.util.Iterables.first; import static se.streamsource.streamflow.infrastructure.event.domain.source.helper.Events.events; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Desktop; import java.awt.Font; import java.awt.Rectangle; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URISyntaxException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Observable; import java.util.Observer; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.UIManager; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import javax.swing.text.html.HTMLDocument; import org.jdesktop.swingx.JXPanel; import org.jdesktop.swingx.ScrollableSizeHint; import org.netbeans.spi.wizard.Wizard; import org.netbeans.spi.wizard.WizardPage; import org.netbeans.spi.wizard.WizardPanelNavResult; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.injection.scope.Uses; import org.qi4j.api.property.Property; import org.qi4j.api.structure.Module; import org.qi4j.api.util.DateFunctions; import org.qi4j.api.value.ValueBuilder; import org.restlet.resource.ResourceException; import se.streamsource.streamflow.api.administration.form.AttachmentFieldValue; import se.streamsource.streamflow.api.administration.form.CheckboxesFieldValue; import se.streamsource.streamflow.api.administration.form.ComboBoxFieldValue; import se.streamsource.streamflow.api.administration.form.CommentFieldValue; import se.streamsource.streamflow.api.administration.form.DateFieldValue; import se.streamsource.streamflow.api.administration.form.FieldGroupFieldValue; import se.streamsource.streamflow.api.administration.form.FieldValue; import se.streamsource.streamflow.api.administration.form.GeoLocationFieldValue; import se.streamsource.streamflow.api.administration.form.ListBoxFieldValue; import se.streamsource.streamflow.api.administration.form.NumberFieldValue; import se.streamsource.streamflow.api.administration.form.OpenSelectionFieldValue; import se.streamsource.streamflow.api.administration.form.OptionButtonsFieldValue; import se.streamsource.streamflow.api.administration.form.TextAreaFieldValue; import se.streamsource.streamflow.api.administration.form.TextFieldValue; import se.streamsource.streamflow.api.workspace.cases.form.AttachmentFieldDTO; import se.streamsource.streamflow.api.workspace.cases.form.AttachmentFieldSubmission; import se.streamsource.streamflow.api.workspace.cases.general.FieldSubmissionDTO; import se.streamsource.streamflow.api.workspace.cases.general.PageSubmissionDTO; import se.streamsource.streamflow.client.OperationException; import se.streamsource.streamflow.client.ui.workspace.WorkspaceResources; import se.streamsource.streamflow.client.ui.workspace.cases.CaseResources; import se.streamsource.streamflow.client.ui.workspace.cases.general.forms.geo.GeoLocationFieldPanel; import se.streamsource.streamflow.client.util.BindingFormBuilder; import se.streamsource.streamflow.client.util.CommandTask; import se.streamsource.streamflow.client.util.StateBinder; import se.streamsource.streamflow.client.util.i18n; import se.streamsource.streamflow.infrastructure.event.domain.DomainEvent; import se.streamsource.streamflow.infrastructure.event.domain.TransactionDomainEvents; import se.streamsource.streamflow.infrastructure.event.domain.source.TransactionListener; import se.streamsource.streamflow.infrastructure.event.domain.source.helper.EventParameters; import se.streamsource.streamflow.infrastructure.event.domain.source.helper.Events; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import com.jgoodies.validation.ValidationResult; import com.jgoodies.validation.ValidationResultModel; import com.jgoodies.validation.util.DefaultValidationResultModel; import com.jgoodies.validation.util.ValidationUtils; import com.jgoodies.validation.view.ValidationResultViewFactory; /** * JAVADOC */ public class FormSubmissionWizardPageView extends WizardPage implements Observer, TransactionListener { private java.util.Map<String, AbstractFieldPanel> componentFieldMap; private java.util.Map<StateBinder, EntityReference> fieldBinders; private ValidationResultModel validationResultModel; private FormSubmissionWizardPageModel model; private static final Map<Class<? extends FieldValue>, Class<? extends AbstractFieldPanel>> fields = new HashMap<Class<? extends FieldValue>, Class<? extends AbstractFieldPanel>>(); @Structure Module module; static { // Remember to add editors here when creating new types fields.put( CheckboxesFieldValue.class, CheckboxesPanel.class ); fields.put( ComboBoxFieldValue.class, ComboBoxPanel.class ); fields.put( DateFieldValue.class, DatePanel.class ); fields.put( ListBoxFieldValue.class, ListBoxPanel.class ); fields.put( NumberFieldValue.class, NumberPanel.class ); fields.put( OptionButtonsFieldValue.class, OptionButtonsPanel.class ); fields.put( OpenSelectionFieldValue.class, OpenSelectionPanel.class ); fields.put( TextAreaFieldValue.class, TextAreaFieldPanel.class ); fields.put( TextFieldValue.class, TextFieldPanel.class ); fields.put( AttachmentFieldValue.class, AttachmentFieldPanel.class ); fields.put( GeoLocationFieldValue.class, GeoLocationFieldPanel.class ); } public FormSubmissionWizardPageView( @Structure Module module, @Uses PageSubmissionDTO page, @Uses FormDraftModel model) { super( page.title().get() ); this.module = module; this.model = module.objectBuilderFactory().newObjectBuilder(FormSubmissionWizardPageModel.class).use( model ).newInstance(); componentFieldMap = new HashMap<String, AbstractFieldPanel>(); validationResultModel = new DefaultValidationResultModel(); setLayout( new BorderLayout() ); final JXPanel panel = new JXPanel( new FormLayout() ); panel.setScrollableHeightHint( ScrollableSizeHint.VERTICAL_STRETCH ); fieldBinders = new HashMap<StateBinder, EntityReference>( page.fields().get().size() ); FormLayout formLayout = new FormLayout( "250dlu, 70dlu:grow", "" ); DefaultFormBuilder formBuilder = new DefaultFormBuilder( formLayout, panel ); BindingFormBuilder bb = new BindingFormBuilder( formBuilder, null ); for (FieldSubmissionDTO DTO : page.fields().get()) { AbstractFieldPanel component; FieldValue fieldValue = DTO.field().get().fieldValue().get(); if ( fieldValue instanceof FieldGroupFieldValue ) { bb.append( new JLabel( "<html><b>"+DTO.field().get().description().get()+"</b>:</html>" ) ); } else if (!(fieldValue instanceof CommentFieldValue)) { component = getComponent(DTO); componentFieldMap.put( DTO.field().get().field().get().identity(), component ); StateBinder stateBinder = component.bindComponent( bb, DTO); stateBinder.addObserver( this ); fieldBinders.put( stateBinder, DTO.field().get().field().get() ); } else { // comment field does not have any input component String comment = DTO.field().get().note().get(); comment = comment.replaceAll( "\n", "<br/>" ); JEditorPane commentPane = new JEditorPane( "text/html", "<html>" + comment + "</html>" ); Font font = UIManager.getFont( "Label.font" ); String bodyRule = "body { font-family: " + font.getFamily() + "; " + "font-size: " + font.getSize() + "pt; }"; ((HTMLDocument) commentPane.getDocument()).getStyleSheet().addRule( bodyRule ); commentPane.setOpaque( false ); commentPane.setBorder( null ); commentPane.setEditable( false ); commentPane.setFocusable( true ); commentPane.addHyperlinkListener( new HyperlinkListener() { public void hyperlinkUpdate( HyperlinkEvent e ) { if (e.getEventType().equals( HyperlinkEvent.EventType.ACTIVATED )) { // Open in browser try { Desktop.getDesktop().browse( e.getURL().toURI() ); } catch (IOException e1) { e1.printStackTrace(); } catch (URISyntaxException e1) { e1.printStackTrace(); } } } } ); bb.append( commentPane ); } } JComponent validationResultsComponent = ValidationResultViewFactory.createReportList( validationResultModel ); formBuilder.appendRow( "top:30dlu:g" ); CellConstraints cc = new CellConstraints(); formBuilder.add( validationResultsComponent, cc.xywh( 1, formBuilder.getRow() + 1, 1, 1, "fill, bottom" ) ); final JScrollPane scroll = new JScrollPane( panel ); add( scroll, BorderLayout.CENTER ); createFocusListenerForComponents( panel, panel.getComponents() ); } private void createFocusListenerForComponents( final JXPanel main, Component[] components ) { for (final Component component : components) { if (component instanceof AbstractFieldPanel) { Component firstFocusable = ((AbstractFieldPanel) component).firstFocusableComponent( (AbstractFieldPanel) component ); if (firstFocusable != null) { firstFocusable.addFocusListener( new FocusAdapter() { @Override public void focusGained( FocusEvent e ) { // increase visible rectangle to cover title label too Rectangle rectangle = component.getBounds(); rectangle.add( rectangle.getX(), rectangle.getY()-21 ); main.scrollRectToVisible( rectangle ); } } ); } } else { if (component.isFocusable()) { component.addFocusListener( new FocusAdapter() { @Override public void focusGained( FocusEvent e ) { main.scrollRectToVisible( component.getBounds() ); } } ); } } } } @Override public WizardPanelNavResult allowNext( String s, Map map, Wizard wizard ) { ValidationResult validation = validatePage(); validationResultModel.setResult( validation ); if (!validation.hasErrors()) { // last page check needed ??? return WizardPanelNavResult.PROCEED; } return WizardPanelNavResult.REMAIN_ON_PAGE; } @Override public WizardPanelNavResult allowBack( String stepName, Map settings, Wizard wizard ) { // first page check needed ??? return super.allowBack( stepName, settings, wizard ); } @Override public WizardPanelNavResult allowFinish( String s, Map map, Wizard wizard ) { ValidationResult validationResult = validatePage(); validationResultModel.setResult( validationResult ); if (validationResult.hasErrors()) { return WizardPanelNavResult.REMAIN_ON_PAGE; } return WizardPanelNavResult.PROCEED; } private ValidationResult validatePage() { ValidationResult validationResult = new ValidationResult(); for (AbstractFieldPanel component : componentFieldMap.values()) { String value = component.getValue(); if (component.mandatory()) { if (ValidationUtils.isEmpty( value )) { validationResult.addError( i18n.text( WorkspaceResources.mandatory_field_missing ) + ": " + component.title() ); } } } return validationResult; } public void update( final Observable observable, Object arg ) { final Property property = (Property) arg; if (property.qualifiedName().name().equals( "value" )) { try { if (property.get() instanceof Date) { new CommandTask() { @Override public void command() throws Exception { model.updateField( fieldBinders.get( observable ), DateFunctions.toUtcString( (Date) property.get() ) ); } }.execute(); } else if (property.get() instanceof File) { try { final FileInputStream fin = new FileInputStream( (File) property.get() ); new CommandTask() { @Override protected void command() throws Exception { model.createAttachment( fieldBinders.get( observable ), (File) property.get(), fin ); } }.execute(); } catch (Exception e) { throw new OperationException( CaseResources.could_not_upload_file, e ); } } else { new CommandTask() { @Override protected void command() throws Exception { model.updateField( fieldBinders.get( observable ), property.get().toString() ); } }.execute(); } } catch (ResourceException e) { throw new OperationException( CaseResources.could_not_update_field, e ); } } } public void updateFieldPanel( String fieldId, String fieldValue ) { AbstractFieldPanel panel = componentFieldMap.get( fieldId ); if (panel != null && fieldValue != null && !fieldValue.equals( panel.getValue() )) { panel.setValue( fieldValue ); } } private AbstractFieldPanel getComponent( FieldSubmissionDTO field ) { FieldValue fieldValue = field.field().get().fieldValue().get(); Class<? extends FieldValue> fieldValueType = (Class<FieldValue>) fieldValue.getClass().getInterfaces()[0]; return module.objectBuilderFactory().newObjectBuilder(fields.get(fieldValueType)).use( field, fieldValue, model ).newInstance(); } public void notifyTransactions( Iterable<TransactionDomainEvents> transactions ) { if (Events.matches( Events.withNames( "changedFieldAttachmentValue" ), transactions )) { String value = EventParameters.getParameter( first( filter( Events.withNames( "changedFieldAttachmentValue" ), events( transactions ) ) ), "param1" ); AttachmentFieldDTO dto = module.valueBuilderFactory().newValueFromJSON(AttachmentFieldDTO.class, value); ValueBuilder<AttachmentFieldSubmission> builder = module.valueBuilderFactory().newValueBuilder(AttachmentFieldSubmission.class); builder.prototype().attachment().set( dto.attachment().get() ); builder.prototype().name().set( dto.name().get() ); updateFieldPanel( dto.field().get().identity(), builder.newInstance().toJSON() ); } else if (Events.matches( Events.withNames( "changedFieldValue" ), transactions )) { DomainEvent event = first( filter( Events.withNames( "changedFieldValue" ), events( transactions ) ) ); String fieldId = EventParameters.getParameter( event, "param1" ); String value = EventParameters.getParameter( event, "param2" ); updateFieldPanel( fieldId, value ); } } }