From 307a679ac0d3f365ff383bb96fe43193357b211d Mon Sep 17 00:00:00 2001
From: Steven Luke <97394870+sluke-nuix@users.noreply.github.com>
Date: Thu, 7 Sep 2023 08:30:13 -0400
Subject: [PATCH 1/5] =?UTF-8?q?Expanded=20visibility=20for=20filtering=20o?=
=?UTF-8?q?n=20dynamic=20table=20to=20allow=20editing=20them=E2=80=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../com/nuix/nx/models/DynamicTableModel.java | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java b/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java
index 896921e..18b5341 100644
--- a/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java
+++ b/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java
@@ -188,7 +188,7 @@ public Class> getColumnClass(int columnIndex) {
/***
* Notify listeners that changes were made
*/
- private void notifyChanged(){
+ protected void notifyChanged(){
if(changeListener != null){
changeListener.dataChanged();
}
@@ -206,7 +206,7 @@ private void notifyChanged(){
* while building a new index mapping. Once a new mapping has been constructed the associated DynamicTable is informed that data
* has changed and it will re-populate.
*/
- private void applyFiltering(){
+ protected void applyFiltering(){
Map
model for the JSlider component
* @return this CustomTabPanel instance to allow method chaining
* @throws Exception if the identifier has already been used
*/
protected CustomTabPanel buildGenericSlider(String identifier, String controlLabel,
BoundedRangeModel model,
- Consumer
+ * This control also contains a JSpinner control that displays and allows direct modification of the slider
+ * value. The spinner will have the same range as the slider, and its step size will be such that 10
+ * steps reaches the maximum value. The JSpinner control can be retrieved from the panel using the provided
+ * identifier with ".Spinner" appended to it:
+ *
+ * panel = panel.appendSlider(identifier, label, 0.0, 0.0, 1.0); + * JSpinner spinner = (JSpinner)panel.getControl(identifier + ".Spinner"); + * SpinnerNumberModel = spinner.getModel(); + *+ *
+ * Internally, the JSpinner uses the JSlider's data model to store data. Values will be kept the same between + * the but only the JSlider's model will be guaranteed to get all events from changes to both the slider and + * the spinner. It is for this reason the JSlider's model is the preferred model to interact with. + *
* @param identifier The unique identifier for this control, used to modify the control or get its result value * @param controlLabel String to display in the UI to label this control * @param initialValue The initial position for slider and resulting value @@ -595,14 +605,74 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel, doubl DoubleBoundedRangeModel model = new DoubleBoundedRangeModel(initialValue, 0.0, min, max,5); model.setValue(initialValue); - long integral = Double.valueOf(max).longValue(); - int integralLength = String.valueOf(integral).length(); - int decimalLength = 6; // Period plus 5 decimal places - String displayFormat = "%" + (integralLength + decimalLength) + ".5f"; + SpinnerNumberModel spinnerModel = new SpinnerNumberModel(initialValue, min, max, (max/10)) { + @Override public Comparable> getMaximum() { + return model.getMaximumAsDouble(); + } - return buildGenericSlider(identifier, controlLabel, model, label -> { - label.setText(String.format(displayFormat, model.getValueAsDouble())); - }); + @Override public Comparable> getMinimum() { + return model.getMinimumAsDouble(); + } + + @Override public Object getNextValue() { + double nextCandidate = model.getValueAsDouble() + getStepSize().doubleValue(); + if(Double.compare(nextCandidate, model.getMaximumAsDouble()) > 0) { + return null; + } else { + return nextCandidate; + } + } + + @Override public Number getNumber() { + return model.getValueAsDouble(); + } + + @Override public Object getPreviousValue() { + double nextCandidate = model.getValueAsDouble() - getStepSize().doubleValue(); + if(Double.compare(nextCandidate, model.getMinimumAsDouble()) < 0) { + return null; + } else { + return nextCandidate; + } + } + + @Override public Object getValue() { + return getNumber(); + } + + @Override public void setMinimum(Comparable> minimum) { + if(null == minimum || !Number.class.isAssignableFrom(minimum.getClass())) { + throw new IllegalArgumentException("The spinner's minimum must be a non-null Number."); + } + + model.setMinimum(((Number)minimum).doubleValue()); + fireStateChanged(); + } + + @Override public void setMaximum(Comparable> maximum) { + if(null == maximum || !Number.class.isAssignableFrom(maximum.getClass())) { + throw new IllegalArgumentException("The spinner's minimum must be a non-null Number."); + } + + model.setMaximum(((Number)maximum).doubleValue()); + fireStateChanged(); + } + + @Override public void setValue(Object value) { + if(null == value || !Number.class.isAssignableFrom(value.getClass())) { + throw new IllegalArgumentException("The spinner's value must be a non-null Number."); + } + + Double valueAsDouble = ((Number)value).doubleValue(); + if(valueAsDouble.compareTo(model.getMaximumAsDouble()) > 0) valueAsDouble = model.getMaximumAsDouble(); + else if (valueAsDouble.compareTo(model.getMinimumAsDouble()) < 0) valueAsDouble = model.getMinimumAsDouble(); + + model.setValue(valueAsDouble); + fireStateChanged(); + } + }; + + return buildGenericSlider(identifier, controlLabel, model, spinnerModel); } /** @@ -648,6 +718,22 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel, doubl * JSlider slider = (JSlider)panel.getControl(identifier); * BoundedRangeModel model = slider.getModel(); * + *
+ * This control also contains a JSpinner control that displays and allows direct modification of the slider
+ * value. The spinner will have the same range as the slider, and its step size will be such that 10
+ * steps reaches the maximum value. The JSpinner control can be retrieved from the panel using the provided
+ * identifier with ".Spinner" appended to it:
+ *
+ * panel = panel.appendSlider(identifier, label, 0, 0, 1); + * JSpinner spinner = (JSpinner)panel.getControl(identifier + ".Spinner"); + * SpinnerNumberModel = spinner.getModel(); + *+ *
+ * Internally, the JSpinner uses the JSlider's data model to store data. Values will be kept the same between + * the but only the JSlider's model will be guaranteed to get all events from changes to both the slider and + * the spinner. It is for this reason the JSlider's model is the preferred model to interact with. + *
* @param identifier The unique identifier for this control, used to modify the control or get its result value * @param controlLabel String to display in the UI to label this control * @param initialValue The initial position for slider and resulting value @@ -659,13 +745,76 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel, doubl public CustomTabPanel appendSlider(String identifier, String controlLabel, int initialValue, int min, int max) throws Exception { DefaultBoundedRangeModel model = new DefaultBoundedRangeModel(initialValue, 0, min, max); model.setValue(initialValue); - int integralLength = String.valueOf(max).length(); + model.setValue(initialValue); - String displayFormat = "%" + integralLength + "d"; + SpinnerNumberModel spinnerModel = new SpinnerNumberModel(initialValue, min, max, (max/10)) { + @Override public Comparable> getMaximum() { + return model.getMaximum(); + } - return buildGenericSlider(identifier, controlLabel, model, label -> { - label.setText(String.format(displayFormat, model.getValue())); - }); + @Override public Comparable> getMinimum() { + return model.getMinimum(); + } + + @Override public Object getNextValue() { + double nextCandidate = model.getValue() + getStepSize().intValue(); + if(Double.compare(nextCandidate, model.getMaximum()) > 0) { + return null; + } else { + return nextCandidate; + } + } + + @Override public Number getNumber() { + return model.getValue(); + } + + @Override public Object getPreviousValue() { + double nextCandidate = model.getValue() - getStepSize().intValue(); + if(Double.compare(nextCandidate, model.getMinimum()) < 0) { + return null; + } else { + return nextCandidate; + } + } + + @Override public Object getValue() { + return getNumber(); + } + + @Override public void setMinimum(Comparable> minimum) { + if(null == minimum || !Number.class.isAssignableFrom(minimum.getClass())) { + throw new IllegalArgumentException("The spinner's minimum must be a non-null Number."); + } + + model.setMinimum(((Number)minimum).intValue()); + fireStateChanged(); + } + + @Override public void setMaximum(Comparable> maximum) { + if(null == maximum || !Number.class.isAssignableFrom(maximum.getClass())) { + throw new IllegalArgumentException("The spinner's maximum must be a non-null Number."); + } + + model.setMaximum(((Number)maximum).intValue()); + fireStateChanged(); + } + + @Override public void setValue(Object value) { + if(null == value || !Number.class.isAssignableFrom(value.getClass())) { + throw new IllegalArgumentException("The spinner's value must be a non-null Number."); + } + + Integer valueAsInteger = ((Number)value).intValue(); + if(valueAsInteger.compareTo(model.getMaximum()) > 0) valueAsInteger = model.getMaximum(); + else if (valueAsInteger.compareTo(model.getMinimum()) < 0) valueAsInteger = model.getMinimum(); + + model.setValue(valueAsInteger); + fireStateChanged(); + } + }; + + return buildGenericSlider(identifier, controlLabel, model, spinnerModel); } /** @@ -720,6 +869,24 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel) throw return appendSlider(identifier, controlLabel, 50, 0, 100); } + protected JSpinner buildGenericSpinner(String identifier, SpinnerNumberModel spinnerModel) throws Exception { + JSpinner component = new JSpinner(); + //component.setValue(initialValue); + double preferredHeight = component.getPreferredSize().getHeight(); + component.setPreferredSize(new Dimension(150, (int) preferredHeight)); + component.setModel(spinnerModel); + + trackComponent(identifier, component); + + return component; + } + + public CustomTabPanel appendSpinner(String identifier, String controlLabel, double initialValue, double min, double max, double step) throws Exception { + JSpinner component = buildGenericSpinner(identifier, new SpinnerNumberModel(initialValue, min, max, step)); + addBasicLabelledComponent(controlLabel, component, false, false); + return this; + } + /*** * Creates a up/down number picker control (known in Java as a Spinner). * @param identifier The unique identifier for this control. @@ -732,13 +899,8 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel) throw * @throws Exception May throw an exception if the provided identifier has already been used. */ public CustomTabPanel appendSpinner(String identifier, String controlLabel, int initialValue, int min, int max, int step) throws Exception { - JSpinner component = new JSpinner(); - component.setValue(initialValue); - double preferredHeight = component.getPreferredSize().getHeight(); - component.setPreferredSize(new Dimension(150, (int) preferredHeight)); - component.setModel(new SpinnerNumberModel(initialValue, min, max, step)); + JSpinner component = buildGenericSpinner(identifier, new SpinnerNumberModel(initialValue, min, max, step)); addBasicLabelledComponent(controlLabel, component, false, false); - trackComponent(identifier, component); return this; } From b6df66e25e5c524dd888312272d5d62729327a7a Mon Sep 17 00:00:00 2001 From: Steven Luke <97394870+sluke-nuix@users.noreply.github.com> Date: Thu, 7 Sep 2023 08:53:31 -0400 Subject: [PATCH 3/5] Ensure JSlider updates inform the linked JSpinner --- Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java | 2 ++ Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java b/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java index 58c5f4d..d59e3bc 100644 --- a/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java +++ b/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java @@ -671,6 +671,7 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel, doubl fireStateChanged(); } }; + model.addChangeListener(change -> spinnerModel.setValue(model.getValueAsDouble())); return buildGenericSlider(identifier, controlLabel, model, spinnerModel); } @@ -813,6 +814,7 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel, int i fireStateChanged(); } }; + model.addChangeListener(change -> spinnerModel.setValue(model.getValue())); return buildGenericSlider(identifier, controlLabel, model, spinnerModel); } diff --git a/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java b/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java index 18b5341..f9a85bd 100644 --- a/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java +++ b/Nx/src/main/java/com/nuix/nx/models/DynamicTableModel.java @@ -200,7 +200,7 @@ protected void notifyChanged(){ /*** * Filter the displayed records. When a method such as {@link #getValueAt(int, int)} is called by DynamicTable, the given method will use - * the index mapping stored in {@value #filterMap} to determine for the given display index what item to fetch from + * the index mapping stored in {@link #filterMap} to determine for the given display index what item to fetch from * the actual underlying full collection of records. The act of applying filtering is therefore really just building * a modified mapping. This method takes the filter expression that has been provided and iteratively apply it to each record * while building a new index mapping. Once a new mapping has been constructed the associated DynamicTable is informed that data From 745767c01e0f16b129a5913919e9e419dffe2793 Mon Sep 17 00:00:00 2001 From: Steven Luke <97394870+sluke-nuix@users.noreply.github.com> Date: Thu, 7 Sep 2023 08:55:57 -0400 Subject: [PATCH 4/5] Fixed label bug with spinner --- .../java/com/nuix/nx/dialogs/CustomTabPanel.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java b/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java index d59e3bc..79c9f1b 100644 --- a/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java +++ b/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java @@ -548,7 +548,7 @@ protected CustomTabPanel buildGenericSlider(String identifier, String controlLab JSlider component = new JSlider(JSlider.HORIZONTAL); component.setModel(model); - JSpinner valueDisplay = buildGenericSpinner(identifier + ".Spinner", spinnerModel); + JSpinner valueDisplay = buildGenericSpinner(identifier + ".Spinner", spinnerModel, null); GridBagConstraints c = makeLabelConstraintsForNextRow(); addComponent(makeComponentLabel(controlLabel),c); @@ -871,21 +871,22 @@ public CustomTabPanel appendSlider(String identifier, String controlLabel) throw return appendSlider(identifier, controlLabel, 50, 0, 100); } - protected JSpinner buildGenericSpinner(String identifier, SpinnerNumberModel spinnerModel) throws Exception { + protected JSpinner buildGenericSpinner(String identifier, SpinnerNumberModel spinnerModel, String controlLabel) throws Exception { JSpinner component = new JSpinner(); //component.setValue(initialValue); double preferredHeight = component.getPreferredSize().getHeight(); component.setPreferredSize(new Dimension(150, (int) preferredHeight)); component.setModel(spinnerModel); - + if(null != controlLabel) { + addBasicLabelledComponent(controlLabel, component, false, false); + } trackComponent(identifier, component); return component; } public CustomTabPanel appendSpinner(String identifier, String controlLabel, double initialValue, double min, double max, double step) throws Exception { - JSpinner component = buildGenericSpinner(identifier, new SpinnerNumberModel(initialValue, min, max, step)); - addBasicLabelledComponent(controlLabel, component, false, false); + JSpinner component = buildGenericSpinner(identifier, new SpinnerNumberModel(initialValue, min, max, step), controlLabel); return this; } @@ -901,8 +902,7 @@ public CustomTabPanel appendSpinner(String identifier, String controlLabel, doub * @throws Exception May throw an exception if the provided identifier has already been used. */ public CustomTabPanel appendSpinner(String identifier, String controlLabel, int initialValue, int min, int max, int step) throws Exception { - JSpinner component = buildGenericSpinner(identifier, new SpinnerNumberModel(initialValue, min, max, step)); - addBasicLabelledComponent(controlLabel, component, false, false); + JSpinner component = buildGenericSpinner(identifier, new SpinnerNumberModel(initialValue, min, max, step), controlLabel); return this; } From 5a77d0c2d6865316481bb4a0ec195310dfef4c83 Mon Sep 17 00:00:00 2001 From: Steven Luke <97394870+sluke-nuix@users.noreply.github.com> Date: Thu, 7 Sep 2023 08:58:02 -0400 Subject: [PATCH 5/5] Allow model to be passed into StringList --- Nx/src/main/java/com/nuix/nx/controls/StringList.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Nx/src/main/java/com/nuix/nx/controls/StringList.java b/Nx/src/main/java/com/nuix/nx/controls/StringList.java index 62a453c..b62f865 100644 --- a/Nx/src/main/java/com/nuix/nx/controls/StringList.java +++ b/Nx/src/main/java/com/nuix/nx/controls/StringList.java @@ -46,9 +46,15 @@ public class StringList extends JPanel { private JButton btnImportFile; private JPanel buttonLayoutPanel; private JTable table; - private StringListTableModel model = new StringListTableModel(); + private StringListTableModel model; public StringList() { + this(new StringListTableModel()); + } + + public StringList(StringListTableModel tableModel) { + model = tableModel; + GridBagLayout gridBagLayout = new GridBagLayout(); gridBagLayout.columnWidths = new int[]{0, 0}; gridBagLayout.rowHeights = new int[]{0, 0, 0};