From 02390c2ba9f8b518c1d8f93d15b5efe97399750e Mon Sep 17 00:00:00 2001 From: Wiehann Matthysen Date: Mon, 12 Dec 2016 19:34:07 +0200 Subject: [PATCH 1/3] Moved SelectionHighlightController. The SelectionHighlightController is a useful class that can be used in combination with the ScreenSelector class. Thus, changed the ScreenSelection example application by pulling out the SelectionHighlightController and moving it to the gov.nasa.worldwindx.examples.util package. --- .../worldwindx/examples/ScreenSelection.java | 96 ---------------- .../util/SelectionHighlightController.java | 107 ++++++++++++++++++ 2 files changed, 107 insertions(+), 96 deletions(-) create mode 100644 src/gov/nasa/worldwindx/examples/util/SelectionHighlightController.java diff --git a/src/gov/nasa/worldwindx/examples/ScreenSelection.java b/src/gov/nasa/worldwindx/examples/ScreenSelection.java index 7f1785d826..5ef4122c4d 100644 --- a/src/gov/nasa/worldwindx/examples/ScreenSelection.java +++ b/src/gov/nasa/worldwindx/examples/ScreenSelection.java @@ -111,102 +111,6 @@ public void actionPerformed(ActionEvent actionEvent) } } - /** - * Extends HighlightController to add the capability to highlight objects selected by a ScreenSelector. This tracks - * objects highlighted by both cursor rollover events and screen selection changes, and ensures that objects stay - * highlighted when they are either under cursor or in the ScreenSelector's selection rectangle. - */ - protected static class SelectionHighlightController extends HighlightController implements MessageListener - { - protected ScreenSelector screenSelector; - protected List lastBoxHighlightObjects = new ArrayList(); - - public SelectionHighlightController(WorldWindow wwd, ScreenSelector screenSelector) - { - super(wwd, SelectEvent.ROLLOVER); - - this.screenSelector = screenSelector; - this.screenSelector.addMessageListener(this); - } - - @Override - public void dispose() - { - super.dispose(); - - this.screenSelector.removeMessageListener(this); - } - - public void onMessage(Message msg) - { - try - { - // Update the list of highlighted objects whenever the ScreenSelector's selection changes. We capture - // both the selection started and selection changed events to ensure that we clear the list of selected - // objects when the selection begins or re-starts, as well as update the list when it changes. - if (msg.getName().equals(ScreenSelector.SELECTION_STARTED) - || msg.getName().equals(ScreenSelector.SELECTION_CHANGED)) - { - this.highlightSelectedObjects(this.screenSelector.getSelectedObjects()); - } - } - catch (Exception e) - { - // Wrap the handler in a try/catch to keep exceptions from bubbling up - Util.getLogger().warning(e.getMessage() != null ? e.getMessage() : e.toString()); - } - } - - protected void highlight(Object o) - { - // Determine if the highlighted object under the cursor has changed, but should remain highlighted because - // its in the selection box. In this case we assign the highlighted object under the cursor to null and - // return, and thereby avoid changing the highlight state of objects still highlighted by the selection box. - if (this.lastHighlightObject != o && this.lastBoxHighlightObjects.contains(this.lastHighlightObject)) - { - this.lastHighlightObject = null; - return; - } - - super.highlight(o); - } - - protected void highlightSelectedObjects(List list) - { - if (this.lastBoxHighlightObjects.equals(list)) - return; // same thing selected - - // Turn off highlight for the last set of selected objects, if any. Since one of these objects may still be - // highlighted due to a cursor rollover, we detect that object and avoid changing its highlight state. - for (Highlightable h : this.lastBoxHighlightObjects) - { - if (h != this.lastHighlightObject) - h.setHighlighted(false); - } - this.lastBoxHighlightObjects.clear(); - - if (list != null) - { - // Turn on highlight if object selected. - for (Object o : list) - { - if (o instanceof Highlightable) - { - ((Highlightable) o).setHighlighted(true); - this.lastBoxHighlightObjects.add((Highlightable) o); - } - } - } - - // We've potentially changed the highlight state of one or more objects. Request that the world window - // redraw itself in order to refresh these object's display. This is necessary because changes in the - // objects in the pick rectangle do not necessarily correspond to mouse movements. For example, the pick - // rectangle may be cleared when the user releases the mouse button at the end of a drag. In this case, - // there's no mouse movement to cause an automatic redraw. - this.wwd.redraw(); - } - } - public static void main(String[] args) { start("World Wind Screen Selection", AppFrame.class); diff --git a/src/gov/nasa/worldwindx/examples/util/SelectionHighlightController.java b/src/gov/nasa/worldwindx/examples/util/SelectionHighlightController.java new file mode 100644 index 0000000000..e50e695906 --- /dev/null +++ b/src/gov/nasa/worldwindx/examples/util/SelectionHighlightController.java @@ -0,0 +1,107 @@ +package gov.nasa.worldwindx.examples.util; + +import gov.nasa.worldwind.WorldWindow; +import gov.nasa.worldwind.event.Message; +import gov.nasa.worldwind.event.MessageListener; +import gov.nasa.worldwind.event.SelectEvent; +import gov.nasa.worldwind.render.Highlightable; +import gov.nasa.worldwindx.applications.worldwindow.util.Util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Extends HighlightController to add the capability to highlight objects selected by a ScreenSelector. This tracks + * objects highlighted by both cursor rollover events and screen selection changes, and ensures that objects stay + * highlighted when they are either under cursor or in the ScreenSelector's selection rectangle. + */ +public class SelectionHighlightController extends HighlightController implements MessageListener +{ + protected ScreenSelector screenSelector; + protected List lastBoxHighlightObjects = new ArrayList(); + + public SelectionHighlightController(WorldWindow wwd, ScreenSelector screenSelector) + { + super(wwd, SelectEvent.ROLLOVER); + + this.screenSelector = screenSelector; + this.screenSelector.addMessageListener(this); + } + + @Override + public void dispose() + { + super.dispose(); + + this.screenSelector.removeMessageListener(this); + } + + public void onMessage(Message msg) + { + try + { + // Update the list of highlighted objects whenever the ScreenSelector's selection changes. We capture + // both the selection started and selection changed events to ensure that we clear the list of selected + // objects when the selection begins or re-starts, as well as update the list when it changes. + if (msg.getName().equals(ScreenSelector.SELECTION_STARTED) + || msg.getName().equals(ScreenSelector.SELECTION_CHANGED)) + { + this.highlightSelectedObjects(this.screenSelector.getSelectedObjects()); + } + } + catch (Exception e) + { + // Wrap the handler in a try/catch to keep exceptions from bubbling up + Util.getLogger().warning(e.getMessage() != null ? e.getMessage() : e.toString()); + } + } + + protected void highlight(Object o) + { + // Determine if the highlighted object under the cursor has changed, but should remain highlighted because + // its in the selection box. In this case we assign the highlighted object under the cursor to null and + // return, and thereby avoid changing the highlight state of objects still highlighted by the selection box. + if (this.lastHighlightObject != o && this.lastBoxHighlightObjects.contains(this.lastHighlightObject)) + { + this.lastHighlightObject = null; + return; + } + + super.highlight(o); + } + + protected void highlightSelectedObjects(List list) + { + if (this.lastBoxHighlightObjects.equals(list)) + return; // same thing selected + + // Turn off highlight for the last set of selected objects, if any. Since one of these objects may still be + // highlighted due to a cursor rollover, we detect that object and avoid changing its highlight state. + for (Highlightable h : this.lastBoxHighlightObjects) + { + if (h != this.lastHighlightObject) + h.setHighlighted(false); + } + this.lastBoxHighlightObjects.clear(); + + if (list != null) + { + // Turn on highlight if object selected. + for (Object o : list) + { + if (o instanceof Highlightable) + { + ((Highlightable) o).setHighlighted(true); + this.lastBoxHighlightObjects.add((Highlightable) o); + } + } + } + + // We've potentially changed the highlight state of one or more objects. Request that the world window + // redraw itself in order to refresh these object's display. This is necessary because changes in the + // objects in the pick rectangle do not necessarily correspond to mouse movements. For example, the pick + // rectangle may be cleared when the user releases the mouse button at the end of a drag. In this case, + // there's no mouse movement to cause an automatic redraw. + this.wwd.redraw(); + } +} \ No newline at end of file From 31107fb8a54a40e51f76c3e302a284bd3f45edcd Mon Sep 17 00:00:00 2001 From: Wiehann Matthysen Date: Mon, 12 Dec 2016 21:39:15 +0200 Subject: [PATCH 2/3] Auto arm/disarm ScreenSelector. Modified the ScreenSelector class by introducing additional behaviour. Added two flags, namely autoEnable and autoDisable (with getter and setter methods) to allow for auto-arm and disarming of the selector based on whether the user Control or Shift clicks before dragging the mouse. --- .../examples/util/ScreenSelector.java | 167 ++++++++++++++---- 1 file changed, 137 insertions(+), 30 deletions(-) diff --git a/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java b/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java index af93c571da..3771b63083 100644 --- a/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java +++ b/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java @@ -8,6 +8,7 @@ import gov.nasa.worldwind.*; import gov.nasa.worldwind.event.*; import gov.nasa.worldwind.layers.*; +import gov.nasa.worldwind.pick.*; import gov.nasa.worldwind.render.*; import gov.nasa.worldwind.util.*; import gov.nasa.worldwindx.applications.worldwindow.util.Util; @@ -296,9 +297,16 @@ protected void drawOrderedRenderable(DrawContext dc) protected WorldWindow wwd; protected Layer layer; + protected SelectionRectangle selectionRect; protected List selectedObjects = new ArrayList(); + protected List messageListeners = new ArrayList(); + protected List selectListeners = new ArrayList(); + + protected boolean enabled; + protected boolean autoEnable; + protected boolean autoDisable; protected boolean armed; public ScreenSelector(WorldWindow worldWindow) @@ -315,6 +323,10 @@ public ScreenSelector(WorldWindow worldWindow) this.layer.setPickEnabled(false); // The screen selector is not pickable. this.selectionRect = this.createSelectionRectangle(); ((RenderableLayer) this.layer).addRenderable(this.selectionRect); + + // Listen for mouse input on the World Window. + this.wwd.getInputHandler().addMouseListener(this); + this.wwd.getInputHandler().addMouseMotionListener(this); } protected Layer createLayer() @@ -356,6 +368,26 @@ public void setBorderColor(Color color) { this.selectionRect.setBorderColor(color); } + + public void setAutoEnable(boolean autoEnable) + { + this.autoEnable = autoEnable; + } + + public boolean getAutoEnable() + { + return this.autoEnable; + } + + public void setAutoDisable(boolean autoDisable) + { + this.autoDisable = autoDisable; + } + + public boolean getAutoDisable() + { + return this.autoDisable; + } public void enable() { @@ -373,9 +405,7 @@ public void enable() if (!this.getLayer().isEnabled()) this.getLayer().setEnabled(true); - // Listen for mouse input on the World Window. - this.getWwd().getInputHandler().addMouseListener(this); - this.getWwd().getInputHandler().addMouseMotionListener(this); + this.enabled = true; } public void disable() @@ -389,10 +419,8 @@ public void disable() // Remove the layer that displays this ScreenSelector's selection rectangle. this.getWwd().getModel().getLayers().remove(this.getLayer()); - - // Stop listening for mouse input on the world window. - this.getWwd().getInputHandler().removeMouseListener(this); - this.getWwd().getInputHandler().removeMouseMotionListener(this); + + this.enabled = false; } public List getSelectedObjects() @@ -423,6 +451,30 @@ public void removeMessageListener(MessageListener listener) this.messageListeners.remove(listener); } + + public void addSelectListener(SelectListener listener) + { + if (listener == null) + { + String msg = Logging.getMessage("nullValue.ListenerIsNull"); + Logging.logger().severe(msg); + throw new IllegalArgumentException(msg); + } + + this.selectListeners.add(listener); + } + + public void removeSelectListener(SelectListener listener) + { + if (listener == null) + { + String msg = Logging.getMessage("nullValue.ListenerIsNull"); + Logging.logger().severe(msg); + throw new IllegalArgumentException(msg); + } + + this.selectListeners.remove(listener); + } protected void sendMessage(Message message) { @@ -440,6 +492,23 @@ protected void sendMessage(Message message) } } } + + protected void sendSelectEvent(SelectEvent event) + { + for (SelectListener listener : this.selectListeners) + { + try + { + listener.selected(event); + } + catch (Exception e) + { + String msg = Logging.getMessage("generic.ExceptionInvokingMessageListener"); + Logging.logger().severe(msg); + // Don't throw an exception, just log a severe message and continue to the next listener. + } + } + } public void mouseClicked(MouseEvent mouseEvent) { @@ -448,28 +517,47 @@ public void mouseClicked(MouseEvent mouseEvent) public void mousePressed(MouseEvent mouseEvent) { - if (mouseEvent == null) // Ignore null events. - return; - - if (MouseEvent.BUTTON1_DOWN_MASK != mouseEvent.getModifiersEx()) // Respond to button 1 down w/o modifiers. - return; + if (this.enabled) + { + if (mouseEvent == null) // Ignore null events. + return; - this.armed = true; - this.selectionStarted(mouseEvent); - mouseEvent.consume(); // Consume the mouse event to prevent the view from responding to it. + this.armed = true; + this.selectionStarted(mouseEvent); + mouseEvent.consume(); // Consume the mouse event to prevent the view from responding to it. + } + else if (this.autoEnable) + { + if (mouseEvent.isControlDown() || mouseEvent.isShiftDown()) + { + enable(); + this.armed = true; + this.selectionStarted(mouseEvent); + mouseEvent.consume(); // Consume the mouse event to prevent the view from responding to it. + } + } } public void mouseReleased(MouseEvent mouseEvent) { - if (mouseEvent == null) // Ignore null events. - return; - - if (!this.armed) // Respond to mouse released events when armed. - return; - - this.armed = false; - this.selectionEnded(mouseEvent); - mouseEvent.consume(); // Consume the mouse event to prevent the view from responding to it. + if (this.enabled) + { + if (mouseEvent == null) // Ignore null events. + return; + + if (!this.armed) // Respond to mouse released events when armed. + return; + + this.armed = false; + this.selectionEnded(mouseEvent); + mouseEvent.consume(); // Consume the mouse event to prevent the view from responding to it. + + // If auto-enable, then we auto-disable after selection. + if (this.autoDisable) + { + disable(); + } + } } public void mouseEntered(MouseEvent mouseEvent) @@ -484,14 +572,17 @@ public void mouseExited(MouseEvent mouseEvent) public void mouseDragged(MouseEvent mouseEvent) { - if (mouseEvent == null) // Ignore null events. - return; + if (this.enabled) + { + if (mouseEvent == null) // Ignore null events. + return; - if (!this.armed) // Respond to mouse dragged events when armed. - return; + if (!this.armed) // Respond to mouse dragged events when armed. + return; - this.selectionChanged(mouseEvent); - mouseEvent.consume(); // Consume the mouse event to prevent the view from responding to it. + this.selectionChanged(mouseEvent); + mouseEvent.consume(); // Consume the mouse event to prevent the view from responding to it. + } } public void mouseMoved(MouseEvent mouseEvent) @@ -522,6 +613,22 @@ protected void selectionEnded(MouseEvent mouseEvent) // Send a message indicating that the user has completed their selection. We don't clear the list of selected // objects in order to preserve the list of selected objects for the caller. this.sendMessage(new Message(SELECTION_ENDED, this)); + + PickedObjectList list = new PickedObjectList(); + for (Object selected : this.getSelectedObjects()) + { + PickedObject pickedObject = new PickedObject(0, selected); + pickedObject.setOnTop(); + list.add(pickedObject); + } + + String eventAction = SelectEvent.LEFT_CLICK; + if (mouseEvent.getButton() == MouseEvent.BUTTON2) + { + eventAction = SelectEvent.RIGHT_CLICK; + } + + this.sendSelectEvent(new SelectEvent(this.getWwd(), eventAction, mouseEvent, list)); } protected void selectionChanged(MouseEvent mouseEvent) From 8924ba8d8f5ebc3071e5880304834ffa00d65742 Mon Sep 17 00:00:00 2001 From: Wiehann Matthysen Date: Mon, 12 Dec 2016 22:36:24 +0200 Subject: [PATCH 3/3] Small ScreenSelector bug-fix. The ScreenSelector class should not fire a selection event when a single-click is done on the map. It should only fire events when clicking and dragging a rectangle. The reason for this is that the single-click events will not contain any objects if it originates from the ScreenSelector. This can cause odd behaviour in terms of selecting an object and then deselecting everything. Thus, single-click select events should only be generated by the WorldWind canvas itself. --- .../examples/util/ScreenSelector.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java b/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java index 3771b63083..3e49f7736f 100644 --- a/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java +++ b/src/gov/nasa/worldwindx/examples/util/ScreenSelector.java @@ -605,30 +605,34 @@ protected void selectionStarted(MouseEvent mouseEvent) @SuppressWarnings({"UnusedParameters"}) protected void selectionEnded(MouseEvent mouseEvent) { - this.selectionRect.clearSelection(); - this.getWwd().getSceneController().setPickRectangle(null); - this.getWwd().removeSelectListener(this); // Stop listening for changes the pick rectangle selection. - this.getWwd().redraw(); - - // Send a message indicating that the user has completed their selection. We don't clear the list of selected - // objects in order to preserve the list of selected objects for the caller. - this.sendMessage(new Message(SELECTION_ENDED, this)); - - PickedObjectList list = new PickedObjectList(); - for (Object selected : this.getSelectedObjects()) + Rectangle rectangle = this.selectionRect.getSelection(); + if (rectangle.width > 0 && rectangle.height > 0) { - PickedObject pickedObject = new PickedObject(0, selected); - pickedObject.setOnTop(); - list.add(pickedObject); - } - - String eventAction = SelectEvent.LEFT_CLICK; - if (mouseEvent.getButton() == MouseEvent.BUTTON2) - { - eventAction = SelectEvent.RIGHT_CLICK; + this.selectionRect.clearSelection(); + this.getWwd().getSceneController().setPickRectangle(null); + this.getWwd().removeSelectListener(this); // Stop listening for changes the pick rectangle selection. + this.getWwd().redraw(); + + // Send a message indicating that the user has completed their selection. We don't clear the list of selected + // objects in order to preserve the list of selected objects for the caller. + this.sendMessage(new Message(SELECTION_ENDED, this)); + + PickedObjectList list = new PickedObjectList(); + for (Object selected : this.getSelectedObjects()) + { + PickedObject pickedObject = new PickedObject(0, selected); + pickedObject.setOnTop(); + list.add(pickedObject); + } + + String eventAction = SelectEvent.LEFT_CLICK; + if (mouseEvent.getButton() == MouseEvent.BUTTON2) + { + eventAction = SelectEvent.RIGHT_CLICK; + } + + this.sendSelectEvent(new SelectEvent(this.getWwd(), eventAction, mouseEvent, list)); } - - this.sendSelectEvent(new SelectEvent(this.getWwd(), eventAction, mouseEvent, list)); } protected void selectionChanged(MouseEvent mouseEvent)