Revision: 1232
Author:   peterdb
Date:     2006-07-25 04:51:57 -0700 (Tue, 25 Jul 2006)
ViewCVS:  http://svn.sourceforge.net/spring-rich-c/?rev=1232&view=rev

Log Message:
-----------
some refactoring to make the interceptor testable + added tests

Modified Paths:
--------------
    
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactory.java

Added Paths:
-----------
    
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptor.java
    
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/
    
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/
    
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactoryTests.java
    
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorTests.java
    
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/TestBean.java
Added: 
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptor.java
===================================================================
--- 
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptor.java
                            (rev 0)
+++ 
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptor.java
    2006-07-25 11:51:57 UTC (rev 1232)
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 org.springframework.richclient.form.builder.support;
+
+import java.awt.BorderLayout;
+
+import java.awt.Insets;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import java.util.Locale;
+
+import javax.swing.Icon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import javax.swing.JPanel;
+
+import org.springframework.binding.form.FormModel;
+import org.springframework.binding.value.ValueChangeDetector;
+import org.springframework.binding.value.support.ValueHolder;
+import org.springframework.context.MessageSource;
+import org.springframework.richclient.application.ApplicationServicesLocator;
+import org.springframework.richclient.factory.AbstractControlFactory;
+import org.springframework.richclient.image.IconSource;
+import org.springframework.richclient.util.OverlayHelper;
+
+/**
+ * Adds a "dirty overlay" to a component that is triggered by user editing. 
The overlaid
+ * image is retrieved by the image key "dirty.overlay". The image is placed at 
the
+ * top-left corner of the component, and the image's tooltip is set to a 
message
+ * (retrieved with key "dirty.message") such as "{field} has changed, original 
value was
+ * {value}.". It also adds a small revert button that resets the value of the 
field.
+ *
+ * @author Peter De Bruycker
+ */
+public class DirtyIndicatorInterceptor extends 
AbstractFormComponentInterceptor {
+    private static final String DIRTY_ICON_KEY = "dirty.overlay";
+    private static final String DIRTY_MESSAGE_KEY = "dirty.message";
+    private static final String REVERT_ICON_KEY = "revert.overlay";
+    private static final String REVERT_MESSAGE_KEY = "revert.message";
+
+    private ValueChangeDetector valueChangeDetector;
+
+    public DirtyIndicatorInterceptor(FormModel formModel) {
+        super(formModel);
+    }
+
+    public void processComponent(final String propertyName, final JComponent 
component) {
+        final OriginalValueHolder originalValueHolder = new 
OriginalValueHolder();
+        final DirtyOverlay overlay = new DirtyOverlay(getFormModel(), 
propertyName, originalValueHolder);
+
+        final ValueHolder reset = new ValueHolder(Boolean.FALSE);
+        getFormModel().getValueModel(propertyName).addValueChangeListener(new 
PropertyChangeListener() {
+                    public void propertyChange(PropertyChangeEvent evt) {
+                        if (reset.getValue() == Boolean.TRUE) {
+                            originalValueHolder.reset();
+                            reset.setValue(Boolean.FALSE);
+
+                            overlay.setVisible(false);
+
+                            return;
+                        }
+
+                        if (!originalValueHolder.isInitialized()) {
+                            
originalValueHolder.setOriginalValue(evt.getOldValue());
+                        }
+
+                        Object oldValue = originalValueHolder.getValue();
+                        Object newValue = evt.getNewValue();
+                        
overlay.setVisible(getValueChangeDetector().hasValueChanged(oldValue, 
newValue));
+                    }
+                });
+        getFormModel().getFormObjectHolder().addValueChangeListener(new 
PropertyChangeListener() {
+                    public void propertyChange(PropertyChangeEvent evt) {
+                        // reset original value, new "original" value is in 
the form model as
+                        // the form object has changed
+                        reset.setValue(Boolean.TRUE);
+                    }
+                });
+
+        InterceptorOverlayHelper.attachOverlay(overlay.getControl(), 
component, OverlayHelper.NORTH_WEST, 3, 0);
+        overlay.setVisible(false);
+    }
+
+    private ValueChangeDetector getValueChangeDetector() {
+        if (valueChangeDetector == null) {
+            valueChangeDetector =
+                    
(ValueChangeDetector)ApplicationServicesLocator.services().getService(ValueChangeDetector.class);
+        }
+
+        return valueChangeDetector;
+    }
+
+    private static class DirtyOverlay extends AbstractControlFactory {
+        private JButton revertButton;
+        private JLabel dirtyLabel;
+        private FormModel formModel;
+        private String propertyName;
+        private OriginalValueHolder originalValueHolder;
+
+        public DirtyOverlay(FormModel formModel, String propertyName, 
OriginalValueHolder originalValueHolder) {
+            this.formModel = formModel;
+            this.propertyName = propertyName;
+            this.originalValueHolder = originalValueHolder;
+        }
+
+        protected JComponent createControl() {
+            JPanel control = new JPanel(new BorderLayout());
+            control.setName("dirtyOverlay");
+
+            control.setOpaque(false);
+
+            IconSource iconSource = 
(IconSource)ApplicationServicesLocator.services().getService(IconSource.class);
+            Icon icon = iconSource.getIcon(DIRTY_ICON_KEY);
+            dirtyLabel = new JLabel(icon);
+            control.add(dirtyLabel, BorderLayout.CENTER);
+
+            createRevertButton();
+            control.add(revertButton, BorderLayout.LINE_END);
+
+            return control;
+        }
+
+        private void createRevertButton() {
+            IconSource iconSource = 
(IconSource)ApplicationServicesLocator.services().getService(IconSource.class);
+            Icon icon = iconSource.getIcon(REVERT_ICON_KEY);
+
+            revertButton = new JButton(icon);
+            revertButton.setBorderPainted(false);
+            revertButton.setContentAreaFilled(false);
+            revertButton.setFocusable(false);
+            revertButton.setMargin(new Insets(-3, -3, -3, -3));
+            revertButton.addActionListener(new ActionListener() {
+                        public void actionPerformed(ActionEvent e) {
+                            // reset
+                            
formModel.getValueModel(propertyName).setValue(originalValueHolder.getValue());
+                        }
+                    });
+        }
+
+        public void setVisible(boolean visible) {
+            getControl().setVisible(visible);
+            // manually set the size, otherwise sometimes the overlay is not 
shown (it has size 0,0)
+            getControl().setSize(getControl().getPreferredSize());
+
+            if (visible) {
+                MessageSource messageSource =
+                    
(MessageSource)ApplicationServicesLocator.services().getService(MessageSource.class);
+                String dirtyTooltip =
+                    messageSource.getMessage(DIRTY_MESSAGE_KEY, new Object[] { 
formModel.getFieldFace(propertyName).getDisplayName(),
+                                                                               
originalValueHolder.getValue() },
+                                             Locale.getDefault());
+                dirtyLabel.setToolTipText(dirtyTooltip);
+
+                String revertTooltip =
+                    messageSource.getMessage(REVERT_MESSAGE_KEY, new Object[] 
{ formModel.getFieldFace(propertyName).getDisplayName() },
+                                             Locale.getDefault());
+                revertButton.setToolTipText(revertTooltip);
+            }
+        }
+    }
+
+    private static class OriginalValueHolder {
+        private boolean initialized;
+        private Object originalValue;
+
+        public void setOriginalValue(Object value) {
+            initialized = true;
+            originalValue = value;
+        }
+
+        public void reset() {
+            initialized = false;
+            originalValue = null;
+        }
+
+        public Object getValue() {
+            return originalValue;
+        }
+
+        public boolean isInitialized() {
+            return initialized;
+        }
+    }
+}

