GP-875 - Popup window placement integration

This commit is contained in:
dragonmacher
2021-06-03 11:18:24 -04:00
parent d7eb2004a1
commit f978164cfc
6 changed files with 78 additions and 27 deletions

View File

@@ -192,9 +192,20 @@ public abstract class AbstractHoverProvider implements HoverProvider {
popupWindow.showPopup(event); popupWindow.showPopup(event);
} }
else { 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); popupWindow.showOffsetPopup(event, keepVisibleArea);
} }
} }

View File

@@ -25,10 +25,16 @@ import java.util.List;
import javax.swing.*; import javax.swing.*;
import javax.swing.Timer; import javax.swing.Timer;
import docking.widgets.shapes.*;
import generic.util.WindowUtilities; import generic.util.WindowUtilities;
import ghidra.util.bean.GGlassPane; import ghidra.util.bean.GGlassPane;
import ghidra.util.bean.GGlassPanePainter; 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 { public class PopupWindow {
private static final int X_PADDING = 25; private static final int X_PADDING = 25;
private static final int Y_PADDING = 25; private static final int Y_PADDING = 25;
@@ -302,15 +308,12 @@ public class PopupWindow {
// painters.add(painter); // painters.add(painter);
} }
// for debug
// private static List<GGlassPanePainter> painters = new ArrayList<>();
//================================================================================================== //==================================================================================================
// Inner Classes // Inner Classes
//================================================================================================== //==================================================================================================
// for debug // for debug
//private static List<GGlassPanePainter> painters = new ArrayList<>(); // private static List<GGlassPanePainter> painters = new ArrayList<>();
/** Paints shapes used by this class (useful for debugging) */ /** Paints shapes used by this class (useful for debugging) */
@SuppressWarnings("unused") @SuppressWarnings("unused")

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package docking.widgets; package docking.widgets.shapes;
/** /**
* Specifies location and metrics for {@link PopupWindowPlacer}. * Specifies location and metrics for {@link PopupWindowPlacer}.

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package docking.widgets; package docking.widgets.shapes;
import java.awt.*; 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 * 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, * in circumstances where the developer believes such an assertion is not possible,
* such as when allowing an overlapping placement solution. * such as when allowing an overlapping placement solution.
*
* @see PopupWindowPlacerBuilder
*/ */
public abstract class PopupWindowPlacer { public abstract class PopupWindowPlacer {
@@ -144,10 +146,10 @@ public abstract class PopupWindowPlacer {
private PopupWindowPlacer next = null; 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() { 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 major edge
* @param minorBegin start location on edge * @param minorBegin start location on edge
* @param minorEnd end location on edge * @param minorEnd end location on edge
*
* @see PopupWindowPlacerBuilder
*/ */
public PopupWindowPlacer(Location major, Location minorBegin, Location minorEnd) { public PopupWindowPlacer(Location major, Location minorBegin, Location minorEnd) {
if (major == Location.CENTER) { 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 * 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. * horizontal. These two situations are independent, but the same algorithm is used.
* <p> * <p>
* <B>Algorithm Design...</B><p> * <B>Algorithm Design</B><p>
* Note: smaller values are up and bigger values are down, in the presentation below. * Note: smaller values are up and bigger values are down, in the presentation below.
* <p> * <p>
* In trying to allay some confusion (yes it can be confusing), note that for any given major * 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 * 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 * 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 * 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 * 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 * 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 * 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 * else
* no solution * no solution
* </pre> * </pre>
* ... or... * or
* <pre> * <pre>
* <B>lesserLocation</B> * <B>lesserLocation</B>
* <B>&rarr;</B> <B>lesserBoundedLocation</B> (&ge; <B>lesserLocation</B>) * <B>&rarr;</B> <B>lesserBoundedLocation</B> (&ge; <B>lesserLocation</B>)
@@ -369,7 +373,7 @@ public abstract class PopupWindowPlacer {
* else * else
* solution is centerLocation * solution is centerLocation
* </pre> * </pre>
* ... or... * or
* <pre> * <pre>
* <B>lesserLocation</B> * <B>lesserLocation</B>
* <B>centerLocation</B> * <B>centerLocation</B>

View File

@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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. * This class builds a PopWindowPlacer that can have subsequent PopWindowPlacers.
@@ -24,7 +24,11 @@ import docking.widgets.PopupWindowPlacer.*;
* placers, and a clean-up <B>assert</B> placer. Additionally, there are <B>rotational</B> placers * placers, and a clean-up <B>assert</B> placer. Additionally, there are <B>rotational</B> placers
* that are composed of edge placers. * that are composed of edge placers.
* <p> * <p>
* <B>EDGE PLACERS</B> * <BR>
* <BR>
*
* <H1>Edge Placers</H1>
*
* <p> * <p>
* The <B>edge</B> placers are the leftEdge, rightEdge, topEdge, and bottomEdge methods that take * The <B>edge</B> 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 * 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, * same way for each method, though some arguments are not valid for some edges; for instance,
* <code>Location.TOP</code> is only valid for left and right edges. * <code>Location.TOP</code> is only valid for left and right edges.
* <p> * <p>
* <B>Two or More Location Arguments</B><p> *
* <H2>Two or More Location Arguments</H2>
*
* <p>
* When two or more arguments are used, the first argument specifies the nominal placement cell * 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 * 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 * 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 * far the solution is allowed to shift. To specify a "no-shift" solution, one specifies the same
* placement cell twice (e.g., <code>rightEdge(Location.TOP, Location.TOP)</code>). * placement cell twice (e.g., <code>rightEdge(Location.TOP, Location.TOP)</code>).
* <p> * <p>
* <B>One Location Argument</B><p> *
* <H2>One Location Argument</H2>
*
* <p>
* When one argument is used, the solution is the same as when two arguments are specified except * 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, * that the second argument is automatically set to the nearest neighboring cell. Thus,
* <code>rightEdge(Location.TOP)</code> is the same as * <code>rightEdge(Location.TOP)</code> is the same as
@@ -54,11 +64,17 @@ import docking.widgets.PopupWindowPlacer.*;
* <code>Location.CENTER</code>, two attempts are built, the first being the BOTTOM or RIGHT cell * <code>Location.CENTER</code>, two attempts are built, the first being the BOTTOM or RIGHT cell
* and the second being the TOP or LEFT cell. * and the second being the TOP or LEFT cell.
* <p> * <p>
* <B>No Arguments</B><p> *
* <H2>No Arguments</H2>
*
* <p>
* When no arguments are specified, two arguments to the underlying placer are automatically set * 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. * to BOTTOM or RIGHT for the first and TOP or LEFT for the second.
* <p> * <p>
* <B>Examples</B><p> *
* <H2>Examples</H2>
*
* <p>
* Builds a placer that first attempts a placement at the bottom of the right edge with no * 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: * shift, then tries the top of the right edge with no shift, then top center with no shift:
* <pre> * <pre>
@@ -77,7 +93,11 @@ import docking.widgets.PopupWindowPlacer.*;
* .topEdge(Location.CENTER); * .topEdge(Location.CENTER);
* .build();</pre> * .build();</pre>
* <p> * <p>
* <B>ROTATIONAL PLACERS</B> * <BR>
* <BR>
*
* <H1>Rotational Placers</H1>
*
* <p> * <p>
* There are clockwise and counter-clockwise rotational placers that built up from edge placers. * There are clockwise and counter-clockwise rotational placers that built up from edge placers.
* These are: * 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 * 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. * there was not a previous placer, then the BOTTOM RIGHT cell is chosen as the starting cell.
* <p> * <p>
* <B>OVERLAPPING CORNER PLACER</B> * <BR>
* <BR>
*
* <H1>Overlapping Corner Placer</H1>
*
* <p> * <p>
* There is one corner placer, <code>leastOverlapCorner()</code>. This placer tries to make a * There is one corner placer, <code>leastOverlapCorner()</code>. This placer tries to make a
* placement at each of the corners of the context area and shifts into the context region as much * 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 * top left. Unless ill-constructed (sized of context area, screen, and pop-up dimension), this
* placer should always find a solution. * placer should always find a solution.
* <p> * <p>
* <B>ASSERT PLACER</B> * <BR>
* <BR>
*
* <H1>Assert Placer</H1>
*
* <p> * <p>
* The <code>throwsAssertException()</code> placer is available, which automatically throws an * The <code>throwsAssertException()</code> placer is available, which automatically throws an
* AssertException. This placer is only intended to be used by the client in such as case when * 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.*;
* <code>leastOverlapCorner()</code> placer. This just throws an exception instead of returning * <code>leastOverlapCorner()</code> placer. This just throws an exception instead of returning
* the <code>null</code> return value that would be returned from previous placement attempts. * the <code>null</code> return value that would be returned from previous placement attempts.
* <p> * <p>
* <B>COMPOSITE EXAMPLES</B><p> * <BR>
* <BR>
*
* <H1>Composite Placer</H1>
*
* <p>
* Builds a placer that first attempts a placement at the right edge from bottom to top, then * 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 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: * left, followed by a least-overlap-corner solution, followed by a failure assert:

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package docking.widgets; package docking.widgets.shapes;
import static org.junit.Assert.*; import static org.junit.Assert.*;