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 tempFilterMap = new HashMap(); DynamicTableFilterProvider filterProviderToUse = null; @@ -315,7 +315,16 @@ public void setFilter(String filter){ applyFiltering(); notifyChanged(); } - + + /*** + * Get the current filter string. + * @return The current string filtering values in the table. This may be an empty string if the table is not being + * filtered. + */ + public String getFilter() { + return this.filterExpression; + } + /*** * Used to determine whether a given record is checked in the table * @param record The record to check for From a976f532831865526f9bfd7023160e2b0bd361a1 Mon Sep 17 00:00:00 2001 From: Steven Luke <97394870+sluke-nuix@users.noreply.github.com> Date: Thu, 7 Sep 2023 08:51:02 -0400 Subject: [PATCH 2/5] Add spinner to JSlider control --- .../com/nuix/nx/dialogs/CustomTabPanel.java | 224 +++++++++++++++--- 1 file changed, 193 insertions(+), 31 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 9cb19bc..58c5f4d 100644 --- a/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java +++ b/Nx/src/main/java/com/nuix/nx/dialogs/CustomTabPanel.java @@ -534,27 +534,21 @@ public ButtonRow appendButtonRow(String identifier) { *

* @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 model The {@link BoundedRangeModel} which controls and stores the value and its range limits - * @param displayAdapter A {@link Consumer} which can take a JLabel and display the model's current value in it. - * The consumer will need its own reference to the model, as it will not get the model from - * this callback. + * @param model The {@link BoundedRangeModel} which controls and stores the value and its range limits used on the + * JSlider control + * @param spinnerModel The {@link SpinnerNumberModel} which controls and stores the value in the JSpinner that will + * get created. This model should ensure the data is always in sync with the values in the + * 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 displayAdapter) throws Exception { + SpinnerNumberModel spinnerModel) throws Exception { JSlider component = new JSlider(JSlider.HORIZONTAL); component.setModel(model); - JLabel valueDisplay = new JLabel(); - valueDisplay.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); - valueDisplay.setBorder(BorderFactory.createLineBorder(Color.BLACK)); - displayAdapter.accept(valueDisplay); - - model.addChangeListener(event -> { - displayAdapter.accept(valueDisplay); - }); + JSpinner valueDisplay = buildGenericSpinner(identifier + ".Spinner", spinnerModel); GridBagConstraints c = makeLabelConstraintsForNextRow(); addComponent(makeComponentLabel(controlLabel),c); @@ -583,6 +577,22 @@ protected CustomTabPanel buildGenericSlider(String identifier, String controlLab * JSlider slider = (JSlider)panel.getControl(identifier); * DoubleBoundedRangeModel 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, 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};