Modified: 
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactory.java
===================================================================
--- 
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactory.java
     2006-07-25 11:31:31 UTC (rev 1231)
+++ 
trunk/spring-richclient/support/src/main/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactory.java
     2006-07-25 11:51:57 UTC (rev 1232)
@@ -15,137 +15,17 @@
  */
 package org.springframework.richclient.form.builder.support;
 
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-import java.util.Locale;
-
-import javax.swing.Icon;
-import javax.swing.JComponent;
-import javax.swing.JLabel;
-import javax.swing.SwingConstants;
-
 import org.springframework.binding.form.FormModel;
-import org.springframework.binding.value.support.ValueHolder;
-import org.springframework.context.MessageSource;
-import org.springframework.richclient.application.ApplicationServicesLocator;
 import org.springframework.richclient.form.builder.FormComponentInterceptor;
 import 
org.springframework.richclient.form.builder.FormComponentInterceptorFactory;
-import org.springframework.richclient.image.IconSource;
-import org.springframework.util.ObjectUtils;
 
 /**
- * Adds a "dirty overlay" to a component that is triggered by user editing. 
The overlaid
- * image is retrieved by the image key "dirty.overlay". The image is placed at 
the
- * top-left corner of the component, and the image's tooltip is set to a 
message
- * (retrieved with key "dirty.message") such as "{field} has changed, original 
value was
- * {value}.".
+ * Factory for <code>DirtyIndicatorInterceptor</code> instances.
  *
- *
  * @author Peter De Bruycker
  */
 public class DirtyIndicatorInterceptorFactory implements 
