diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..ea5647b
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/plugin-widget-digitroll.iml b/plugin-widget-digitroll.iml
new file mode 100644
index 0000000..20fe623
--- /dev/null
+++ b/plugin-widget-digitroll.iml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugin.xml b/plugin.xml
new file mode 100644
index 0000000..fbbb73d
--- /dev/null
+++ b/plugin.xml
@@ -0,0 +1,29 @@
+
+
+ com.fr.plugin.widget.digitroll
+
+ yes
+ 1.0
+ 8.0
+ 2017-8-05
+ Felix
+
+ [2018-07-13]Felix:首发
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/fr/plugin/widget/digitroll/Constants.java b/src/com/fr/plugin/widget/digitroll/Constants.java
new file mode 100644
index 0000000..41ddfcf
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/Constants.java
@@ -0,0 +1,6 @@
+package com.fr.plugin.widget.digitroll;
+
+public class Constants {
+ public static final String PLUGIN_ID = "com.fr.plugin.widget.digitroll";
+
+}
diff --git a/src/com/fr/plugin/widget/digitroll/CssFileHandler.java b/src/com/fr/plugin/widget/digitroll/CssFileHandler.java
new file mode 100644
index 0000000..0ca2bdb
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/CssFileHandler.java
@@ -0,0 +1,10 @@
+package com.fr.plugin.widget.digitroll;
+
+import com.fr.stable.fun.impl.AbstractCssFileHandler;
+
+public class CssFileHandler extends AbstractCssFileHandler {
+ @Override
+ public String[] pathsForFiles() {
+ return new String[]{""};
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/JavaScriptFileLoader.java b/src/com/fr/plugin/widget/digitroll/JavaScriptFileLoader.java
new file mode 100644
index 0000000..38f375d
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/JavaScriptFileLoader.java
@@ -0,0 +1,15 @@
+package com.fr.plugin.widget.digitroll;
+
+import com.fr.stable.fun.Authorize;
+import com.fr.stable.fun.impl.AbstractJavaScriptFileHandler;
+
+@Authorize(
+ callSignKey = Constants.PLUGIN_ID
+)
+public class JavaScriptFileLoader extends AbstractJavaScriptFileHandler {
+ @Override
+ public String[] pathsForFiles() {
+ return new String[]{
+ "/com/fr/plugin/widget/digitroll/web/widget.digitRoll.js"};
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/LocaleFinder.java b/src/com/fr/plugin/widget/digitroll/LocaleFinder.java
new file mode 100644
index 0000000..dee40a8
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/LocaleFinder.java
@@ -0,0 +1,10 @@
+package com.fr.plugin.widget.digitroll;
+
+import com.fr.stable.fun.impl.AbstractLocaleFinder;
+
+public class LocaleFinder extends AbstractLocaleFinder {
+ @Override
+ public String find() {//查找字符串,类似于android的value
+ return "com/fr/plugin/widget/digitroll/locale/digitroll";
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/OptionProvider.java b/src/com/fr/plugin/widget/digitroll/OptionProvider.java
new file mode 100644
index 0000000..e48355a
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/OptionProvider.java
@@ -0,0 +1,53 @@
+package com.fr.plugin.widget.digitroll;
+import com.fr.design.fun.impl.AbstractFormWidgetOptionProvider;
+import com.fr.form.ui.Widget;
+import com.fr.general.Inter;
+import com.fr.plugin.ExtraClassManager;
+import com.fr.stable.fun.FunctionHelper;
+import com.fr.stable.fun.FunctionProcessor;
+import com.fr.stable.fun.impl.AbstractFunctionProcessor;
+
+
+public class OptionProvider extends AbstractFormWidgetOptionProvider {
+
+ private static FunctionProcessor ONEFUNCTION = new AbstractFunctionProcessor() {
+ public int getId() {
+ return FunctionHelper.generateFunctionID(Constants.PLUGIN_ID);
+ }
+
+ public String toString() {
+ return Inter.getLocText("Plugin-DigitRoll_Widget");
+ }
+ };
+
+ @Override
+ public Class extends Widget> classForWidget() {
+ FunctionProcessor processor = ExtraClassManager.getInstance().getFunctionProcessor();
+ if (processor != null) {
+ processor.recordFunction(ONEFUNCTION);
+ }
+
+ return Widget.class;
+ }
+
+ @Override
+ public Class> appearanceForWidget() {
+ //界面类
+ return WidgetUI.class;
+ }
+
+ @Override
+ public String iconPathForWidget() {//图标
+ return "com/fr/plugin/widget/digitroll/images/digitroll_icon.png";
+ }
+
+ @Override
+ public String nameForWidget() {//插件名称
+ return Inter.getLocText("Plugin-DigitRoll_Widget");
+ }
+
+ @Override
+ public boolean isContainer() {//是否容器
+ return false;
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/Util.java b/src/com/fr/plugin/widget/digitroll/Util.java
new file mode 100644
index 0000000..673fe73
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/Util.java
@@ -0,0 +1,33 @@
+package com.fr.plugin.widget.digitroll;
+
+import com.fr.base.FRContext;
+
+import java.math.BigDecimal;
+
+public class Util {
+ private Util() {
+ }
+
+ public static String colorValueToHexString(int color) {
+ return '#' + Integer.toHexString(color).substring(2);
+ }
+
+ public static int valueChangeToInt(Object o) {
+ int value = 0;
+ if (o instanceof Integer) {
+ value = ((Integer) o).intValue();
+ } else if (o instanceof BigDecimal) {
+ value = ((BigDecimal) o).intValue();
+ } else if (o instanceof Double) {
+ value = ((Double) o).intValue();
+ } else if (o instanceof String) {
+ try {
+ value = Integer.parseInt(o.toString());
+ } catch (Exception var4) {
+ FRContext.getLogger().error(var4.getMessage(), var4);
+ }
+ }
+
+ return value;
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/Widget.java b/src/com/fr/plugin/widget/digitroll/Widget.java
new file mode 100644
index 0000000..48628fc
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/Widget.java
@@ -0,0 +1,321 @@
+package com.fr.plugin.widget.digitroll;
+
+import com.fr.base.FRContext;
+import com.fr.base.Formula;
+import com.fr.base.ParameterMapNameSpace;
+import com.fr.form.data.DataBinding;
+import com.fr.form.ui.DataControl;
+import com.fr.form.ui.DirectWriteEditor;
+import com.fr.form.ui.WidgetValue;
+import com.fr.general.Inter;
+import com.fr.general.ModuleContext;
+import com.fr.json.JSONArray;
+import com.fr.json.JSONException;
+import com.fr.json.JSONObject;
+import com.fr.plugin.PluginLicense;
+import com.fr.plugin.PluginLicenseManager;
+import com.fr.script.Calculator;
+import com.fr.stable.ArrayUtils;
+import com.fr.stable.CodeUtils;
+import com.fr.stable.core.NodeVisitor;
+import com.fr.stable.script.CalculatorProvider;
+import com.fr.stable.web.Repository;
+import com.fr.stable.xml.XMLPrintWriter;
+import com.fr.stable.xml.XMLableReader;
+import com.fr.web.core.SessionIDInfor;
+import com.fr.web.utils.WebUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import java.awt.*;
+
+
+public class Widget extends DirectWriteEditor implements DataControl {
+ public static final int TYPE_YEAR_NONE = 8;
+ public static final String ALIGN = "align";
+ public static final String DURATION = "duration";
+ public static final String FONT_SIZE = "fontSize";
+ public static final String FONT_COLOR = "fontColor";
+ public static final String BACKGROUND_COLOR = "backgroundColor";
+ public static final String PREFIX = "prefix";
+ public static final String SUFFIX = "suffix";
+ public static final String CSS = "css";
+
+ private WidgetValue widgetValue = new WidgetValue();
+ private String align = new String();
+ private int duration = 0;
+ private int fontSize = 70;
+ private Object fontColor = new Color(204, 204, 204);
+ private Object backgroundColor = new Color(51, 51, 51);
+ private String prefix;
+ private String suffix;
+
+ public Widget() {
+ }
+
+ public String getAlign() {
+ return this.align;
+ }
+
+ public void setAlign(String align) {
+ this.align = align;
+ }
+
+ public int getDuration() {
+ return this.duration;
+ }
+
+ public void setDuration(int duration) {
+ this.duration = duration;
+ }
+
+ public WidgetValue getWidgetValue() {
+ return this.widgetValue;
+ }
+
+ public void setWidgetValue(WidgetValue widgetValue) {
+ this.widgetValue = widgetValue;
+ }
+
+ public int getFontSize() {
+ return fontSize;
+ }
+
+ public void setFontSize(int fontSize) {
+ this.fontSize = fontSize;
+ }
+
+ public Object getFontColor() {
+ return fontColor;
+ }
+
+ public void setFontColor(Object fontColor) {
+ this.fontColor = fontColor;
+ }
+
+ public Object getBackgroundColor() {
+ return backgroundColor;
+ }
+
+ public void setBackgroundColor(Object backgroundColor) {
+ this.backgroundColor = backgroundColor;
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public void setPrefix(String prefix) {
+ this.prefix = prefix;
+ }
+
+ public String getSuffix() {
+ return suffix;
+ }
+
+ public void setSuffix(String suffix) {
+ this.suffix = suffix;
+ }
+
+ @Override
+ public String getXType() {//控件类型,参考其他的案例,返回的是文本框
+ return "digitroll";
+ }
+
+ @Override
+ // 数据源类型:空置,公式,字段
+ public int[] getValueType() {
+ return new int[]{TYPE_YEAR_NONE, TYPE_FORMULA, TYPE_DATABINDING};
+ }
+
+ @Override
+ public String[] dependence(CalculatorProvider calculatorProvider) {
+ return super.dependence(calculatorProvider);
+ }
+
+ private int getWidgetValueFM(Calculator calculator) throws Exception {
+ int value = 0;
+ if (this.widgetValue.getValue() == null) {
+ value = 0;
+ } else if (this.widgetValue.getValue() instanceof Formula) {
+ Object v = calculator.eval((Formula) this.widgetValue.getValue());
+ value = Util.valueChangeToInt(v);
+ } else if (this.widgetValue.getValue() instanceof DataBinding) {
+ value = this.getWidgetValueByBinding(calculator);
+ }
+
+ return value;
+ }
+
+ private int getWidgetValueByBinding(Calculator calculator) {
+ int value = 0;
+ WidgetValue.WidgetValueInfo valueInfo = new WidgetValue.WidgetValueInfo(this.getWidgetName());
+ Object result = this.getWidgetValue().createAttrResult(valueInfo, calculator, new JSONObject());
+ value = Util.valueChangeToInt(result);
+ return value;
+ }
+
+ @Override
+ public void createValueResult(DataControl dataControl, Calculator calculator,
+ JSONObject jsonObject, JSONObject jsonObject1) {
+ if (this.getWidgetValue() != null) {
+ WidgetValue.WidgetValueInfo valueInfo = new WidgetValue.WidgetValueInfo(this.getWidgetName());
+ Object result = this.getWidgetValue().createAttrResult(valueInfo, calculator, jsonObject1);
+
+ try {
+ jsonObject.put(this.widgetName.toUpperCase(), result == null ? "" : result);
+ } catch (JSONException e) {
+ FRContext.getLogger().error(e.getMessage(), e);
+ }
+ }
+ }
+
+ @Override
+ public String getFormatText() {
+ return "";
+ }
+
+ @Override
+ public JSONObject createJSONConfig(Repository repository,
+ Calculator calculator,
+ NodeVisitor visitor) throws JSONException {
+ // 许可证
+ PluginLicense pluginLicense = PluginLicenseManager.getInstance().getPluginLicenseByID(Constants.PLUGIN_ID);
+ if (pluginLicense.isAvailable()) {
+ JSONObject jsonConfig = super.createJSONConfig(repository, calculator, visitor);
+ try {
+ Object value = jsonConfig.get("value");
+ int v = Util.valueChangeToInt(value);
+ if (value == null) {
+ jsonConfig.remove("value");
+ } else if (value instanceof String) {
+ jsonConfig.put("value", (new JSONObject()).put("digitroll", v));
+ } else {
+ jsonConfig.put("value", v);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ if (this.align != null) {
+ jsonConfig.put(ALIGN, this.value2Config(this.align, calculator));
+ }
+
+ jsonConfig.put(DURATION, this.value2Config(this.duration, calculator));
+ jsonConfig.put(PREFIX, getPrefix());
+ jsonConfig.put(SUFFIX, getSuffix());
+ jsonConfig.put(CSS, buildCss());
+
+ String[] dependence = this.dependence(calculator);
+ if (!ArrayUtils.isEmpty(dependence)) {
+ jsonConfig.put("dependence", dependence);
+ }
+ return jsonConfig;
+ } else {
+ return licenseErr();
+ }
+ }
+
+ private String buildCss() {
+ StringBuilder css = new StringBuilder();
+ String fontClr = Util.colorValueToHexString(((Color) getFontColor()).getRGB());
+ String bgClr = Util.colorValueToHexString(((Color) getBackgroundColor()).getRGB());
+ css.append("color:" + fontClr + ';');
+ css.append("font-size:" + getFontSize() + ';');
+ css.append("background:" + bgClr + ";");
+ css.append("position:absolute;");
+ css.append("left:0px;");
+ css.append("top:0px;");
+ return css.toString();
+ }
+
+ @Override
+ public JSONArray createJSONData(SessionIDInfor sessionIDInfor,
+ Calculator calculator, HttpServletRequest request) throws Exception {
+ PluginLicense pluginLicense = PluginLicenseManager.getInstance().getPluginLicenseByID(Constants.PLUGIN_ID);
+ JSONArray errorMsg;
+ if (pluginLicense.isAvailable()) {
+ errorMsg = super.createJSONData(sessionIDInfor, calculator, request);
+ ParameterMapNameSpace parameterMapNameSpace = ParameterMapNameSpace.create(sessionIDInfor.getParameterMap4Execute());
+ DependenceNameSpace dependenceNameSpace = new DependenceNameSpace(CodeUtils.cjkDecode(WebUtils.getHTTPRequestParameter(request, "dependence")));
+ calculator.pushNameSpace(parameterMapNameSpace);
+ calculator.pushNameSpace(dependenceNameSpace);
+ JSONObject jsonObject = new JSONObject();
+ jsonObject.put("value", this.getWidgetValueFM(calculator));
+ errorMsg.put(jsonObject);
+ calculator.removeNameSpace(parameterMapNameSpace);
+ calculator.removeNameSpace(dependenceNameSpace);
+ return errorMsg;
+ } else {
+ errorMsg = new JSONArray();
+ errorMsg.put(licenseErr());
+ return errorMsg;
+ }
+ }
+
+ private JSONObject licenseErr() throws JSONException {
+ return (new JSONObject()).put("text", Inter.getLocText("Plugin-Flip_License_Expired"))
+ .put("value", Inter.getLocText("Plugin-Flip_License_Expired"));
+ }
+
+ private boolean isDesign() {
+ return ModuleContext.isModuleStarted("com.fr.design.module.DesignerModule");
+ }
+
+ @Override
+ public String[] supportedEvents() {
+ return new String[0];
+ }
+
+ @Override
+ public void readXML(XMLableReader reader) {
+ super.readXML(reader);
+ if (reader.isChildNode()) {
+ String tagName = reader.getTagName();
+ if (tagName.equals("DigitRollAttr")) {
+ String s = null;
+ if ((s = reader.getAttrAsString(ALIGN, null)) != null) {
+ this.setAlign(s);
+ }
+ if ((s = reader.getAttrAsString(DURATION, null)) != null) {
+ this.setDuration(Integer.parseInt(s));
+ }
+ if ((s = reader.getAttrAsString(FONT_SIZE, null)) != null) {
+ setFontSize(Integer.parseInt(s));
+ }
+
+ int color;
+ if ((color = reader.getAttrAsInt(FONT_COLOR, -1)) != -1) {
+ setFontColor(new Color(color));
+ }
+ if ((color = reader.getAttrAsInt(BACKGROUND_COLOR, -1)) != -1) {
+ setBackgroundColor(new Color(color));
+ }
+ } else if ("widgetValue".equals(tagName)) {
+ this.widgetValue = new WidgetValue();
+ reader.readXMLObject(this.widgetValue);
+ }
+ }
+ }
+
+ @Override
+ public void writeXML(XMLPrintWriter writer) {
+ super.writeXML(writer);
+ writer.startTAG("DigitRollAttr");
+ if (this.align != null) {
+ writer.attr(ALIGN, this.getAlign().toString());
+ }
+
+ if (this.duration != 0) {
+ writer.attr(DURATION, this.getDuration());
+ }
+
+ writer.attr(FONT_SIZE, getFontSize());
+ writer.attr(FONT_COLOR, ((Color) getFontColor()).getRGB());
+ writer.attr(BACKGROUND_COLOR, ((Color) getBackgroundColor()).getRGB());
+ writer.end();
+
+ if (this.widgetValue != null) {
+ this.widgetValue.writeXML(writer);
+ }
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/WidgetUI.java b/src/com/fr/plugin/widget/digitroll/WidgetUI.java
new file mode 100644
index 0000000..c372950
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/WidgetUI.java
@@ -0,0 +1,84 @@
+package com.fr.plugin.widget.digitroll;
+
+import com.fr.design.designer.creator.CRPropertyDescriptor;
+import com.fr.design.designer.creator.XWidgetCreator;
+import com.fr.design.gui.ilable.UILabel;
+import com.fr.design.gui.itextfield.UITextField;
+import com.fr.design.layout.FRGUIPaneFactory;
+import com.fr.design.mainframe.widget.editors.ColorEditor;
+import com.fr.form.ui.Widget;
+import com.fr.general.IOUtils;
+import com.fr.general.Inter;
+import com.fr.plugin.widget.digitroll.editor.DigitRollAlign;
+import com.fr.plugin.widget.digitroll.editor.DigitRollDataSourceEditor;
+import com.fr.stable.ArrayUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.beans.IntrospectionException;
+
+public class WidgetUI extends XWidgetCreator {
+ private UITextField textField;
+
+ public WidgetUI(Widget widget, Dimension dimension) {
+ super(widget, dimension);
+ }
+
+ @Override
+ protected JComponent initEditor() {
+ //界面,就是在拖动到主面板上的时候,添加了一个LABLE控件,然后显示图标,并居中对齐
+ if (this.editor == null) {
+ this.editor = FRGUIPaneFactory.createBorderLayout_S_Pane();
+ UILabel label = new UILabel();
+ label.setIcon(IOUtils.readIcon("/com/fr/plugin/widget/digitroll/images/digitroll_display.png"));
+ label.setHorizontalAlignment(0);
+ label.setVerticalAlignment(0);
+ this.editor.add(label, "Center");
+ this.textField = new UITextField(5);
+ this.textField.setOpaque(false);
+ this.editor.add(this.textField, "South");
+ this.editor.setBackground(Color.WHITE);
+ }
+
+ return this.editor;
+ }
+
+ public CRPropertyDescriptor[] supportedDescriptor() throws IntrospectionException {
+ return ArrayUtils.addAll(super.supportedDescriptor()
+ , new CRPropertyDescriptor[]{
+ (new CRPropertyDescriptor("widgetValue", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_DataSource"))
+ .setEditorClass(DigitRollDataSourceEditor.class)
+ .putKeyValue("category", "Advanced"),
+ (new CRPropertyDescriptor("align", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_AlignType"))
+ .setEditorClass(DigitRollAlign.class)
+ .putKeyValue("category", "Advanced"),
+ (new CRPropertyDescriptor("duration", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_Duration"))
+ .putKeyValue("category", "Advanced"),
+ (new CRPropertyDescriptor("prefix", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_Prefix"))
+ .putKeyValue("category", "Advanced"),
+ (new CRPropertyDescriptor("suffix", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_Suffix"))
+ .putKeyValue("category", "Advanced"),
+ (new CRPropertyDescriptor("fontSize", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_FontSize"))
+ .putKeyValue("category", "Advanced"),
+ (new CRPropertyDescriptor("fontColor", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_ColorFont"))
+ .setEditorClass(ColorEditor.class)
+ .putKeyValue("category", "Advanced"),
+ (new CRPropertyDescriptor("backgroundColor", this.data.getClass()))
+ .setI18NName(Inter.getLocText("Plugin-DigitRoll_ColorBackground"))
+ .setEditorClass(ColorEditor.class)
+ .putKeyValue("category", "Advanced")
+ }
+ );
+ }
+
+ public String getIconPath() {
+ return "com/fr/plugin/widget/digitroll/images/digitroll_icon.png";
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/editor/DigitRollAlign.java b/src/com/fr/plugin/widget/digitroll/editor/DigitRollAlign.java
new file mode 100644
index 0000000..3944c31
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/editor/DigitRollAlign.java
@@ -0,0 +1,28 @@
+package com.fr.plugin.widget.digitroll.editor;
+
+import com.fr.design.mainframe.widget.editors.ComboEditor;
+import com.fr.general.Inter;
+
+import javax.swing.*;
+import java.util.Vector;
+
+public class DigitRollAlign extends ComboEditor {
+ public DigitRollAlign() {
+ }
+
+ public ComboBoxModel model() {
+ Vector vector = new Vector();
+ vector.add(Inter.getLocText("Plugin-DigitRoll_AlignLeft"));
+ vector.add(Inter.getLocText("Plugin-DigitRoll_AlignCenter"));
+ vector.add(Inter.getLocText("Plugin-DigitRoll_AlignRight"));
+ return new DefaultComboBoxModel(vector);
+ }
+
+ /*public void setValue(Object o) {
+ this.comboBox.setSelectedItem(o);
+ }*/
+
+ /*public Object getValue() {
+ return this.comboBox.getSelectedItem();
+ }*/
+}
diff --git a/src/com/fr/plugin/widget/digitroll/editor/DigitRollDataSourceEditor.java b/src/com/fr/plugin/widget/digitroll/editor/DigitRollDataSourceEditor.java
new file mode 100644
index 0000000..955852b
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/editor/DigitRollDataSourceEditor.java
@@ -0,0 +1,88 @@
+package com.fr.plugin.widget.digitroll.editor;
+
+import com.fr.design.Exception.ValidationException;
+import com.fr.design.editor.ValueEditorPane;
+import com.fr.design.editor.editor.Editor;
+import com.fr.design.editor.editor.FormulaEditor;
+import com.fr.design.editor.editor.NoneEditor;
+import com.fr.design.mainframe.widget.editors.AbstractPropertyEditor;
+import com.fr.design.mainframe.widget.editors.DataBindingEditor;
+import com.fr.design.mainframe.widget.editors.ServerDataBindingEditor;
+import com.fr.form.ui.DataControl;
+import com.fr.form.ui.WidgetValue;
+import com.fr.general.Inter;
+
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+public class DigitRollDataSourceEditor extends AbstractPropertyEditor {
+ private DataControl widget;
+ private ValueEditorPane wep;
+
+ public DigitRollDataSourceEditor(Object o) {
+ this.widget = (DataControl) o;
+ Editor[] editors = createWidgetValueEditor(this.widget, false);
+ Editor[] editors1 = editors;
+ int length = editors.length;
+
+ for (int i = 0; i < length; ++i) {
+ Editor e = editors1[i];
+ e.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ DigitRollDataSourceEditor.this.firePropertyChanged();
+ }
+ });
+ }
+
+ this.wep = new ValueEditorPane(editors);
+ this.wep.addPropertyChangeListener("value", new PropertyChangeListener() {
+ public void propertyChange(PropertyChangeEvent evt) {
+ DigitRollDataSourceEditor.this.firePropertyChanged();
+ }
+ });
+ }
+
+ public static Editor[] createWidgetValueEditor(DataControl data, boolean onlyServer) {
+ int[] types = data.getValueType();
+ Editor[] editor = new Editor[types.length];
+
+ for (int i = 0; i < types.length; ++i) {
+ editor[i] = createWidgetValueEditorByType(types[i], onlyServer);
+ }
+
+ return editor;
+ }
+
+ public static Editor createWidgetValueEditorByType(int type, boolean onlyServer) {
+ switch (type) {
+ case 2:
+ return (Editor) (onlyServer ? new ServerDataBindingEditor() : new DataBindingEditor());
+ case 3:
+ return new FormulaEditor(Inter.getLocText("Parameter-Formula"));
+ case 8:
+ return new NoneEditor((String) null, Inter.getLocText("Plugin-DigitRoll_None"));
+ default:
+ return null;
+ }
+ }
+
+ public void validateValue() throws ValidationException {
+ }
+
+ public Component getCustomEditor() {
+ return this.wep;
+ }
+
+ public Object getValue() {
+ return new WidgetValue(this.wep.update());
+ }
+
+ public void setValue(Object value) {
+ if (value != null) {
+ this.wep.populate(((WidgetValue) value).getValue());
+ }
+ }
+}
diff --git a/src/com/fr/plugin/widget/digitroll/images/digitroll_display.png b/src/com/fr/plugin/widget/digitroll/images/digitroll_display.png
new file mode 100644
index 0000000..da530a2
Binary files /dev/null and b/src/com/fr/plugin/widget/digitroll/images/digitroll_display.png differ
diff --git a/src/com/fr/plugin/widget/digitroll/images/digitroll_icon.png b/src/com/fr/plugin/widget/digitroll/images/digitroll_icon.png
new file mode 100644
index 0000000..3ca516a
Binary files /dev/null and b/src/com/fr/plugin/widget/digitroll/images/digitroll_icon.png differ
diff --git a/src/com/fr/plugin/widget/digitroll/local/digitroll.properties b/src/com/fr/plugin/widget/digitroll/local/digitroll.properties
new file mode 100644
index 0000000..c8daf03
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/local/digitroll.properties
@@ -0,0 +1,14 @@
+Plugin-DigitRoll_Widget=DigitRoll
+Plugin-DigitRoll_License_Expired=Expired
+Plugin-DigitRoll_DataSource=DataSource
+Plugin-DigitRoll_AlignType=AlignType
+Plugin-DigitRoll_Duration=Duration
+Plugin-DigitRoll_Prefix=Prefix
+Plugin-DigitRoll_Suffix=Suffix
+Plugin-DigitRoll_None=NoneValue
+Plugin-DigitRoll_AlignLeft=Left
+Plugin-DigitRoll_AlignCenter=Center
+Plugin-DigitRoll_AlignRight=Right
+Plugin-DigitRoll_FontSize = FontSize
+Plugin-DigitRoll_ColorFont=FontColor
+Plugin-DigitRoll_ColorBackground=BackgroundColor
diff --git a/src/com/fr/plugin/widget/digitroll/local/digitroll_zh_CN.properties b/src/com/fr/plugin/widget/digitroll/local/digitroll_zh_CN.properties
new file mode 100644
index 0000000..d898e50
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/local/digitroll_zh_CN.properties
@@ -0,0 +1,14 @@
+Plugin-DigitRoll_Widget=\u6EDA\u52A8\u6570\u5B57
+Plugin-DigitRoll_License_Expired=\u8BB8\u53EF\u8BC1\u5DF2\u8FC7\u671F
+Plugin-DigitRoll_DataSource=\u6570\u636E\u6E90
+Plugin-DigitRoll_AlignType=\u5BF9\u9F50\u65B9\u5F0F
+Plugin-DigitRoll_Duration=\u6301\u7EED\u65F6\u95F4\uFF08\u79D2\uFF09
+Plugin-DigitRoll_Prefix=\u524D\u7F00
+Plugin-DigitRoll_Suffix=\u540E\u7F00
+Plugin-DigitRoll_None=\u7A7A\u503C
+Plugin-DigitRoll_AlignLeft=\u5DE6\u5BF9\u9F50
+Plugin-DigitRoll_AlignCenter=\u5C45\u4E2D\u5BF9\u9F50
+Plugin-DigitRoll_AlignRight=\u53F3\u5BF9\u9F50
+Plugin-DigitRoll_FontSize = \u6587\u5B57\u5927\u5C0F
+Plugin-DigitRoll_ColorFont=\u6587\u5B57\u989C\u8272
+Plugin-DigitRoll_ColorBackground=\u80CC\u666F\u989C\u8272
diff --git a/src/com/fr/plugin/widget/digitroll/web/countUp.js b/src/com/fr/plugin/widget/digitroll/web/countUp.js
new file mode 100644
index 0000000..cf7523e
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/web/countUp.js
@@ -0,0 +1,254 @@
+/*
+
+ countUp.js
+ by @inorganik
+
+*/
+
+// target = id of html element or var of previously selected html element where counting occurs
+// startVal = the value you want to begin at
+// endVal = the value you want to arrive at
+// decimals = number of decimal places, default 0
+// duration = duration of animation in seconds, default 2
+// options = optional object of options (see below)
+
+var CountUp = function (target, startVal, endVal, decimals, duration, options) {
+
+ var self = this;
+ self.version = function () {
+ return '1.9.3';
+ };
+
+ // default options
+ self.options = {
+ useEasing: true, // toggle easing
+ useGrouping: true, // 1,000,000 vs 1000000
+ separator: ',', // character to use as a separator
+ decimal: '.', // character to use as a decimal
+ easingFn: easeOutExpo, // optional custom easing function, default is Robert Penner's easeOutExpo
+ formattingFn: formatNumber, // optional custom formatting function, default is formatNumber above
+ prefix: '', // optional text before the result
+ suffix: '', // optional text after the result
+ numerals: [] // optionally pass an array of custom numerals for 0-9
+ };
+
+ // extend default options with passed options object
+ if (options && typeof options === 'object') {
+ for (var key in self.options) {
+ if (options.hasOwnProperty(key) && options[key] !== null) {
+ self.options[key] = options[key];
+ }
+ }
+ }
+
+ if (self.options.separator === '') {
+ self.options.useGrouping = false;
+ }
+ else {
+ // ensure the separator is a string (formatNumber assumes this)
+ self.options.separator = '' + self.options.separator;
+ }
+
+ // make sure requestAnimationFrame and cancelAnimationFrame are defined
+ // polyfill for browsers without native support
+ // by Opera engineer Erik Möller
+ var lastTime = 0;
+ var vendors = ['webkit', 'moz', 'ms', 'o'];
+ for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
+ window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
+ }
+ if (!window.requestAnimationFrame) {
+ window.requestAnimationFrame = function (callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function () {
+ callback(currTime + timeToCall);
+ }, timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ }
+ if (!window.cancelAnimationFrame) {
+ window.cancelAnimationFrame = function (id) {
+ clearTimeout(id);
+ };
+ }
+
+ function formatNumber(num) {
+ var neg = (num < 0),
+ x, x1, x2, x3, i, len;
+ num = Math.abs(num).toFixed(self.decimals);
+ num += '';
+ x = num.split('.');
+ x1 = x[0];
+ x2 = x.length > 1 ? self.options.decimal + x[1] : '';
+ if (self.options.useGrouping) {
+ x3 = '';
+ for (i = 0, len = x1.length; i < len; ++i) {
+ if (i !== 0 && ((i % 3) === 0)) {
+ x3 = self.options.separator + x3;
+ }
+ x3 = x1[len - i - 1] + x3;
+ }
+ x1 = x3;
+ }
+ // optional numeral substitution
+ if (self.options.numerals.length) {
+ x1 = x1.replace(/[0-9]/g, function (w) {
+ return self.options.numerals[+w];
+ })
+ x2 = x2.replace(/[0-9]/g, function (w) {
+ return self.options.numerals[+w];
+ })
+ }
+ return (neg ? '-' : '') + self.options.prefix + x1 + x2 + self.options.suffix;
+ }
+
+ // Robert Penner's easeOutExpo
+ function easeOutExpo(t, b, c, d) {
+ return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b;
+ }
+
+ function ensureNumber(n) {
+ return (typeof n === 'number' && !isNaN(n));
+ }
+
+ self.initialize = function () {
+ if (self.initialized) return true;
+
+ self.error = '';
+ self.d = (typeof target === 'string') ? document.getElementById(target) : target;
+ if (!self.d) {
+ self.error = '[CountUp] target is null or undefined'
+ return false;
+ }
+ self.startVal = Number(startVal);
+ self.endVal = Number(endVal);
+ // error checks
+ if (ensureNumber(self.startVal) && ensureNumber(self.endVal)) {
+ self.decimals = Math.max(0, decimals || 0);
+ self.dec = Math.pow(10, self.decimals);
+ self.duration = Number(duration) * 1000 || 2000;
+ self.countDown = (self.startVal > self.endVal);
+ self.frameVal = self.startVal;
+ self.initialized = true;
+ return true;
+ }
+ else {
+ self.error = '[CountUp] startVal (' + startVal + ') or endVal (' + endVal + ') is not a number';
+ return false;
+ }
+ };
+
+ // Print value to target
+ self.printValue = function (value) {
+ var result = self.options.formattingFn(value);
+
+ if (self.d.tagName === 'INPUT') {
+ this.d.value = result;
+ }
+ else if (self.d.tagName === 'text' || self.d.tagName === 'tspan') {
+ this.d.textContent = result;
+ }
+ else {
+ this.d.innerHTML = result;
+ }
+ };
+
+ self.count = function (timestamp) {
+
+ if (!self.startTime) {
+ self.startTime = timestamp;
+ }
+
+ self.timestamp = timestamp;
+ var progress = timestamp - self.startTime;
+ self.remaining = self.duration - progress;
+
+ // to ease or not to ease
+ if (self.options.useEasing) {
+ if (self.countDown) {
+ self.frameVal = self.startVal - self.options.easingFn(progress, 0, self.startVal - self.endVal, self.duration);
+ } else {
+ self.frameVal = self.options.easingFn(progress, self.startVal, self.endVal - self.startVal, self.duration);
+ }
+ } else {
+ if (self.countDown) {
+ self.frameVal = self.startVal - ((self.startVal - self.endVal) * (progress / self.duration));
+ } else {
+ self.frameVal = self.startVal + (self.endVal - self.startVal) * (progress / self.duration);
+ }
+ }
+
+ // don't go past endVal since progress can exceed duration in the last frame
+ if (self.countDown) {
+ self.frameVal = (self.frameVal < self.endVal) ? self.endVal : self.frameVal;
+ } else {
+ self.frameVal = (self.frameVal > self.endVal) ? self.endVal : self.frameVal;
+ }
+
+ // decimal
+ self.frameVal = Math.round(self.frameVal * self.dec) / self.dec;
+
+ // format and print value
+ self.printValue(self.frameVal);
+
+ // whether to continue
+ if (progress < self.duration) {
+ self.rAF = requestAnimationFrame(self.count);
+ } else {
+ if (self.callback) self.callback();
+ }
+ };
+ // start your animation
+ self.start = function (callback) {
+ if (!self.initialize()) return;
+ self.callback = callback;
+ self.rAF = requestAnimationFrame(self.count);
+ };
+ // toggles pause/resume animation
+ self.pauseResume = function () {
+ if (!self.paused) {
+ self.paused = true;
+ cancelAnimationFrame(self.rAF);
+ } else {
+ self.paused = false;
+ delete self.startTime;
+ self.duration = self.remaining;
+ self.startVal = self.frameVal;
+ requestAnimationFrame(self.count);
+ }
+ };
+ // reset to startVal so animation can be run again
+ self.reset = function () {
+ self.paused = false;
+ delete self.startTime;
+ self.initialized = false;
+ if (self.initialize()) {
+ cancelAnimationFrame(self.rAF);
+ self.printValue(self.startVal);
+ }
+ };
+ // pass a new endVal and start animation
+ self.update = function (newEndVal) {
+ if (!self.initialize()) return;
+ newEndVal = Number(newEndVal);
+ if (!ensureNumber(newEndVal)) {
+ self.error = '[CountUp] update() - new endVal is not a number: ' + newEndVal;
+ return;
+ }
+ self.error = '';
+ if (newEndVal === self.frameVal) return;
+ cancelAnimationFrame(self.rAF);
+ self.paused = false;
+ delete self.startTime;
+ self.startVal = self.frameVal;
+ self.endVal = newEndVal;
+ self.countDown = (self.startVal > self.endVal);
+ self.rAF = requestAnimationFrame(self.count);
+ };
+
+ // format startVal on initialization
+ if (self.initialize()) self.printValue(self.startVal);
+};
diff --git a/src/com/fr/plugin/widget/digitroll/web/widget.digitRoll.js b/src/com/fr/plugin/widget/digitroll/web/widget.digitRoll.js
new file mode 100644
index 0000000..d337dcd
--- /dev/null
+++ b/src/com/fr/plugin/widget/digitroll/web/widget.digitRoll.js
@@ -0,0 +1,43 @@
+;
+(function ($) {
+ FR.DigitRoll = FR.extend(FR.Widget, {
+ _defaultConfig: function () {
+ return $.extend(FR.DigitRoll.superclass._defaultConfig.apply(), {
+ baseName: 'fr-digitRoll',
+ baseClass: 'fr-digitRoll'
+ });
+ },
+
+ _init: function () {
+ FR.DigitRoll.superclass._init.apply(this, arguments);
+ FR.$defaultImport('/com/fr/plugin/widget/digitroll/web/countUp.js', 'js');
+ var o = this.options;
+ var element = this.element;
+ var widget = '';
+ var widgetId = $(widget).appendTo(element);
+ o.options.prefix = o.prefix;
+ o.options.suffix = o.suffix;
+ var digitRoll = new CountUp(widgetId, 0, o.value, 0/*o.decimals*/, o.duration, o.options);
+
+ var callback = function () {
+ $.ajax({
+ type: "POST",
+ url: o.widgetUrl,
+ success: function (res) {
+ var data = FR.jsonDecode(res);
+ o.data = data[0];
+ var v = data[0].value;
+ digitRoll.update(v);
+ }
+ });
+ };
+ if (!digitRoll.error) {
+ digitRoll.start(callback);
+ } else {
+ console.error(digitRoll.error);
+ }
+ }
+ });
+
+ $.shortcut("digitRoll", FR.DigitRoll);
+})(jQuery);
\ No newline at end of file