From f978164cfc4c05ab35408ce2d1eb260a4a93887b Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Thu, 3 Jun 2021 11:18:24 -0400 Subject: [PATCH] GP-875 - Popup window placement integration --- .../core/hover/AbstractHoverProvider.java | 17 +++++- .../java/docking/widgets/PopupWindow.java | 11 ++-- .../widgets/{ => shapes}/Location.java | 2 +- .../{ => shapes}/PopupWindowPlacer.java | 18 +++--- .../PopupWindowPlacerBuilder.java | 55 +++++++++++++++---- .../{ => shapes}/PopupWindowPlacerTest.java | 2 +- 6 files changed, 78 insertions(+), 27 deletions(-) rename Ghidra/Framework/Docking/src/main/java/docking/widgets/{ => shapes}/Location.java (98%) rename Ghidra/Framework/Docking/src/main/java/docking/widgets/{ => shapes}/PopupWindowPlacer.java (98%) rename Ghidra/Framework/Docking/src/main/java/docking/widgets/{ => shapes}/PopupWindowPlacerBuilder.java (96%) rename Ghidra/Framework/Docking/src/test/java/docking/widgets/{ => shapes}/PopupWindowPlacerTest.java (99%) diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java index 3eccd3e4bf..ef7562bad0 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/hover/AbstractHoverProvider.java @@ -192,9 +192,20 @@ public abstract class AbstractHoverProvider implements HoverProvider { popupWindow.showPopup(event); } else { - int xOffset = 50;// magic: trial and error - Dimension size = fieldBounds.getSize(); - Dimension keepVisibleArea = new Dimension(xOffset, size.height); + + // + // Make an area over which to show the popup. The popup should not cover this area. + // The field that is hovered may be too big to be this area, as a big field may cause + // the popup to be too far away from the cursor. + // + // Use the mouse point and then create an area (based on trial-and-error) that should + // not be occluded. + // + int horizontalPad = 100; + int verticalPad = 50; + Rectangle keepVisibleArea = new Rectangle(event.getPoint()); + keepVisibleArea.grow(horizontalPad, verticalPad); + popupWindow.showOffsetPopup(event, keepVisibleArea); } } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java index ec2ffda275..48a911fe3d 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindow.java @@ -25,10 +25,16 @@ import java.util.List; import javax.swing.*; import javax.swing.Timer; +import docking.widgets.shapes.*; import generic.util.WindowUtilities; import ghidra.util.bean.GGlassPane; import ghidra.util.bean.GGlassPanePainter; +/** + * A generic window intended to be used as a temporary window to show information. This window is + * designed to stay open as long as the user mouses over the window. Once the user mouses away, + * the window will be closed. + */ public class PopupWindow { private static final int X_PADDING = 25; private static final int Y_PADDING = 25; @@ -302,15 +308,12 @@ public class PopupWindow { // painters.add(painter); } - // for debug -// private static List painters = new ArrayList<>(); - //================================================================================================== // Inner Classes //================================================================================================== // for debug - //private static List painters = new ArrayList<>(); +// private static List painters = new ArrayList<>(); /** Paints shapes used by this class (useful for debugging) */ @SuppressWarnings("unused") diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/Location.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/Location.java similarity index 98% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/Location.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/Location.java index a4b7aadb9c..b72558b8a6 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/Location.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/Location.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.widgets; +package docking.widgets.shapes; /** * Specifies location and metrics for {@link PopupWindowPlacer}. diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindowPlacer.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/PopupWindowPlacer.java similarity index 98% rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindowPlacer.java rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/PopupWindowPlacer.java index 09b7cf3ef5..0ee33e1d34 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindowPlacer.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/PopupWindowPlacer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.widgets; +package docking.widgets.shapes; import java.awt.*; @@ -134,6 +134,8 @@ import ghidra.util.exception.AssertException; * This last placer, when chosen, serves as an assert condition, which is helpful * in circumstances where the developer believes such an assertion is not possible, * such as when allowing an overlapping placement solution. + * + * @see PopupWindowPlacerBuilder */ public abstract class PopupWindowPlacer { @@ -144,10 +146,10 @@ public abstract class PopupWindowPlacer { private PopupWindowPlacer next = null; /** - * Constructor only for classes that do you use Preferences + * Constructor only for classes that do not use placement preferences */ public PopupWindowPlacer() { - // Only for implementations that do not use Preferences + // Only for implementations that do not use placement preferences } /** @@ -156,6 +158,8 @@ public abstract class PopupWindowPlacer { * @param major edge * @param minorBegin start location on edge * @param minorEnd end location on edge + * + * @see PopupWindowPlacerBuilder */ public PopupWindowPlacer(Location major, Location minorBegin, Location minorEnd) { if (major == Location.CENTER) { @@ -263,7 +267,7 @@ public abstract class PopupWindowPlacer { * and the minor axis being vertical; the major axis being vertical, and the minor axis being * horizontal. These two situations are independent, but the same algorithm is used. *

- * Algorithm Design...

+ * Algorithm Design

* Note: smaller values are up and bigger values are down, in the presentation below. *

* In trying to allay some confusion (yes it can be confusing), note that for any given major @@ -283,7 +287,7 @@ public abstract class PopupWindowPlacer { * Keeping with the original right edge scenario begun above, we are trying to find a minor * axis placement on the right (major) edge. For this placement, one can refer to * documentation elsewhere in this class, but essentially, we are trying to place a popup - * area against a context rectangle without exceeded the outer bounds (screen) rectangle. But, + * area against a context rectangle without exceeding the outer bounds (screen) rectangle. But, * again, we are only considering the placement against the right edge already chosen and only * trying to fit in the vertical dimension against this edge. Thus, this algorithm only needs * values for this one dimension. These are the length of the placement area in this one @@ -348,7 +352,7 @@ public abstract class PopupWindowPlacer { * else * no solution * - * ... or... + * or *

 	 *         lesserLocation
 	 *             → lesserBoundedLocation (≥ lesserLocation)
@@ -369,7 +373,7 @@ public abstract class PopupWindowPlacer {
 	 *         else
 	 *             solution is centerLocation
 	 * 
- * ... or... + * or *
 	 *         lesserLocation
 	 *         centerLocation
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindowPlacerBuilder.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/PopupWindowPlacerBuilder.java
similarity index 96%
rename from Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindowPlacerBuilder.java
rename to Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/PopupWindowPlacerBuilder.java
index e6e4bf0814..f49f3e4c22 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupWindowPlacerBuilder.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/shapes/PopupWindowPlacerBuilder.java
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package docking.widgets;
+package docking.widgets.shapes;
 
-import docking.widgets.PopupWindowPlacer.*;
+import docking.widgets.shapes.PopupWindowPlacer.*;
 
 /**
  * This class builds a PopWindowPlacer that can have subsequent PopWindowPlacers.
@@ -24,7 +24,11 @@ import docking.widgets.PopupWindowPlacer.*;
  * placers, and a clean-up assert placer.  Additionally, there are rotational placers
  * that are composed of edge placers.
  * 

- * EDGE PLACERS + *
+ *
+ * + *

Edge Placers

+ * *

* The edge placers are the leftEdge, rightEdge, topEdge, and bottomEdge methods that take * Location arguments that one can think of as "cells" for optimal placement, but which have some @@ -38,7 +42,10 @@ import docking.widgets.PopupWindowPlacer.*; * same way for each method, though some arguments are not valid for some edges; for instance, * Location.TOP is only valid for left and right edges. *

- * Two or More Location Arguments

+ * + *

Two or More Location Arguments

+ * + *

* When two or more arguments are used, the first argument specifies the nominal placement cell * and the second argument specifies how far the solution is allowed to shift. If a solution is * not found and if there are more than two arguments, another placement attempt is made where @@ -46,7 +53,10 @@ import docking.widgets.PopupWindowPlacer.*; * far the solution is allowed to shift. To specify a "no-shift" solution, one specifies the same * placement cell twice (e.g., rightEdge(Location.TOP, Location.TOP)). *

- * One Location Argument

+ * + *

One Location Argument

+ * + *

* When one argument is used, the solution is the same as when two arguments are specified except * that the second argument is automatically set to the nearest neighboring cell. Thus, * rightEdge(Location.TOP) is the same as @@ -54,11 +64,17 @@ import docking.widgets.PopupWindowPlacer.*; * Location.CENTER, two attempts are built, the first being the BOTTOM or RIGHT cell * and the second being the TOP or LEFT cell. *

- * No Arguments

+ * + *

No Arguments

+ * + *

* When no arguments are specified, two arguments to the underlying placer are automatically set * to BOTTOM or RIGHT for the first and TOP or LEFT for the second. *

- * Examples

+ * + *

Examples

+ * + *

* Builds a placer that first attempts a placement at the bottom of the right edge with no * shift, then tries the top of the right edge with no shift, then top center with no shift: *

@@ -77,7 +93,11 @@ import docking.widgets.PopupWindowPlacer.*;
  *            .topEdge(Location.CENTER);
  *            .build();
*

- * ROTATIONAL PLACERS + *
+ *
+ * + *

Rotational Placers

+ * *

* There are clockwise and counter-clockwise rotational placers that built up from edge placers. * These are: @@ -96,7 +116,11 @@ import docking.widgets.PopupWindowPlacer.*; * except that they start at the "beginning" cell where the most previous placer had left off. If * there was not a previous placer, then the BOTTOM RIGHT cell is chosen as the starting cell. *

- * OVERLAPPING CORNER PLACER + *
+ *
+ * + *

Overlapping Corner Placer

+ * *

* There is one corner placer, leastOverlapCorner(). This placer tries to make a * placement at each of the corners of the context area and shifts into the context region as much @@ -106,7 +130,11 @@ import docking.widgets.PopupWindowPlacer.*; * top left. Unless ill-constructed (sized of context area, screen, and pop-up dimension), this * placer should always find a solution. *

- * ASSERT PLACER + *
+ *
+ * + *

Assert Placer

+ * *

* The throwsAssertException() placer is available, which automatically throws an * AssertException. This placer is only intended to be used by the client in such as case when @@ -114,7 +142,12 @@ import docking.widgets.PopupWindowPlacer.*; * leastOverlapCorner() placer. This just throws an exception instead of returning * the null return value that would be returned from previous placement attempts. *

- * COMPOSITE EXAMPLES

+ *
+ *
+ * + *

Composite Placer

+ * + *

* Builds a placer that first attempts a placement at the right edge from bottom to top, then * left edge from bottom to top, then top edge from right to left, then bottom edge from right to * left, followed by a least-overlap-corner solution, followed by a failure assert: diff --git a/Ghidra/Framework/Docking/src/test/java/docking/widgets/PopupWindowPlacerTest.java b/Ghidra/Framework/Docking/src/test/java/docking/widgets/shapes/PopupWindowPlacerTest.java similarity index 99% rename from Ghidra/Framework/Docking/src/test/java/docking/widgets/PopupWindowPlacerTest.java rename to Ghidra/Framework/Docking/src/test/java/docking/widgets/shapes/PopupWindowPlacerTest.java index 7e2ad360f9..a94430a2d4 100644 --- a/Ghidra/Framework/Docking/src/test/java/docking/widgets/PopupWindowPlacerTest.java +++ b/Ghidra/Framework/Docking/src/test/java/docking/widgets/shapes/PopupWindowPlacerTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.widgets; +package docking.widgets.shapes; import static org.junit.Assert.*;