FormComponentInterceptorFactory {
-    private static final String DEFAULT_ICON_KEY = "dirty.overlay";
-    private static final String DEFAULT_MESSAGE_KEY = "dirty.message";
-
-    private String iconKey = DEFAULT_ICON_KEY;
-
-    public FormComponentInterceptor getInterceptor( FormModel formModel ) {
-        IconSource iconSource = (IconSource) 
ApplicationServicesLocator.services().getService(IconSource.class);
-        return new DirtyIndicatorInterceptor( formModel, iconSource.getIcon( 
iconKey ) );
+    public FormComponentInterceptor getInterceptor(FormModel formModel) {
+        return new DirtyIndicatorInterceptor(formModel);
     }
-
-    public void setIconKey( String iconKey ) {
-        this.iconKey = iconKey;
-    }
-
-    public String getIconKey() {
-        return iconKey;
-    }
-
-    private static class DirtyIndicatorInterceptor extends 
AbstractFormComponentInterceptor {
-        private Icon icon;
-
-        private DirtyIndicatorInterceptor( FormModel formModel, Icon icon ) {
-            super( formModel );
-            this.icon = icon;
-        }
-
-        public void processComponent( final String propertyName, final 
JComponent component ) {
-            final JLabel overlay = new JLabel( icon );
-
-            final OriginalValueHolder originalValueHolder = new 
OriginalValueHolder();
-            final ValueHolder reset = new ValueHolder( Boolean.FALSE );
-            getFormModel().getValueModel( propertyName 
).addValueChangeListener( new PropertyChangeListener() {
-                public void propertyChange( PropertyChangeEvent evt ) {
-                    if( reset.getValue() == Boolean.TRUE ) {
-                        originalValueHolder.reset();
-                        reset.setValue( Boolean.FALSE );
-
-                        overlay.setVisible( false );
-
-                        return;
-                    }
-
-                    if( !originalValueHolder.isInitialized() ) {
-                        originalValueHolder.setOriginalValue( 
evt.getOldValue() );
-                    }
-
-                    if( isDirty(originalValueHolder.getValue(), 
evt.getNewValue()) ) {
-                        MessageSource messageSource = 
(MessageSource)ApplicationServicesLocator.services().getService(MessageSource.class);
-                        String tooltip = messageSource.getMessage(
-                                DEFAULT_MESSAGE_KEY,
-                                new Object[] {
-                                        messageSource.getMessage( propertyName 
+ ".label", null,
-                                                Locale.getDefault() ), 
originalValueHolder.getValue() }, null );
-                        overlay.setToolTipText( tooltip );
-                        overlay.setVisible( true );
-                    } else {
-                        overlay.setVisible( false );
-                    }
-                }
-            } );
-            getFormModel().getFormObjectHolder().addValueChangeListener( new 
PropertyChangeListener() {
-                public void propertyChange( PropertyChangeEvent evt ) {
-                    // reset original value, new "original" value is in the 
form model as
-                    // the form object has changed
-                    reset.setValue( Boolean.TRUE );
-                }
-            } );
-
-            InterceptorOverlayHelper.attachOverlay( overlay, component, 
SwingConstants.NORTH_WEST, 0, 0 );
-            overlay.setVisible( false );
-        }
-
-        private boolean isDirty( Object oldValue, Object newValue ) {
-            if( oldValue == null && newValue instanceof String ) {
-                // hack for string comparison: null equals empty string
-                return !ObjectUtils.nullSafeEquals( "", newValue );
-            }
-
-            return !ObjectUtils.nullSafeEquals( oldValue, newValue );
-        }
-    }
-
-    private static class OriginalValueHolder {
-        private boolean initialized;
-        private Object originalValue;
-
-        public void setOriginalValue( Object value ) {
-            initialized = true;
-            originalValue = value;
-        }
-
-        public void reset() {
-            initialized = false;
-            originalValue = null;
-        }
-
-        public Object getValue() {
-            return originalValue;
-        }
-
-        public boolean isInitialized() {
-            return initialized;
-        }
-    }
 }

Added: 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactoryTests.java
===================================================================
--- 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactoryTests.java
                                (rev 0)
+++ 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorFactoryTests.java
        2006-07-25 11:51:57 UTC (rev 1232)
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 org.springframework.richclient.form.builder.support;
+
+import junit.framework.TestCase;
+
+import org.springframework.binding.form.FormModel;
+import org.springframework.binding.form.support.DefaultFormModel;
+
+/**
+ * Sanity tests for <code>DirtyIndicatorInterceptorFactory</code>
+ *
+ * @author Peter De Bruycker
+ */
+public class DirtyIndicatorInterceptorFactoryTests extends TestCase {
+    public void testGetInterceptor() {
+        DirtyIndicatorInterceptorFactory factory = new 
DirtyIndicatorInterceptorFactory();
+
+        FormModel formModel = new DefaultFormModel();
+        DirtyIndicatorInterceptor interceptor = 
(DirtyIndicatorInterceptor)factory.getInterceptor(formModel);
+
+        assertNotNull("factory returned null", interceptor);
+    }
+
+    public void testGetInterceptorWithNullFormModel() {
+        try {
+            DirtyIndicatorInterceptorFactory factory = new 
DirtyIndicatorInterceptorFactory();
+            factory.getInterceptor(null);
+        } catch (IllegalArgumentException ex) {
+            // test passes
+        }
+
+    }
+}

Added: 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorTests.java
===================================================================
--- 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorTests.java
                               (rev 0)
+++ 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/DirtyIndicatorInterceptorTests.java
       2006-07-25 11:51:57 UTC (rev 1232)
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 org.springframework.richclient.form.builder.support;
+
+import java.util.Locale;
+
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JLayeredPane;
+import javax.swing.JTextField;
+
+import org.springframework.binding.form.FormModel;
+import org.springframework.binding.form.support.DefaultFormModel;
+import org.springframework.binding.value.ValueModel;
+import org.springframework.context.support.StaticMessageSource;
+import 
org.springframework.richclient.application.support.DefaultApplicationServices;
+import org.springframework.richclient.form.binding.Binding;
+import org.springframework.richclient.form.binding.swing.SwingBindingFactory;
+import org.springframework.richclient.test.SpringRichTestCase;
+
+/**
+ * Tests for <code>DirtyIndicatorInterceptor</code>.
+ *
+ * @author Peter De Bruycker
+ */
+public class DirtyIndicatorInterceptorTests extends SpringRichTestCase {
+    public void testProcessComponent() throws InterruptedException {
+        TestBean bean = new TestBean();
+        bean.setProperty("original value");
+
+        FormModel formModel = new DefaultFormModel(bean);
+
+        DirtyIndicatorInterceptor interceptor = new 
DirtyIndicatorInterceptor(formModel);
+        assertEquals(formModel, interceptor.getFormModel());
+
+        Binding binding = new 
SwingBindingFactory(formModel).createBinding("property");
+        JTextField field = (JTextField)binding.getControl();
+        field.setColumns(25);
+        assertNotNull("sanity check: binding defines no component", field);
+
+        interceptor.processComponent("property", field);
+
+        // start a frame to trigger visual updates
+        JFrame frame = new JFrame("test");
+        frame.getContentPane().add(field);
+        frame.pack();
+        frame.setVisible(true);
+
+        // trigger a show of the overlay, so we can get a reference to it
+        ValueModel valueModel = formModel.getValueModel("property");
+        valueModel.setValue("dirty");
+
+        // sleep for a while so the gui can update itself
+        Thread.sleep(500);
+        formModel.revert();
+
+        // find a reference to the overlay component
+        JLayeredPane layeredPane = frame.getRootPane().getLayeredPane();
+        assertEquals("sanity check: assume the layered pane only has one 
component, and that it is a panel and the overlay", 
+                     2, layeredPane.getComponentCount());
+        // the overlay is the first component
+        JComponent overlay = (JComponent)layeredPane.getComponent(0);
+        assertEquals("unable to locate overlay", "dirtyOverlay", 
overlay.getName());
+
+        assertFalse("Overlay must be hidden", overlay.isVisible());
+
+        // mimic user editing
+        valueModel.setValue("ttt");
+        assertTrue("Value is dirty, so overlay must be visible", 
overlay.isVisible());
+
+        // user reverts the edit
+        valueModel.setValue("original value");
+        assertFalse("value is not dirty, so overlay must be hidden", 
overlay.isVisible());
+
+        // dispose of the frame
+        frame.dispose();
+    }
+
+    protected void registerBasicServices(DefaultApplicationServices 
applicationServices) {
+        super.registerBasicServices(applicationServices);
+
+        StaticMessageSource messageSource = new StaticMessageSource();
+        messageSource.addMessage("dirty.message", Locale.getDefault(), "{0} 
has changed, original value was {1}.");
+        messageSource.addMessage("revert.message", Locale.getDefault(), 
"Revert value to {0}.");
+
+        messageSource.addMessage("property.label", Locale.getDefault(), 
"Property");
+
+        applicationServices.setMessageSource(messageSource);
+    }
+}

Added: 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/TestBean.java
===================================================================
--- 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/TestBean.java
                             (rev 0)
+++ 
trunk/spring-richclient/support/src/test/java/org/springframework/richclient/form/builder/support/TestBean.java
     2006-07-25 11:51:57 UTC (rev 1232)
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2002-2006 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
+ *
+ * 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 org.springframework.richclient.form.builder.support;
+
+/**
+ * Bean for testing purposes.
+ *
+ * @author Peter De Bruycker
+ */
+public class TestBean {
+    private String property;
+
+    public String getProperty() {
+        return property;
+    }
+
+    public void setProperty(String property) {
+        this.property = property;
+    }
+}


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
spring-rich-c-cvs mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/spring-rich-c-cvs

Reply via email to