View Javadoc
1   /**
2    * This file Copyright (c) 2019 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   */
33  package info.magnolia.ui.field;
34  
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import com.vaadin.data.HasValue;
39  import com.vaadin.shared.Registration;
40  import com.vaadin.shared.ui.MarginInfo;
41  import com.vaadin.ui.Component;
42  import com.vaadin.ui.CustomField;
43  import com.vaadin.ui.HorizontalLayout;
44  import com.vaadin.ui.Label;
45  import com.vaadin.ui.Layout;
46  import com.vaadin.ui.Slider;
47  import com.vaadin.ui.VerticalLayout;
48  
49  /**
50   * A field wrapper for a slider, supporting the FormLayout-managed (left) caption.
51   */
52  public class SliderField extends CustomField<Double> {
53  
54      private static final Logger log = LoggerFactory.getLogger(SliderField.class);
55  
56      private final SliderFieldDefinition definition;
57      private final Label sliderValueLabel;
58      private final Slider slider;
59  
60      public SliderField(SliderFieldDefinition definition) {
61          double stepSize = definition.getStepSize();
62          if (stepSize <= 0) {
63              log.warn("Invalid stepSize: {}. Positive number is required for the stepSize. Fallback to stepSize: 1", stepSize);
64              stepSize = 1;
65          }
66          this.definition = definition;
67          sliderValueLabel = new Label("");
68          int decimalDigits = digitsAfterDecimalPoint(stepSize);
69          this.slider = new Slider(definition.getMin(), definition.getMax(), decimalDigits + 1);
70          this.slider.setCaption(definition.getLabel());
71          slider.addValueChangeListener(createStepValueChangeListener(stepSize, decimalDigits));
72      }
73  
74      @Override
75      protected Component initContent() {
76          addStyleName("sliderfield");
77          HorizontalLayout rootLayout = new HorizontalLayout();
78          rootLayout.setSizeFull();
79          rootLayout.setSpacing(false);
80          sliderValueLabel.addStyleName("slider-value");
81          sliderValueLabel.setWidth("50px");
82          rootLayout.addComponent(sliderValueLabel);
83          VerticalLayout sliderWithGridLayout = new VerticalLayout();
84          sliderWithGridLayout.setSizeFull();
85          sliderWithGridLayout.setSpacing(false);
86          slider.setWidth("100%");
87          sliderWithGridLayout.addComponent(slider);
88          sliderWithGridLayout.setExpandRatio(slider, 1);
89          if (definition.getGridStepSize() > 0) {
90              sliderWithGridLayout.addComponent(createGrid());
91          }
92          rootLayout.addComponent(sliderWithGridLayout);
93          rootLayout.setExpandRatio(sliderWithGridLayout, 1f);
94          return rootLayout;
95      }
96  
97      @Override
98      protected void doSetValue(Double value) {
99          slider.setValue(value == null ? 0.0 : value);
100     }
101 
102     @Override
103     public Double getValue() {
104         return slider.getValue();
105     }
106 
107     @Override
108     public Registration addValueChangeListener(ValueChangeListener<Double> listener) {
109         return slider.addValueChangeListener(listener);
110     }
111 
112     private int digitsAfterDecimalPoint(double value) {
113         String stringValue = Double.toString(value);
114         int decimalIndex = stringValue.indexOf('.');
115         int digitsAfterDecimalPoint = stringValue.length() - decimalIndex - 1;
116         char lastDecimalDigit = stringValue.charAt(stringValue.length() - 1);
117         return lastDecimalDigit == '0' ? digitsAfterDecimalPoint - 1 : digitsAfterDecimalPoint;
118     }
119 
120     private HasValue.ValueChangeListener<Double> createStepValueChangeListener(double stepSize, int decimalDigits) {
121         return valueChangeEvent -> {
122             int toIntMultiplier = (int) Math.pow(10, slider.getResolution());
123             int intStepSize = (int) (stepSize * toIntMultiplier);
124             int sliderIntValue = (int) (valueChangeEvent.getValue() * toIntMultiplier);
125             if (sliderIntValue % intStepSize == 0) {
126                 sliderValueLabel.setValue(definition.getValueFormat().apply(valueChangeEvent.getValue(), decimalDigits));
127                 return;
128             }
129             if (valueChangeEvent.getValue() > valueChangeEvent.getOldValue()) {
130                 //ceil snapped value
131                 slider.setValue((double) ((sliderIntValue / intStepSize + 1) * intStepSize) / toIntMultiplier);
132             } else {
133                 // floor snapped value
134                 slider.setValue((double) ((sliderIntValue / intStepSize) * intStepSize) / toIntMultiplier);
135             }
136         };
137     }
138 
139     private Layout createGrid() {
140         HorizontalLayout gridLayout = new HorizontalLayout();
141         gridLayout.setSpacing(false);
142         gridLayout.setWidth("100%");
143         gridLayout.setHeight("5px");
144         gridLayout.setMargin(new MarginInfo(false, true));
145         gridLayout.addStyleName("slidergrid");
146         double gridStepSize = definition.getGridStepSize();
147         int gridSize = (int) ((definition.getMax() - definition.getMin()) / gridStepSize);
148         float gridExpansionRatio = 1f / (float) gridSize;
149         int decimalDigits = digitsAfterDecimalPoint(gridStepSize);
150         for (int gridIndex = 0; gridIndex < gridSize; gridIndex++) {
151             double gridValue = definition.getMin() + gridIndex * gridStepSize;
152             Label gridLabel = new Label(definition.getValueFormat().apply(gridValue, decimalDigits));
153             gridLayout.addComponent(gridLabel);
154             gridLayout.setExpandRatio(gridLabel, gridExpansionRatio);
155         }
156         Label lastGridLabel = new Label(definition.getValueFormat().apply(definition.getMax(), decimalDigits));
157         gridLayout.addComponent(lastGridLabel);
158         return gridLayout;
159     }
160 }