You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2022/06/29 16:23:12 UTC
[sis] branch geoapi-4.0 updated (d2ef60b78d -> 07618780bc)
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a change to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
from d2ef60b78d Add a "Referencing by grid cell indices" menu item. The available choices depend on the grid coverages currently shown in the widget.
new c778d9a0d5 Add visual separators.
new 07618780bc Fix erroneous pixel values sometime computed for a compressed tile using horizontal predictor.
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../java/org/apache/sis/gui/map/StatusBar.java | 17 +++++-
.../org/apache/sis/gui/referencing/MenuSync.java | 67 +++++++++++++---------
.../storage/inflater/HorizontalPredictor.java | 49 +++++++++-------
.../internal/storage/inflater/PixelChannel.java | 2 +-
.../storage/inflater/PredictorChannel.java | 34 +++++++----
.../internal/storage/inflater/package-info.java | 2 +-
6 files changed, 107 insertions(+), 64 deletions(-)
[sis] 02/02: Fix erroneous pixel values sometime computed for a compressed tile using horizontal predictor.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 07618780bcbff7aecd6ba7ad1683309e6ec2fe9c
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Jun 20 17:13:02 2022 +0200
Fix erroneous pixel values sometime computed for a compressed tile using horizontal predictor.
---
.../storage/inflater/HorizontalPredictor.java | 49 +++++++++++++---------
.../internal/storage/inflater/PixelChannel.java | 2 +-
.../storage/inflater/PredictorChannel.java | 34 ++++++++++-----
.../internal/storage/inflater/package-info.java | 2 +-
4 files changed, 54 insertions(+), 33 deletions(-)
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java
index 54da47096f..9f9ac83576 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/HorizontalPredictor.java
@@ -28,7 +28,7 @@ import org.apache.sis.internal.jdk9.JDK9;
* Values packed on 4, 2 or 1 bits are not yet supported.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -76,7 +76,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
private final int truncationMask;
/**
- * Creates a new predictor which will read compressed data from the given channel.
+ * Creates a new predictor which will read uncompressed data from the given channel.
* The {@link #setInputRegion(long, long)} method must be invoked after construction
* before a reading process can start.
*
@@ -141,7 +141,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
* unless the predictor needs more data for processing the last bytes.
*/
@Override
- protected final int uncompress(final ByteBuffer buffer, final int start) {
+ protected final int apply(final ByteBuffer buffer, final int start) {
final int limit = buffer.position();
final int limitMS = limit - sampleSizeM1; // Limit with enough space for last sample value.
/*
@@ -159,7 +159,8 @@ abstract class HorizontalPredictor extends PredictorChannel {
* at the end of the last invocation of this method. Note that this will perform no operation
* if the block above skipped fully the pixel in the first column.
*/
- position = applyOnFirst(buffer, position, Math.min(start + pixelStride, limitMS), position - start);
+ final int endOfFirstPixel = Math.min(Math.min(start + pixelStride, scanlineStride), limitMS);
+ position = applyOnFirst(buffer, position, endOfFirstPixel, position - start);
if ((column += position - start) >= scanlineStride) {
column = 0;
}
@@ -202,7 +203,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
/**
* Applies the predictor on the specified region of the given buffer, but using {@code savedValues}
* array as the source of previous values. This is used only for the first pixel in a new invocation
- * of {@link #uncompress(ByteBuffer, int)}.
+ * of {@link #apply(ByteBuffer, int)}.
*
* @param buffer the buffer on which to apply the predictor.
* @param position position of the first value to modify in the given buffer.
@@ -242,8 +243,8 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
private static final class Bytes extends HorizontalPredictor {
/**
- * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
- * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+ * The trailing values of previous invocation of {@link #apply(ByteBuffer, int)}.
+ * After each call to {@code apply(…)}, the last values in the buffer are saved
* for use by the next invocation. The buffer capacity is exactly one pixel.
*/
private final byte[] savedValues;
@@ -268,7 +269,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
/**
* Applies the predictor, using {@link #savedValues} as the source of previous values.
- * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
+ * Used only for the first pixel in a new invocation of {@link #apply(ByteBuffer, int)}.
*/
@Override
int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
@@ -299,8 +300,8 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
private static final class Shorts extends HorizontalPredictor {
/**
- * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
- * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+ * The trailing values of previous invocation of {@link #apply(ByteBuffer, int)}.
+ * After each call to {@code apply(…)}, the last values in the buffer are saved
* for use by the next invocation. The buffer capacity is exactly one pixel.
*/
private final short[] savedValues;
@@ -319,6 +320,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
@Override
void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+ assert (offset % Short.BYTES) == 0 : offset;
offset /= Short.BYTES;
System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
while (offset < savedValues.length) {
@@ -329,10 +331,11 @@ abstract class HorizontalPredictor extends PredictorChannel {
/**
* Applies the predictor, using {@link #savedValues} as the source of previous values.
- * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
+ * Used only for the first pixel in a new invocation of {@link #apply(ByteBuffer, int)}.
*/
@Override
int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+ assert (offset % Short.BYTES) == 0 : offset;
offset /= Short.BYTES;
while (position < end) {
buffer.putShort(position, (short) (buffer.getShort(position) + savedValues[offset++]));
@@ -361,8 +364,8 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
private static final class Integers extends HorizontalPredictor {
/**
- * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
- * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+ * The trailing values of previous invocation of {@link #apply(ByteBuffer, int)}.
+ * After each call to {@code apply(…)}, the last values in the buffer are saved
* for use by the next invocation. The buffer capacity is exactly one pixel.
*/
private final int[] savedValues;
@@ -381,6 +384,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
@Override
void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+ assert (offset % Integer.BYTES) == 0 : offset;
offset /= Integer.BYTES;
System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
while (offset < savedValues.length) {
@@ -391,10 +395,11 @@ abstract class HorizontalPredictor extends PredictorChannel {
/**
* Applies the predictor, using {@link #savedValues} as the source of previous values.
- * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
+ * Used only for the first pixel in a new invocation of {@link #apply(ByteBuffer, int)}.
*/
@Override
int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+ assert (offset % Integer.BYTES) == 0 : offset;
offset /= Integer.BYTES;
while (position < end) {
buffer.putInt(position, buffer.getInt(position) + savedValues[offset++]);
@@ -423,8 +428,8 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
private static final class Floats extends HorizontalPredictor {
/**
- * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
- * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+ * The trailing values of previous invocation of {@link #apply(ByteBuffer, int)}.
+ * After each call to {@code apply(…)}, the last values in the buffer are saved
* for use by the next invocation. The buffer capacity is exactly one pixel.
*/
private final float[] savedValues;
@@ -443,6 +448,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
@Override
void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+ assert (offset % Float.BYTES) == 0 : offset;
offset /= Float.BYTES;
System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
while (offset < savedValues.length) {
@@ -453,10 +459,11 @@ abstract class HorizontalPredictor extends PredictorChannel {
/**
* Applies the predictor, using {@link #savedValues} as the source of previous values.
- * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
+ * Used only for the first pixel in a new invocation of {@link #apply(ByteBuffer, int)}.
*/
@Override
int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+ assert (offset % Float.BYTES) == 0 : offset;
offset /= Float.BYTES;
while (position < end) {
buffer.putFloat(position, buffer.getFloat(position) + savedValues[offset++]);
@@ -485,8 +492,8 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
private static final class Doubles extends HorizontalPredictor {
/**
- * The trailing values of previous invocation of {@link #uncompress(ByteBuffer, int)}.
- * After each call to {@code uncompress(…)}, the last values in the buffer are saved
+ * The trailing values of previous invocation of {@link #apply(ByteBuffer, int)}.
+ * After each call to {@code apply(…)}, the last values in the buffer are saved
* for use by the next invocation. The buffer capacity is exactly one pixel.
*/
private final double[] savedValues;
@@ -505,6 +512,7 @@ abstract class HorizontalPredictor extends PredictorChannel {
*/
@Override
void saveLastPixel(final ByteBuffer buffer, int offset, int position) {
+ assert (offset % Double.BYTES) == 0 : offset;
offset /= Double.BYTES;
System.arraycopy(savedValues, savedValues.length - offset, savedValues, 0, offset);
while (offset < savedValues.length) {
@@ -515,10 +523,11 @@ abstract class HorizontalPredictor extends PredictorChannel {
/**
* Applies the predictor, using {@link #savedValues} as the source of previous values.
- * Used only for the first pixel in a new invocation of {@link #uncompress(ByteBuffer, int)}.
+ * Used only for the first pixel in a new invocation of {@link #apply(ByteBuffer, int)}.
*/
@Override
int applyOnFirst(final ByteBuffer buffer, int position, final int end, int offset) {
+ assert (offset % Double.BYTES) == 0 : offset;
offset /= Double.BYTES;
while (position < end) {
buffer.putDouble(position, buffer.getDouble(position) + savedValues[offset++]);
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java
index 5247497466..6f805f0d43 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PixelChannel.java
@@ -45,7 +45,7 @@ abstract class PixelChannel implements ReadableByteChannel {
}
/**
- * Prepares this channel for reading a new tile or a new band of a tile.
+ * Prepares this channel for reading a new tile or a new band of a planar image.
*
* @param start stream position where to start reading.
* @param byteCount number of bytes to read from the input.
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java
index e10fc7698b..9202be74cf 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/PredictorChannel.java
@@ -27,7 +27,7 @@ import org.apache.sis.internal.jdk9.JDK9;
* Implementation of a {@link Predictor} to be executed after decompression.
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
@@ -61,7 +61,7 @@ abstract class PredictorChannel extends PixelChannel {
}
/**
- * Prepares this predictor for reading a new tile or a new band of a tile.
+ * Prepares this predictor for reading a new tile or a new band of a planar image.
*
* @param start stream position where to start reading.
* @param byteCount number of bytes to read from the input.
@@ -82,10 +82,10 @@ abstract class PredictorChannel extends PixelChannel {
* @return position after the last sample value processed. Should be {@link ByteBuffer#position()},
* unless the predictor needs more data for processing the last bytes.
*/
- protected abstract int uncompress(ByteBuffer buffer, int start);
+ protected abstract int apply(ByteBuffer buffer, int start);
/**
- * Decompresses some bytes from the {@linkplain #input input} into the given destination buffer.
+ * Decompresses some bytes from the {@linkplain #input} into the given destination buffer.
*
* @param target the buffer into which bytes are to be transferred.
* @return the number of bytes read, or -1 if end-of-stream.
@@ -95,19 +95,31 @@ abstract class PredictorChannel extends PixelChannel {
public int read(final ByteBuffer target) throws IOException {
final int start = target.position();
if (deferredCount != 0) {
+ /*
+ * If we had some bytes from the previous invocation that `apply(…)` could not use,
+ * append those bytes in the target buffer before to read new bytes from the channel.
+ * We may not be able to append all bytes.
+ */
final int n = Math.min(deferredCount, target.remaining());
target.put(deferred, 0, n);
- deferredCount -= n;
+ System.arraycopy(deferred, n, deferred, 0, deferredCount -= n);
}
input.read(target);
- final int end = uncompress(target, start);
- deferredCount = target.position() - end;
- if (deferredCount != 0) {
- if (deferredCount > deferred.length) {
- deferred = new byte[deferredCount];
+ final int end = apply(target, start);
+ final int remaining = target.position() - end;
+ if (remaining != 0) {
+ /*
+ * If there is some bytes that `apply(…)` could not use, save those bytes for next
+ * invocation of this `read(…)` method. Those bytes may need to be appended after
+ * bytes that previous code block has not been able to put in the target buffer.
+ */
+ final int length = deferredCount + remaining;
+ if (length > deferred.length) {
+ deferred = new byte[length];
}
- JDK9.get(target, end, deferred, 0, deferredCount);
+ JDK9.get(target, end, deferred, deferredCount, remaining);
target.position(end);
+ deferredCount = length;
}
return end - start;
}
diff --git a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java
index c975f32d84..7ff1f2a7af 100644
--- a/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java
+++ b/storage/sis-geotiff/src/main/java/org/apache/sis/internal/storage/inflater/package-info.java
@@ -42,7 +42,7 @@
* </dl>
*
* @author Martin Desruisseaux (Geomatys)
- * @version 1.2
+ * @version 1.3
* @since 1.1
* @module
*/
[sis] 01/02: Add visual separators.
Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit c778d9a0d5606cbe5611b72653d6380a287f6ade
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Wed Jun 29 00:02:22 2022 +0200
Add visual separators.
---
.../java/org/apache/sis/gui/map/StatusBar.java | 17 +++++-
.../org/apache/sis/gui/referencing/MenuSync.java | 67 +++++++++++++---------
2 files changed, 53 insertions(+), 31 deletions(-)
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
index 5050a22ab1..7e62bffa90 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/map/StatusBar.java
@@ -29,6 +29,7 @@ import javax.measure.Unit;
import javafx.geometry.Pos;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
+import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.paint.Color;
import javafx.scene.layout.HBox;
@@ -41,6 +42,7 @@ import javafx.scene.control.Tooltip;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Separator;
import javafx.scene.input.MouseEvent;
import javafx.scene.text.TextAlignment;
import javafx.event.EventHandler;
@@ -147,6 +149,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent> {
/**
* The container of controls making the status bar.
+ * Contains {@link #message}, {@link #position} and {@link #sampleValues}.
*
* @see #getView()
*/
@@ -494,7 +497,7 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent> {
position.setAlignment(Pos.CENTER_RIGHT);
position.setTextAlignment(TextAlignment.RIGHT);
- view = new HBox(18, message, position);
+ view = new HBox(6, message, position);
view.setPadding(PADDING);
view.setAlignment(Pos.CENTER_RIGHT);
/*
@@ -1545,11 +1548,19 @@ public class StatusBar extends Widget implements EventHandler<MouseEvent> {
sampleValues.setMaxWidth(Label.USE_PREF_SIZE);
}
if (c.lastIndexOf(sampleValues) < 0) {
- c.add(sampleValues);
+ final Separator separator = new Separator(Orientation.VERTICAL);
+ c.addAll(separator, sampleValues);
}
} else if (sampleValues != null) {
sampleValues.setText(null);
- c.remove(sampleValues);
+ int i = c.lastIndexOf(sampleValues);
+ if (i >= 0) {
+ c.remove(i);
+ if (--i >= 0) {
+ final Node last = c.remove(i);
+ assert last instanceof Separator : last;
+ }
+ }
}
isSampleValuesVisible = visible;
}
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
index ee9b479a97..41bb226624 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/MenuSync.java
@@ -17,12 +17,10 @@
package org.apache.sis.gui.referencing;
import java.util.List;
-import java.util.Arrays;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Locale;
-import java.util.Map;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -30,6 +28,7 @@ import javafx.event.EventHandler;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioMenuItem;
+import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.ToggleGroup;
import org.opengis.referencing.ReferenceSystem;
import org.opengis.referencing.crs.DerivedCRS;
@@ -134,10 +133,13 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements Ev
/*
* Root menu. The list of recent reference system is dynamic and will change according user actions.
*/
- final MenuItem[] items = new MenuItem[systems.size()];
+ final List<MenuItem> items = new ArrayList<>(systems.size() + 1);
final Locale locale = action.owner().locale;
- for (int i=0; i<items.length; i++) {
- items[i] = createItem(systems.get(i), locale);
+ for (final ReferenceSystem system : systems) {
+ if (system == RecentReferenceSystems.OTHER) {
+ items.add(new SeparatorMenuItem());
+ }
+ items.add(createItem(system, locale));
}
rootMenus.addAll(items);
initialize();
@@ -248,9 +250,11 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements Ev
* Must be invoked after removing a menu item for avoiding memory leak.
*/
private static void dispose(final MenuItem item) {
- item.setOnAction(null);
- if (item instanceof RadioMenuItem) {
- ((RadioMenuItem) item).setToggleGroup(null);
+ if (item != null) {
+ item.setOnAction(null);
+ if (item instanceof RadioMenuItem) {
+ ((RadioMenuItem) item).setToggleGroup(null);
+ }
}
}
@@ -265,12 +269,15 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements Ev
/*
* Build a map of current menu items. Key are CRS objects.
*/
- final var subMenus = new ArrayList<Menu>();
- final Map<Object,MenuItem> mapping = new IdentityHashMap<>();
+ SeparatorMenuItem separator = null;
+ final var subMenus = new ArrayList<Menu>(2);
+ final var mapping = new IdentityHashMap<Object,MenuItem>(10);
for (final Iterator<MenuItem> it = rootMenus.iterator(); it.hasNext();) {
final MenuItem item = it.next();
if (item instanceof Menu) {
subMenus.add((Menu) item);
+ } else if (item instanceof SeparatorMenuItem) {
+ separator = (SeparatorMenuItem) item; // Should have only one.
} else if (mapping.putIfAbsent(item.getProperties().get(REFERENCE_SYSTEM_KEY), item) != null) {
it.remove(); // Remove duplicated item. Should never happen, but we are paranoiac.
dispose(item);
@@ -282,12 +289,12 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements Ev
* loop, the map will contain only menu items for CRS that are no longer in the list of CRS to offer.
*/
final int newCount = recentSystems.size();
- final MenuItem[] items = new MenuItem[newCount + subMenus.size()];
- for (int i=0; i<newCount; i++) {
- Object key = recentSystems.get(i);
+ final var items = new ArrayList<MenuItem>(newCount + 4);
+ for (Object key : recentSystems) {
if (key == RecentReferenceSystems.OTHER) key = CHOOSER;
- items[i] = mapping.remove(key);
+ items.add(mapping.remove(key)); // May be null.
}
+ dispose(mapping.remove(CHOOSER)); // Safety for avoiding AssertionError in block below.
/*
* Previous loop took all items that could be reused as-is. Now search for all items that are new.
* For each new item to create, recycle an arbitrary `mapping` element (in any order) if some exist.
@@ -297,38 +304,42 @@ final class MenuSync extends SimpleObjectProperty<ReferenceSystem> implements Ev
final Iterator<MenuItem> recycle = mapping.values().iterator();
final Locale locale = action.owner().locale;
for (int i=0; i<newCount; i++) {
- if (items[i] == null) {
- MenuItem item = null;
+ if (items.get(i) == null) {
+ final MenuItem item;
final ReferenceSystem system = recentSystems.get(i);
if (system != RecentReferenceSystems.OTHER && recycle.hasNext()) {
item = recycle.next();
- recycle.remove();
- if (item instanceof RadioMenuItem) {
- item.setText(IdentifiedObjects.getDisplayName(system, locale));
- item.getProperties().put(REFERENCE_SYSTEM_KEY, system);
- }
- }
- if (item == null) {
+ assert item instanceof RadioMenuItem : item;
+ item.setText(IdentifiedObjects.getDisplayName(system, locale));
+ item.getProperties().put(REFERENCE_SYSTEM_KEY, system);
+ } else {
item = createItem(system, locale);
}
if (selected != null && system == selected) {
((RadioMenuItem) item).setSelected(true); // ClassCastException should never occur here.
selected = null;
}
- items[i] = item;
+ items.set(i, item);
}
}
/*
* If there is any item left, we must remove them from the ToggleGroup for avoiding memory leak.
- * The sub-menus (if any) are appended last with no change.
+ * Add separator before "Other…" item. The sub-menus (if any) are appended last with no change.
*/
while (recycle.hasNext()) {
dispose(recycle.next());
}
- for (int i=newCount; i<items.length; i++) {
- items[i] = subMenus.get(i - newCount);
+ for (int i = items.size(); --i >= 0;) {
+ if (items.get(i).getClass() == MenuItem.class) {
+ if (separator == null) {
+ separator = new SeparatorMenuItem();
+ }
+ items.add(i, separator);
+ break;
+ }
}
- GUIUtilities.copyAsDiff(Arrays.asList(items), rootMenus);
+ items.addAll(subMenus);
+ GUIUtilities.copyAsDiff(items, rootMenus);
/*
* If we had no previously selected item, selects it now.
*/