mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-25 17:00:36 -09:00
Merge remote-tracking branch 'origin/GP-875-dragonmacher-hover-popup-location' into Ghidra_10.0
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.codebrowser.hover;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JToolTip;
|
||||
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import ghidra.GhidraOptions;
|
||||
import ghidra.app.plugin.core.hover.AbstractConfigurableHover;
|
||||
import ghidra.app.util.ToolTipUtils;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.*;
|
||||
|
||||
/**
|
||||
* A Listing hover to show tool tips for function signatures
|
||||
*/
|
||||
public class FunctionSignatureListingHover extends AbstractConfigurableHover
|
||||
implements ListingHoverService {
|
||||
|
||||
private static final String NAME = "Function Signature Display";
|
||||
private static final String DESCRIPTION =
|
||||
"Toggle whether function signature is displayed in a tooltip " +
|
||||
"when the mouse hovers over a function signature.";
|
||||
|
||||
// note: guilty knowledge that the Truncated Text service has a priority of 10
|
||||
private static final int POPUP_PRIORITY = 20;
|
||||
|
||||
public FunctionSignatureListingHover(PluginTool tool) {
|
||||
super(tool, POPUP_PRIORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDescription() {
|
||||
return DESCRIPTION;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getOptionsCategory() {
|
||||
return GhidraOptions.CATEGORY_BROWSER_POPUPS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getHoverComponent(Program program, ProgramLocation programLocation,
|
||||
FieldLocation fieldLocation, Field field) {
|
||||
|
||||
if (!enabled || programLocation == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<? extends ProgramLocation> clazz = programLocation.getClass();
|
||||
if (clazz != FunctionSignatureFieldLocation.class &&
|
||||
clazz != FunctionNameFieldLocation.class) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// is the label local to the function
|
||||
FunctionSignatureFieldLocation functionLocation =
|
||||
(FunctionSignatureFieldLocation) programLocation;
|
||||
|
||||
Address entry = functionLocation.getFunctionAddress();
|
||||
FunctionManager functionManager = program.getFunctionManager();
|
||||
Function function = functionManager.getFunctionAt(entry);
|
||||
|
||||
String toolTipText = ToolTipUtils.getToolTipText(function, true);
|
||||
JToolTip toolTip = new JToolTip();
|
||||
toolTip.setTipText(toolTipText);
|
||||
return toolTip;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.codebrowser.hover;
|
||||
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
|
||||
/**
|
||||
* A plugin to show tool tip text for a function signature
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.CODE_VIEWER,
|
||||
shortDescription = "Shows formatted tool tip text over function signatures",
|
||||
description = "This plugin extends the functionality of the code browser by adding a "
|
||||
+ "\tooltip\" over function signaturefields in Listing.",
|
||||
servicesProvided = { ListingHoverService.class }
|
||||
)
|
||||
//@formatter:on
|
||||
public class FunctionSignatureListingHoverPlugin extends Plugin {
|
||||
|
||||
private FunctionSignatureListingHover functionSignatureHover;
|
||||
|
||||
public FunctionSignatureListingHoverPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
functionSignatureHover = new FunctionSignatureListingHover(tool);
|
||||
registerServiceProvided(ListingHoverService.class, functionSignatureHover);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose() {
|
||||
functionSignatureHover.dispose();
|
||||
}
|
||||
}
|
||||
@@ -123,9 +123,9 @@ public class ProgramAddressRelationshipListingHover extends AbstractConfigurable
|
||||
return;
|
||||
}
|
||||
|
||||
String dataDescr = "Data Offset";
|
||||
String description = "Data Offset";
|
||||
if (data.getDataType() instanceof Structure) {
|
||||
dataDescr = "Structure Offset";
|
||||
description = "Structure Offset";
|
||||
}
|
||||
|
||||
String name = data.getLabel(); // prefer the label
|
||||
@@ -133,12 +133,14 @@ public class ProgramAddressRelationshipListingHover extends AbstractConfigurable
|
||||
name = data.getDataType().getName();
|
||||
}
|
||||
|
||||
name = StringUtilities.trimMiddle(name, 60);
|
||||
|
||||
if (name == null) {
|
||||
// don't think we can get here
|
||||
name = italic("Unnamed");
|
||||
}
|
||||
|
||||
appendTableRow(sb, dataDescr, name, dataOffset);
|
||||
appendTableRow(sb, description, name, dataOffset);
|
||||
}
|
||||
|
||||
private void addByteSourceInfo(Program program, Address loc, StringBuilder sb) {
|
||||
@@ -150,7 +152,8 @@ public class ProgramAddressRelationshipListingHover extends AbstractConfigurable
|
||||
if (addressSourceInfo.getFileName() == null) {
|
||||
return;
|
||||
}
|
||||
String filename = StringUtilities.trim(addressSourceInfo.getFileName(), MAX_FILENAME_SIZE);
|
||||
String filename =
|
||||
StringUtilities.trimMiddle(addressSourceInfo.getFileName(), MAX_FILENAME_SIZE);
|
||||
long fileOffset = addressSourceInfo.getFileOffset();
|
||||
String dataDescr = "Byte Source Offset";
|
||||
appendTableRow(sb, dataDescr, "File: " + filename, fileOffset);
|
||||
@@ -160,7 +163,10 @@ public class ProgramAddressRelationshipListingHover extends AbstractConfigurable
|
||||
Function function = program.getFunctionManager().getFunctionContaining(loc);
|
||||
if (function != null) {
|
||||
long functionOffset = loc.subtract(function.getEntryPoint());
|
||||
appendTableRow(sb, "Function Offset", HTMLUtilities.escapeHTML(function.getName()),
|
||||
|
||||
String functionName = function.getName();
|
||||
functionName = StringUtilities.trimMiddle(functionName, 60);
|
||||
appendTableRow(sb, "Function Offset", HTMLUtilities.escapeHTML(functionName),
|
||||
functionOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,14 +91,15 @@ public abstract class AbstractReferenceHover extends AbstractConfigurableHover {
|
||||
|
||||
String hoverName = getName();
|
||||
options.getOptions(hoverName).setOptionsHelpLocation(help);
|
||||
|
||||
options.registerOption(hoverName, true, null, getDescription());
|
||||
enabled = options.getBoolean(hoverName, true);
|
||||
|
||||
options.registerOption(hoverName + Options.DELIMITER + "Dialog Height", 400, help,
|
||||
"Height of the popup window");
|
||||
options.registerOption(hoverName + Options.DELIMITER + "Dialog Width", 600, help,
|
||||
"Width of the popup window");
|
||||
|
||||
setOptions(options, hoverName);
|
||||
options.addOptionsChangeListener(this);
|
||||
}
|
||||
|
||||
@@ -152,10 +153,18 @@ public abstract class AbstractReferenceHover extends AbstractConfigurableHover {
|
||||
return;
|
||||
}
|
||||
|
||||
toolTip = new JToolTip();
|
||||
|
||||
panel = new ListingPanel(codeFormatService.getFormatManager());// share the manager from the code viewer
|
||||
panel.setTextBackgroundColor(BACKGROUND_COLOR);
|
||||
|
||||
toolTip = new JToolTip();
|
||||
String name = getName();
|
||||
String widthOptionName = name + Options.DELIMITER + "Dialog Width";
|
||||
String heightOptionName = name + Options.DELIMITER + "Dialog Height";
|
||||
int dialogWidth = options.getInt(widthOptionName, 600);
|
||||
int dialogHeight = options.getInt(heightOptionName, 400);
|
||||
Dimension d = new Dimension(dialogWidth, dialogHeight);
|
||||
panel.setPreferredSize(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
*/
|
||||
package ghidra.app.services;
|
||||
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
||||
import javax.swing.JComponent;
|
||||
|
||||
import docking.widgets.fieldpanel.field.Field;
|
||||
import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
||||
/**
|
||||
* <code>HoverService</code> provides the ability to popup data Windows over a Field viewer
|
||||
@@ -30,7 +29,8 @@ import docking.widgets.fieldpanel.support.FieldLocation;
|
||||
public interface HoverService {
|
||||
|
||||
/**
|
||||
* Returns the priority of this hover service.
|
||||
* Returns the priority of this hover service. A lower priority is more important.
|
||||
* @return the priority
|
||||
*/
|
||||
public int getPriority();
|
||||
|
||||
@@ -41,7 +41,8 @@ public interface HoverService {
|
||||
public void scroll(int amount);
|
||||
|
||||
/**
|
||||
* Return whether hover mode is "on."
|
||||
* Return whether hover mode is "on"
|
||||
* @return the priority
|
||||
*/
|
||||
public boolean hoverModeSelected();
|
||||
|
||||
|
||||
@@ -25,13 +25,19 @@ 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 = 20;
|
||||
private static final int Y_PADDING = 20;
|
||||
private static final int X_PADDING = 25;
|
||||
private static final int Y_PADDING = 25;
|
||||
private static final List<WeakReference<PopupWindow>> VISIBLE_POPUPS = new ArrayList<>();
|
||||
|
||||
public static void hideAllWindows() {
|
||||
@@ -43,11 +49,20 @@ public class PopupWindow {
|
||||
}
|
||||
}
|
||||
|
||||
private JWindow popup;
|
||||
private static final PopupWindowPlacer DEFAULT_WINDOW_PLACER =
|
||||
new PopupWindowPlacerBuilder()
|
||||
.rightEdge(Location.BOTTOM)
|
||||
.leftEdge(Location.BOTTOM)
|
||||
.bottomEdge(Location.RIGHT)
|
||||
.topEdge(Location.CENTER)
|
||||
.leastOverlapCorner()
|
||||
.throwsAssertException()
|
||||
.build();
|
||||
|
||||
private Component sourceComponent;
|
||||
/** Area where user can mouse without hiding the window (in screen coordinates) */
|
||||
private Rectangle neutralMotionZone;
|
||||
private Rectangle mouseMovementArea;
|
||||
private JWindow popup;
|
||||
private Component sourceComponent;
|
||||
|
||||
private MouseMotionListener sourceMouseMotionListener;
|
||||
private MouseListener sourceMouseListener;
|
||||
@@ -87,7 +102,7 @@ public class PopupWindow {
|
||||
|
||||
popup = new JWindow(parentWindow);
|
||||
popup.setFocusableWindowState(false);
|
||||
// this is bad, as it keeps tooltips above all apps and they don't go away, as normal tooltips do
|
||||
// this is bad, as it keeps tooltips above all apps and they don't go away, as normal tooltips do
|
||||
// popup.setAlwaysOnTop( true );
|
||||
|
||||
popup.getContentPane().add(displayComponent);
|
||||
@@ -112,16 +127,16 @@ public class PopupWindow {
|
||||
sourceMouseMotionListener = new MouseMotionAdapter() {
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
Point localPoint = e.getPoint();
|
||||
|
||||
Point localPoint = e.getPoint();
|
||||
SwingUtilities.convertPointToScreen(localPoint, e.getComponent());
|
||||
if (!neutralMotionZone.contains(localPoint)) {
|
||||
if (!mouseMovementArea.contains(localPoint)) {
|
||||
hide();
|
||||
}
|
||||
else {
|
||||
// If the user mouses around the neutral zone, then start the close timer. The
|
||||
// timer will be reset if the user enters the popup.
|
||||
closeTimer.start();
|
||||
closeTimer.restart();
|
||||
}
|
||||
e.consume(); // consume the event so that the source component doesn't processes it
|
||||
}
|
||||
@@ -183,8 +198,8 @@ public class PopupWindow {
|
||||
}
|
||||
|
||||
private void removeOldPopupReferences() {
|
||||
for (Iterator<WeakReference<PopupWindow>> iterator =
|
||||
VISIBLE_POPUPS.iterator(); iterator.hasNext();) {
|
||||
for (Iterator<WeakReference<PopupWindow>> iterator = VISIBLE_POPUPS.iterator(); iterator
|
||||
.hasNext();) {
|
||||
WeakReference<PopupWindow> reference = iterator.next();
|
||||
PopupWindow window = reference.get();
|
||||
if (window == this) {
|
||||
@@ -202,7 +217,7 @@ public class PopupWindow {
|
||||
/**
|
||||
* Sets the amount of time that will pass before the popup window is closed <b>after</b> the
|
||||
* user moves away from the popup window and out of the neutral zone
|
||||
*
|
||||
*
|
||||
* @param delayInMillis the timer delay
|
||||
*/
|
||||
public void setCloseWindowDelay(int delayInMillis) {
|
||||
@@ -210,43 +225,32 @@ public class PopupWindow {
|
||||
closeTimer.setRepeats(false);
|
||||
}
|
||||
|
||||
public void showOffsetPopup(MouseEvent e, Dimension keepVisibleArea) {
|
||||
doShowPopup(e, keepVisibleArea);
|
||||
public void showOffsetPopup(MouseEvent e, Rectangle keepVisibleSize) {
|
||||
doShowPopup(e, keepVisibleSize, DEFAULT_WINDOW_PLACER);
|
||||
}
|
||||
|
||||
public void showPopup(MouseEvent e) {
|
||||
doShowPopup(e, null);
|
||||
doShowPopup(e, null, DEFAULT_WINDOW_PLACER);
|
||||
}
|
||||
|
||||
private void doShowPopup(MouseEvent e, Dimension keepVisibleArea) {
|
||||
private void doShowPopup(MouseEvent e, Rectangle keepVisibleSize, PopupWindowPlacer placer) {
|
||||
hideAllWindows();
|
||||
|
||||
sourceComponent = e.getComponent();
|
||||
sourceComponent.addMouseListener(sourceMouseListener);
|
||||
sourceComponent.addMouseMotionListener(sourceMouseMotionListener);
|
||||
|
||||
Point point = e.getPoint();
|
||||
SwingUtilities.convertPointToScreen(point, sourceComponent);
|
||||
if (keepVisibleArea == null) {
|
||||
keepVisibleArea = new Dimension(0, 0);
|
||||
}
|
||||
Dimension popupDimension = popup.getSize();
|
||||
ensureSize(popupDimension);
|
||||
|
||||
Rectangle popupBounds = popup.getBounds();
|
||||
|
||||
int x = point.x + keepVisibleArea.width + X_PADDING;
|
||||
int y = point.y + keepVisibleArea.height + Y_PADDING;
|
||||
popupBounds.setLocation(x, y);
|
||||
|
||||
WindowUtilities.ensureOnScreen(sourceComponent, popupBounds);
|
||||
|
||||
Rectangle hoverArea = new Rectangle(point, keepVisibleArea);
|
||||
adjustBoundsForCursorLocation(popupBounds, hoverArea);
|
||||
|
||||
neutralMotionZone = createNeutralMotionZone(popupBounds, hoverArea);
|
||||
Rectangle keepVisibleArea = createKeepVisibleArea(e, keepVisibleSize);
|
||||
Rectangle screenBounds = WindowUtilities.getVisibleScreenBounds().getBounds();
|
||||
Rectangle placement = placer.getPlacement(popupDimension, keepVisibleArea, screenBounds);
|
||||
mouseMovementArea = createMovementArea(placement, keepVisibleArea);
|
||||
|
||||
installDebugPainter(e);
|
||||
|
||||
popup.setBounds(popupBounds);
|
||||
popup.setBounds(placement);
|
||||
popup.setVisible(true);
|
||||
|
||||
removeOldPopupReferences();
|
||||
@@ -254,87 +258,62 @@ public class PopupWindow {
|
||||
VISIBLE_POPUPS.add(new WeakReference<>(this));
|
||||
}
|
||||
|
||||
private Rectangle createKeepVisibleArea(MouseEvent e, Rectangle keepVisibleAea) {
|
||||
|
||||
Rectangle newArea;
|
||||
if (keepVisibleAea == null) {
|
||||
Point point = new Point(e.getPoint());
|
||||
newArea = new Rectangle(point);
|
||||
newArea.grow(X_PADDING, Y_PADDING); // pad to avoid placing the popup too close
|
||||
}
|
||||
else {
|
||||
newArea = new Rectangle(keepVisibleAea);
|
||||
}
|
||||
|
||||
Point point = newArea.getLocation();
|
||||
SwingUtilities.convertPointToScreen(point, sourceComponent);
|
||||
newArea.setLocation(point);
|
||||
|
||||
return newArea;
|
||||
}
|
||||
|
||||
private void ensureSize(Dimension popupDimension) {
|
||||
Dimension screenDimension = WindowUtilities.getVisibleScreenBounds().getBounds().getSize();
|
||||
|
||||
if (screenDimension.width < popupDimension.width) {
|
||||
popupDimension.width = screenDimension.width / 2;
|
||||
}
|
||||
|
||||
if (screenDimension.height < popupDimension.height) {
|
||||
popupDimension.height = screenDimension.height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a rectangle that contains both given rectangles entirely and includes padding.
|
||||
* The padding allows users to mouse over the edge of the hovered area without triggering the
|
||||
* popup to close.
|
||||
*/
|
||||
private Rectangle createMovementArea(Rectangle popupBounds, Rectangle hoverRectangle) {
|
||||
Rectangle result = popupBounds.union(hoverRectangle);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void installDebugPainter(MouseEvent e) {
|
||||
// GGlassPane glassPane = GGlassPane.getGlassPane(sourceComponent);
|
||||
// ShapeDebugPainter painter = new ShapeDebugPainter(e, neutralMotionZone);
|
||||
// glassPane.addPainter(painter);
|
||||
// GGlassPane glassPane = GGlassPane.getGlassPane(sourceComponent);
|
||||
// ShapeDebugPainter painter = new ShapeDebugPainter(e, null, neutralMotionZone);
|
||||
// painters.forEach(p -> glassPane.removePainter(p));
|
||||
//
|
||||
// glassPane.addPainter(painter);
|
||||
// painters.add(painter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the given bounds to make sure that they do not cover the given location.
|
||||
* <p>
|
||||
* When the <tt>hoverArea</tt> is obscured, this method will first attempt to move the
|
||||
* bounds up if possible. If moving up is not possible due to space constraints, then this
|
||||
* method will try to shift the bounds to the right of the hover area. If this is not
|
||||
* possible, then the bounds will not be changed.
|
||||
*
|
||||
* @param bounds The bounds to move as necessary.
|
||||
* @param hoverArea The area that should not be covered by the given bounds
|
||||
* @return the original bounds adjusted so that they do not cover the given <tt>hoverArea</tt>,
|
||||
* if possible.
|
||||
*/
|
||||
private Rectangle adjustBoundsForCursorLocation(Rectangle bounds, Rectangle hoverArea) {
|
||||
if (!bounds.intersects(hoverArea)) {
|
||||
return bounds;
|
||||
}
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
// first attempt to move the window--try to go up
|
||||
int movedY = hoverArea.y - bounds.height;
|
||||
boolean canMoveUp = movedY >= 0;
|
||||
if (canMoveUp) {
|
||||
// move the given bounds above the current point
|
||||
bounds.y = movedY;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
// We couldn't move up, so we try to go left, since by default the popup is placed
|
||||
// to the right of the hover area.
|
||||
int movedX = hoverArea.x - bounds.width;
|
||||
boolean canMoveLeft = movedX >= 0;
|
||||
if (canMoveLeft) {
|
||||
bounds.x = movedX;
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a rectangle that contains both given rectangles entirely.
|
||||
*/
|
||||
private Rectangle createNeutralMotionZone(Rectangle popupBounds, Rectangle hoverRectangle) {
|
||||
int newX = Math.min(hoverRectangle.x, popupBounds.x);
|
||||
int newY = Math.min(hoverRectangle.y, popupBounds.y);
|
||||
|
||||
double hoverLowestCornerX = hoverRectangle.x + hoverRectangle.getWidth();
|
||||
double popupLowestCornerX = popupBounds.x + popupBounds.getWidth();
|
||||
int lowestCornerX = (int) Math.max(hoverLowestCornerX, popupLowestCornerX);
|
||||
|
||||
double hoverLowestCornerY = hoverRectangle.y + hoverRectangle.getHeight();
|
||||
double popupLowestCornerY = popupBounds.y + popupBounds.getHeight();
|
||||
int lowestCornerY = (int) Math.max(hoverLowestCornerY, popupLowestCornerY);
|
||||
|
||||
int width = difference(newX, lowestCornerX);
|
||||
int height = difference(newY, lowestCornerY);
|
||||
|
||||
// add in some padding around the edges of the area, so that moving just over the edge
|
||||
// of the popup will not close it (this can happen when the user sloppy-scrolls)
|
||||
int padding = 25;
|
||||
newX -= padding;
|
||||
newY -= padding;
|
||||
width += (padding * 2); // * 2 to give the padding and to compensate for the shifted x
|
||||
height += (padding * 2); // * 2 to give the padding and to compensate for the shifted y
|
||||
|
||||
return new Rectangle(newX, newY, width, height);
|
||||
}
|
||||
|
||||
private int difference(int value1, int value2) {
|
||||
int abs1 = Math.abs(value1);
|
||||
int abs2 = Math.abs(value2);
|
||||
if (abs1 > abs2) {
|
||||
return abs1 - abs2;
|
||||
}
|
||||
return abs2 - abs1;
|
||||
}
|
||||
// for debug
|
||||
// private static List<GGlassPanePainter> painters = new ArrayList<>();
|
||||
|
||||
/** Paints shapes used by this class (useful for debugging) */
|
||||
@SuppressWarnings("unused")
|
||||
@@ -350,23 +329,27 @@ public class PopupWindow {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(GGlassPane glassPane, Graphics graphics) {
|
||||
public void paint(GGlassPane glassPane, Graphics g) {
|
||||
|
||||
// bounds of the popup and the mouse neutral zone
|
||||
Rectangle r = bounds;
|
||||
Point p = new Point(r.getLocation());
|
||||
SwingUtilities.convertPointFromScreen(p, glassPane);
|
||||
if (bounds != null) {
|
||||
Rectangle r = bounds;
|
||||
Point p = new Point(r.getLocation());
|
||||
SwingUtilities.convertPointFromScreen(p, glassPane);
|
||||
|
||||
Color c = new Color(50, 50, 200, 125);
|
||||
graphics.setColor(c);
|
||||
graphics.fillRect(p.x, p.y, r.width, r.height);
|
||||
Color c = new Color(50, 50, 200, 125);
|
||||
g.setColor(c);
|
||||
g.fillRect(p.x, p.y, r.width, r.height);
|
||||
}
|
||||
|
||||
// show where the user hovered
|
||||
p = sourceEvent.getPoint();
|
||||
p = SwingUtilities.convertPoint(sourceEvent.getComponent(), p.x, p.y, glassPane);
|
||||
graphics.setColor(Color.RED);
|
||||
int offset = 10;
|
||||
graphics.fillRect(p.x - offset, p.y - offset, (offset * 2), (offset * 2));
|
||||
if (sourceEvent != null) {
|
||||
Point p = sourceEvent.getPoint();
|
||||
p = SwingUtilities.convertPoint(sourceEvent.getComponent(), p.x, p.y, glassPane);
|
||||
g.setColor(Color.RED);
|
||||
int offset = 10;
|
||||
g.fillRect(p.x - offset, p.y - offset, (offset * 2), (offset * 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.widgets.shapes;
|
||||
|
||||
/**
|
||||
* Specifies location and metrics for {@link PopupWindowPlacer}.
|
||||
*/
|
||||
public enum Location {
|
||||
LEFT, RIGHT, TOP, BOTTOM, CENTER;
|
||||
|
||||
static {
|
||||
LEFT.set(false, true, RIGHT, TOP);
|
||||
RIGHT.set(true, true, LEFT, BOTTOM);
|
||||
TOP.set(false, false, BOTTOM, RIGHT);
|
||||
BOTTOM.set(true, false, TOP, LEFT);
|
||||
CENTER.set(null, null, null, null);
|
||||
}
|
||||
|
||||
// Tri-valued value: false === Lesser, and null === Center
|
||||
private Boolean isGreater;
|
||||
// Tri-valued value: false === Vertical, and null === either (e.g., Center)
|
||||
// Means it is a measure of horizontal (e.g., left right) as opposed to a left edge, which
|
||||
// is a vertical line (the minor elements which describe location on this edge are vertical).
|
||||
private Boolean isHorizontal;
|
||||
private Location match;
|
||||
private Location clockwiseNext;
|
||||
|
||||
private void set(Boolean isGreater, Boolean isHorizontal, Location match,
|
||||
Location clockwiseNext) {
|
||||
this.isGreater = isGreater;
|
||||
this.isHorizontal = isHorizontal;
|
||||
this.match = match;
|
||||
this.clockwiseNext = clockwiseNext;
|
||||
}
|
||||
|
||||
public boolean isGreater() {
|
||||
return isGreater != null && isGreater;
|
||||
}
|
||||
|
||||
public boolean isLesser() {
|
||||
return isGreater != null && !isGreater;
|
||||
}
|
||||
|
||||
public boolean isCenter() {
|
||||
return isGreater == null;
|
||||
}
|
||||
|
||||
public Location match() {
|
||||
return match;
|
||||
}
|
||||
|
||||
public Location clockwise() {
|
||||
return clockwiseNext;
|
||||
}
|
||||
|
||||
public Location counterClockwise() {
|
||||
return clockwiseNext.match();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes "this" is a major axis, and tells whether the minor axis argument is valid for
|
||||
* the major value. Cannot have both major and minor be the same horizontal/vertical bearing.
|
||||
* Note that {@link #CENTER} can be horizontal or vertical, so this method should not count
|
||||
* this value as a bad minor value, as it also represents a good value.
|
||||
* @param minor the minor value to check
|
||||
* @return true if valid.
|
||||
*/
|
||||
public boolean validMinor(Location minor) {
|
||||
return isHorizontal() && minor.isVertical() || isVertical() && minor.isHorizontal();
|
||||
}
|
||||
|
||||
public boolean isHorizontal() {
|
||||
return isHorizontal == null || isHorizontal;
|
||||
}
|
||||
|
||||
public boolean isVertical() {
|
||||
return isHorizontal == null || !isHorizontal;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,895 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.widgets.shapes;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* This class places a rectangle on the boundary of an inner bounds area, such that it is not
|
||||
* placed outside of an outer boundary. It takes the concept of trying to make the placement at
|
||||
* the closest distance, but preferring certain sides or angles of approach in iterating a
|
||||
* solution. However, we reduce this concept down to a very simple form where iteration is not
|
||||
* needed because we are basing the algorithm on a geometric model that has explicit solutions
|
||||
* (for example, instead of picking a starting point around the perimeter and rotating
|
||||
* counter-clockwise to find a fit, or, for example, creating a grid of placements and choosing
|
||||
* the one that is closest but yet has preferences on one side or another). From the geometric
|
||||
* model, we can, instead, calculate the first location that will fit with a preferred boundary
|
||||
* location, such as fit on the right side of the context area, near the bottom. We could have
|
||||
* chosen to iterate through the areas in a counter-clockwise fashion, but by using a builder
|
||||
* model, we give the user more control of the order of choice.
|
||||
* For example, the user might first prefer the right side near the bottom, then the left side near
|
||||
* the bottom, followed by the top near the right, and then the bottom near the right.
|
||||
* <p>
|
||||
* This first drawing shows the overall context of the inner bounds within an outer bounds along
|
||||
* with a good placement and a bad placement that violates the outer bounds.
|
||||
* <pre>
|
||||
*
|
||||
* +-----------------------------------------------+
|
||||
* | outer |
|
||||
* | |
|
||||
* | |
|
||||
* | +------------------+ |
|
||||
* | | good | |
|
||||
* | | placement | |
|
||||
* | | | |
|
||||
* | +------------------+---------+ |
|
||||
* | | | |
|
||||
* | | inner | |
|
||||
* | | | |
|
||||
* | +---------+------------------+
|
||||
* | | bad | |
|
||||
* | | placement |
|
||||
* +-----------------------------------+-----------+ |
|
||||
* +------------------+
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* The next two drawings show the LEFT and RIGHT edges with nominal locations of TOP, CENTER, and
|
||||
* BOTTOM placements and the TOP and BOTTOM edges with nominal location of LEFT, CENTER, and
|
||||
* RIGHT placements. There are a total of eight of these locations ("cells") around the inner
|
||||
* bounds.
|
||||
* <pre>
|
||||
*
|
||||
* LEFT RIGHT
|
||||
* +---------------+ +---------------+
|
||||
* | | | |
|
||||
* | TOP | | TOP |
|
||||
* | | | |
|
||||
* +---------------X---------------X---------------+
|
||||
* | | | |
|
||||
* | CENTER X inner X CENTER |
|
||||
* | | | |
|
||||
* +---------------X---------------X---------------+
|
||||
* | | | |
|
||||
* | BOTTOM | | BOTTOM |
|
||||
* | | | |
|
||||
* +---------------+ +---------------+
|
||||
*
|
||||
*
|
||||
* +---------------+---------------+---------------+
|
||||
* | | | |
|
||||
* | LEFT | CENTER | RIGHT | TOP
|
||||
* | | | |
|
||||
* +---------------X-------X-------X---------------+
|
||||
* | |
|
||||
* | inner |
|
||||
* | |
|
||||
* +---------------X-------X-------X---------------+
|
||||
* | | | |
|
||||
* | LEFT | CENTER | RIGHT | BOTTOM
|
||||
* | | | |
|
||||
* +---------------+---------------+---------------+
|
||||
*
|
||||
* </pre>
|
||||
* <p>
|
||||
*
|
||||
* These cells are shown in their nominal placement locations (where they touch the inner bounds,
|
||||
* marked with an X). However we will shift these locations by particular amounts so that these
|
||||
* locations still fit within the outer bounds. For instance, if we allow the BOTTOM cell
|
||||
* on the LEFT edge to be shifted up far enough such that it fits the lower edge of the outer
|
||||
* bounds, we limit this shift if it reaches the nominal placement of another specified cell
|
||||
* (CENTER or TOP) on that edge. If a solution is not found before the limit is reached, the
|
||||
* placement fails.
|
||||
* <p>
|
||||
* If the chosen cell is a CENTER cell, then it could shift up or down, depending on the
|
||||
* circumstances and the parameters applied.
|
||||
* <p>
|
||||
* These placements and shifts are controlled by specifying the <B>major</B> and <B>minorBegin</B>
|
||||
* and <B>minorEnd</B> {@link Location Locations}. The major Location specifies the <B>edge</B>
|
||||
* for an {@link EdgePopupPlacer} and the minorBegin Location specifies the placement <B>cell</B>
|
||||
* on this edge and the minorEnds specifies the last cell (amount of shift allowed), starting
|
||||
* from the minorBegin Location. For a CENTER minorBeing Location, the minorEnd cell may be
|
||||
* any of the three allowed Locations on that major edge as well as null, representing that a
|
||||
* shift is allowed in either direction. When the minorEnd Location is set to the minorBegin
|
||||
* Location, then no shift is permitted.
|
||||
* <p>
|
||||
* Combinations of these placement attempts can be put together to create more complex strategies.
|
||||
* See {@link PopupWindowPlacerBuilder} for examples of these.
|
||||
* <p>
|
||||
* There are also {@link LeastOverlapCornerPopupWindowPlacer} and
|
||||
* {@link ThrowsAssertExceptionPlacer}, for instance, that do not follow the same cell scheme.
|
||||
* The first of these tries to make the placement at each of the corners of the inner
|
||||
* bounds, but shifts these placements to fit the outer bounds in such a way that the inner
|
||||
* bounds area may be occluded. The placement on the corner which overlaps the least amount of
|
||||
* the inner bounds area is chosen. The second of these placers automatically throws an
|
||||
* {@link AssertException}. It is intended to be used in a builder model in which a sequence of
|
||||
* placement attempts are made until good solution is found or until a null value is returned.
|
||||
* 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 {
|
||||
|
||||
protected Location major;
|
||||
protected Location minorBegin;
|
||||
protected Location minorEnd;
|
||||
|
||||
private PopupWindowPlacer next = null;
|
||||
|
||||
/**
|
||||
* Constructor only for classes that do not use placement preferences
|
||||
*/
|
||||
public PopupWindowPlacer() {
|
||||
// Only for implementations that do not use placement preferences
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor only for classes that specify major edge and minor begin and end location
|
||||
* on that edge.
|
||||
* @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) {
|
||||
throw new IllegalArgumentException("Cannot use " + major + " for major edge.");
|
||||
}
|
||||
if (!major.validMinor(minorBegin)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid minor location for " + major + " edge: " + minorBegin);
|
||||
}
|
||||
if (!major.validMinor(minorEnd)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid minor location for " + major + " edge: " + minorEnd);
|
||||
}
|
||||
this.major = major;
|
||||
this.minorBegin = minorBegin;
|
||||
this.minorEnd = minorEnd;
|
||||
}
|
||||
|
||||
void setNext(PopupWindowPlacer next) {
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the placement Rectangle of toBePlaced Dimension for this PopupWindowPlacer. If it
|
||||
* cannot find a solution, it tries the {@link #next} PopupWindowPlacer and so forth until
|
||||
* there are no others available, upon which null is returned if there is no solution.
|
||||
* @param toBePlaced the Dimension
|
||||
* @param innerBounds the inner bounds Rectangle
|
||||
* @param outerBounds the out bounds in which the final result must fit
|
||||
* @return the placement Rectangle or null if extends outside the outerBounds
|
||||
*/
|
||||
public Rectangle getPlacement(Dimension toBePlaced, Rectangle innerBounds,
|
||||
Rectangle outerBounds) {
|
||||
Rectangle myPlacement = getMyPlacement(toBePlaced, innerBounds, outerBounds);
|
||||
//Msg.info(this, debugDump(myPlacement));
|
||||
if (myPlacement != null) {
|
||||
return myPlacement;
|
||||
}
|
||||
if (next != null) {
|
||||
return next.getPlacement(toBePlaced, innerBounds, outerBounds);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected abstract Rectangle getMyPlacement(Dimension toBePlaced, Rectangle innerBounds,
|
||||
Rectangle outerBounds);
|
||||
|
||||
/**
|
||||
* Returns a Rectangle solution for the placement of a toBePlaced Dimension.
|
||||
* <p>
|
||||
* When dealing with solutions for the top or bottom edge, we are considering <B>vertical</B>
|
||||
* to be the major axis with y/height values representing that axis, and <B>horizontal</B>
|
||||
* to be the minor axis with x/width values representing that axis. When dealing with
|
||||
* solutions for the left and right edge, these major and minor axes are switched.
|
||||
*
|
||||
* @param result the new instance of the resulting class type to be returned
|
||||
* @param toBePlaced the wrapped toBePlaced Dimension
|
||||
* @param context the wrapped context Rectangle
|
||||
* @param outer the wrapped outer boundsRectangle
|
||||
* @return the resultant wrapped Rectangle
|
||||
*/
|
||||
protected PositionableRectangle getPlacement(PositionableRectangle result,
|
||||
PositionableDimension toBePlaced, PositionableRectangle context,
|
||||
PositionableRectangle outer) {
|
||||
|
||||
// Test major axis edge
|
||||
int placementMajorCoordinate;
|
||||
if (major.isLesser()) {
|
||||
placementMajorCoordinate = getLesserLocation(context.getMajorCoordinate(),
|
||||
context.getMajorLength(), toBePlaced.getMajorLength());
|
||||
int shiftedPlacement =
|
||||
getLesserBoundedLocation(placementMajorCoordinate, outer.getMajorCoordinate());
|
||||
if (placementMajorCoordinate < shiftedPlacement) {
|
||||
return null; // no solution on edge
|
||||
}
|
||||
}
|
||||
else if (major.isGreater()) {
|
||||
placementMajorCoordinate = getGreaterLocation(context.getMajorCoordinate(),
|
||||
context.getMajorLength(), toBePlaced.getMajorLength());
|
||||
int shiftedPlacement = getGreaterBoundedLocation(placementMajorCoordinate,
|
||||
toBePlaced.getMajorLength(), outer.getMajorCoordinate(), outer.getMajorLength());
|
||||
if (placementMajorCoordinate > shiftedPlacement) {
|
||||
return null; // no solution on edge
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new AssertException("Should not get here.");
|
||||
}
|
||||
|
||||
// Find placement on the edge using minor axis
|
||||
Integer placementMinorCoordinate =
|
||||
getPlacement(toBePlaced.getMinorLength(), context.getMinorCoordinate(),
|
||||
context.getMinorLength(), outer.getMinorCoordinate(), outer.getMinorLength());
|
||||
if (placementMinorCoordinate == null) {
|
||||
return null; // no solution on edge
|
||||
}
|
||||
|
||||
result.set(placementMajorCoordinate, placementMinorCoordinate, toBePlaced);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* With all inputs on a line (one-dimensional), returns the placement for the minor axis.
|
||||
* In other words, this algorithm is used for both conditions: the major axis being horizontal
|
||||
* 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.
|
||||
* <p>
|
||||
* <B>Algorithm Design</B><p>
|
||||
* Note: smaller values are up and bigger values are down, in the presentation below.
|
||||
* <p>
|
||||
* In trying to allay some confusion (yes it can be confusing), note that for any given major
|
||||
* axis (say horizontal), this axis can portray values that are further right or further left.
|
||||
* This is why the left edge and right edge are noted by horizontal axes values... one
|
||||
* intersects the horizontal axis further to the left and the other intersects the axis
|
||||
* further to the right.
|
||||
* <p>
|
||||
* The location of placements on the left or right edges, however are noted by vertical axis
|
||||
* values, with TOP having a lesser value and bottom having a greater value. These locations
|
||||
* specified by the minor dimension, and are the "one dimension" that is the subject of this
|
||||
* placement algorithm.
|
||||
* <p>
|
||||
* The scenario with top and bottom edge reverses the major dimension to be vertical and the
|
||||
* minor dimension to be horizontal.
|
||||
* <p>
|
||||
* 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 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
|
||||
* dimension, and both the location and lengths of the context and outer bounds rectangles
|
||||
* for this one dimension.
|
||||
* <p>
|
||||
* The algorithm considers three main locations (cells) on this minor axis. When the minor
|
||||
* axis is vertical (which is our current scenario), they are TOP, CENTER, and BOTTOM. When
|
||||
* the minor axis is horizontal, they are LEFT, CENTER, and RIGHT. These locations are
|
||||
* nominal, but are also allowed to be shifted so that the placement fits within the outer
|
||||
* bounds. Thus, we have five key values, in which three have fixed relative placements
|
||||
* (when using <B>positive</B> lengths):
|
||||
* <pre>
|
||||
* <B>lesserLocation</B> (nominal placement on TOP or LEFT)
|
||||
* ≤
|
||||
* <B>centerLocation</B> (nominal placement such that the center of the context rectangle
|
||||
* and the center of the popup area align with each other)
|
||||
* ≤
|
||||
* <B>greaterLocation</B> (nominal placement on BOTTOM or RIGHT)
|
||||
* </pre>
|
||||
* and these two can be found at various placements amongst the other three:
|
||||
* <pre>
|
||||
* <B>lesserBoundedLocation ≥ lesserLocation</B> (lesserLocation shifted so TOP or
|
||||
* LEFT fits outer bounds)
|
||||
* <B>greaterBoundedLocation ≤ greaterLocation</B> (lesserLocation shifted BOTTOM or
|
||||
* RIGHT fits outer bounds)
|
||||
* </pre>
|
||||
* Note that with an ill-constructed scenario, as shown here, we return <B>no solution</B>:
|
||||
* <pre>
|
||||
* <B>greaterBoundedLocation</B>
|
||||
* <
|
||||
* <B>lesserBoundedLocation</B>
|
||||
* </pre>
|
||||
* Given a better-constructed scenario, the <B>lesserBoundedLocation</B> and
|
||||
* <B>greaterBoundedLocation</B> values can fall between the other three values at the following
|
||||
* possible locations:
|
||||
* <pre>
|
||||
* <B>lesserLocation</B>
|
||||
* <B>→</B> <B>lesserBoundedLocation</B> (≥ <B>lesserLocation</B>)
|
||||
* <B>→</B> <B>greaterBoundedLocation</B> (≤ <B>greaterLocation</B>)
|
||||
* <B>centerLocation</B>
|
||||
* <B>→</B> <B>lesserBoundedLocation</B> (≥ <B>lesserLocation</B>)
|
||||
* <B>→</B> <B>greaterBoundedLocation</B> (≤ <B>greaterLocation</B>)
|
||||
* <B>greaterLocation</B>
|
||||
* </pre>
|
||||
* These layout possibilities can be broken down into three possibilities...
|
||||
* <pre>
|
||||
* <B>lesserLocation</B>
|
||||
* <B>→</B> <B>lesserBoundedLocation</B> (≥ <B>lesserLocation</B>)
|
||||
* <B>→</B> <B>greaterBoundedLocation</B>
|
||||
* <B>centerLocation</B>
|
||||
* <B>greaterLocation</B>
|
||||
* ----------
|
||||
* if start is LESSER
|
||||
* if end is LESSER and lesserBoundedLocation != lesserLocation
|
||||
* no solution
|
||||
* else
|
||||
* solution is lesserBoundedLocation
|
||||
* else
|
||||
* if end is LESSER
|
||||
* solution is greaterBoundedLocation
|
||||
* else
|
||||
* no solution
|
||||
* </pre>
|
||||
* or
|
||||
* <pre>
|
||||
* <B>lesserLocation</B>
|
||||
* <B>→</B> <B>lesserBoundedLocation</B> (≥ <B>lesserLocation</B>)
|
||||
* <B>centerLocation</B>
|
||||
* <B>→</B> <B>greaterBoundedLocation</B> (≤ <B>greaterLocation</B>)
|
||||
* <B>greaterLocation</B>
|
||||
* ----------
|
||||
* if start is GREATER
|
||||
* if end is GREATER and greaterBoundedLocation != greaterLocation
|
||||
* no solution
|
||||
* else
|
||||
* solution is greaterBoundedLocation
|
||||
* else if start is LESSER
|
||||
* if end is LESSER and lesserBoundedLocation != lesserLocation
|
||||
* no solution
|
||||
* else
|
||||
* solution is lesserBoundedLocation
|
||||
* else
|
||||
* solution is centerLocation
|
||||
* </pre>
|
||||
* or
|
||||
* <pre>
|
||||
* <B>lesserLocation</B>
|
||||
* <B>centerLocation</B>
|
||||
* <B>→</B> <B>lesserBoundedLocation</B>
|
||||
* <B>→</B> <B>greaterBoundedLocation</B> (≤ <B>greaterLocation</B>)
|
||||
* <B>greaterLocation</B>
|
||||
* ----------
|
||||
* if start is GREATER
|
||||
* if end is GREATER and greaterBoundedLocation != greaterLocation
|
||||
* no solution
|
||||
* else
|
||||
* solution is greaterBoundedLocation
|
||||
* else
|
||||
* if end is GREATER
|
||||
* solution is lesserBoundedLocation
|
||||
* else
|
||||
* no solution
|
||||
* </pre>
|
||||
* The algorithm breaks down into these scenarios and presents the solution as required.
|
||||
* @param placementLength the length of the placement Dimension on the line
|
||||
* @param contextLocation location of the context Rectangle on the line
|
||||
* @param contextLength the length of the context Rectangle Dimension on the line
|
||||
* @param boundLocation location of the outer bounds Rectangle on the line
|
||||
* @param boundLength the length of the outer bounds Rectangle Dimension on the line
|
||||
* @return the resultant location on the line
|
||||
*/
|
||||
private Integer getPlacement(int placementLength, int contextLocation, int contextLength,
|
||||
int boundLocation, int boundLength) {
|
||||
int lesserLocation = getLesserLocation(contextLocation, contextLength, placementLength);
|
||||
int lesserBoundedLocation = getLesserBoundedLocation(lesserLocation, boundLocation);
|
||||
int greaterLocation = getGreaterLocation(contextLocation, contextLength, placementLength);
|
||||
int greaterBoundedLocation =
|
||||
getGreaterBoundedLocation(greaterLocation, placementLength, boundLocation, boundLength);
|
||||
|
||||
if (greaterBoundedLocation < lesserBoundedLocation) {
|
||||
return null; // no solution
|
||||
}
|
||||
|
||||
int centerLocation = getCenterLocation(contextLocation, contextLength, placementLength);
|
||||
|
||||
if (greaterBoundedLocation < centerLocation) {
|
||||
return getSolutionWhenGreaterBoundedLessThanCenter(lesserLocation,
|
||||
lesserBoundedLocation, greaterBoundedLocation);
|
||||
}
|
||||
|
||||
if (lesserBoundedLocation > centerLocation) {
|
||||
return getSolutionWhenLesserBoundedGreaterThanCenter(lesserBoundedLocation,
|
||||
greaterBoundedLocation, greaterLocation);
|
||||
}
|
||||
|
||||
return getSolutionWhenCenterBounded(lesserLocation, lesserBoundedLocation, centerLocation,
|
||||
greaterBoundedLocation, greaterLocation);
|
||||
}
|
||||
|
||||
private Integer getSolutionWhenGreaterBoundedLessThanCenter(int lesserLocation,
|
||||
int lesserBoundedLocation, int greaterBoundedLocation) {
|
||||
if (minorBegin.isLesser()) {
|
||||
if (minorEnd.isLesser() && lesserLocation != lesserBoundedLocation) {
|
||||
return null; // no solution
|
||||
}
|
||||
return lesserBoundedLocation;
|
||||
}
|
||||
if (minorEnd.isLesser()) {
|
||||
return greaterBoundedLocation;
|
||||
}
|
||||
return null; // no solution
|
||||
}
|
||||
|
||||
private Integer getSolutionWhenLesserBoundedGreaterThanCenter(int lesserBoundedLocation,
|
||||
int greaterBoundedLocation, int greaterLocation) {
|
||||
if (minorBegin.isGreater()) {
|
||||
if (minorEnd.isGreater() && greaterLocation != greaterBoundedLocation) {
|
||||
return null; // no solution
|
||||
}
|
||||
return greaterBoundedLocation;
|
||||
}
|
||||
if (minorEnd.isGreater()) {
|
||||
return lesserBoundedLocation;
|
||||
}
|
||||
return null; // no solution
|
||||
}
|
||||
|
||||
private Integer getSolutionWhenCenterBounded(int lesserLocation, int lesserBoundedLocation,
|
||||
int centerLocation, int greaterBoundedLocation, int greaterLocation) {
|
||||
if (minorBegin.isGreater()) {
|
||||
if (minorEnd.isGreater() && greaterLocation != greaterBoundedLocation) {
|
||||
return null; // no solution
|
||||
}
|
||||
return greaterBoundedLocation;
|
||||
}
|
||||
else if (minorBegin.isLesser()) {
|
||||
if (minorEnd.isLesser() && lesserLocation != lesserBoundedLocation) {
|
||||
return null; // no solution
|
||||
}
|
||||
return lesserBoundedLocation;
|
||||
}
|
||||
return centerLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* With all inputs on a line (one-dimensional), returns a location that is shifted enough from
|
||||
* the placementLocation such that the greater end of bounds specified by boundLocation
|
||||
* is not exceeded (i.e., the new location is not bigger than {@code #boundLocation}).
|
||||
*
|
||||
* @param placementLocation starting location that gets shifted
|
||||
* @param placementLength the length of the to-be-placed dimension on the (one-dimensional)
|
||||
* line
|
||||
* @param boundLocation the bounds on the line that must not be exceeded to the greater side
|
||||
* @param boundLength the length of the outer bounds dimension on the (one-dimensional) line
|
||||
* @return the shifted result
|
||||
*/
|
||||
protected int getGreaterBoundedLocation(int placementLocation, int placementLength,
|
||||
int boundLocation, int boundLength) {
|
||||
return Integer.min(placementLocation, boundLocation + boundLength - placementLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* With all inputs on a line (one-dimensional), returns a location that is shifted enough from
|
||||
* the placementLocation such that the lesser end of bounds specified by boundLocation
|
||||
* is not exceeded (i.e., the new location is not smaller than boundLocation).
|
||||
*
|
||||
* @param placementLocation starting location that gets shifted
|
||||
* @param boundLocation the bounds on the line that must not be exceeded to the lesser side
|
||||
* @return the shifted result
|
||||
*/
|
||||
protected int getLesserBoundedLocation(int placementLocation, int boundLocation) {
|
||||
return Integer.max(placementLocation, boundLocation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the placement on a line (one-dimensional) on the greater end of the context area.
|
||||
*
|
||||
* @param contextLocation the context location on the line
|
||||
* @param contextLength the context length on the line
|
||||
* @param placementLength the length of the to-be-place dimension on that line
|
||||
* @return the resultant placement on the line
|
||||
*/
|
||||
protected int getGreaterLocation(int contextLocation, int contextLength, int placementLength) {
|
||||
return contextLocation + contextLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the placement on a line (one-dimensional) on the lesser end of the context area.
|
||||
*
|
||||
* @param contextLocation the context location on the line
|
||||
* @param contextLength the context length on the line
|
||||
* @param placementLength the length of the to-be-place dimension on that line
|
||||
* @return the resultant placement on the line
|
||||
*/
|
||||
protected int getLesserLocation(int contextLocation, int contextLength, int placementLength) {
|
||||
return contextLocation - placementLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the placementLocation such that the midpoint of the context and the midpoint
|
||||
* of the placement are at the same point. Location and Length can either be an x value and
|
||||
* width or a y value and height.
|
||||
*
|
||||
* @param contextLocation the x or y value of the context, depending on if we are doing the
|
||||
* horizontal or vertical midpoint
|
||||
* @param contextLength the corresponding width (if dealing with x/horizontal midpoint) or
|
||||
* height (if dealing with y/vertical midpoint)
|
||||
* @param placementLength the corresponding height or width of the placement
|
||||
* @return the placement location (again x or y value)
|
||||
*/
|
||||
protected int getCenterLocation(int contextLocation, int contextLength, int placementLength) {
|
||||
return contextLocation + (contextLength - placementLength) / 2;
|
||||
}
|
||||
|
||||
/** Dumps some debug output about the current class and its placement result*/
|
||||
@SuppressWarnings("unused")
|
||||
private String debugDump(Rectangle placement) {
|
||||
return String.format("%s: %s(%s,%s)... placement %s", getClass().getSimpleName(), major,
|
||||
minorBegin, minorEnd, dumpRectangle(placement));
|
||||
}
|
||||
|
||||
/** Dumps a simple Rectangle output */
|
||||
private String dumpRectangle(Rectangle r) {
|
||||
if (r == null) {
|
||||
return "null";
|
||||
}
|
||||
return String.format("[x=%d,y=%d,width=%d,height=%d]", r.x, r.y, r.width, r.height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String name = getClass().getSimpleName();
|
||||
String specificName = name.replace(PopupWindowPlacer.class.getSimpleName(), "");
|
||||
String[] words = StringUtils.splitByCharacterTypeCamelCase(specificName);
|
||||
return StringUtils.join(words, ' ');
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Placer Classes
|
||||
//==================================================================================================
|
||||
|
||||
/**
|
||||
* Placer that attempts a placement on the <code>major</code> edge of the inner bounds, with
|
||||
* <code>minorBegin</code> specifying the preferred cell location at which to start the
|
||||
* placement attempt and <code>minorEnd</code> specifying that limit on the amount of shift
|
||||
* that is made in an attempt to make the placement fit within the outer bounds. The inner
|
||||
* bounds is not allowed to be violated.
|
||||
*/
|
||||
static class EdgePopupPlacer extends PopupWindowPlacer {
|
||||
public EdgePopupPlacer(Location major, Location minorBegin, Location minorEnd) {
|
||||
super(major, minorBegin, minorEnd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getMyPlacement(Dimension toBePlaced, Rectangle context, Rectangle outer) {
|
||||
if (major.isHorizontal()) {
|
||||
return getPlacement(new HorizontalMajorRectangle(),
|
||||
new HorizontalMajorDimension(toBePlaced), new HorizontalMajorRectangle(context),
|
||||
new HorizontalMajorRectangle(outer));
|
||||
}
|
||||
return getPlacement(new VerticalMajorRectangle(),
|
||||
new VerticalMajorDimension(toBePlaced), new VerticalMajorRectangle(context),
|
||||
new VerticalMajorRectangle(outer));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Placer picks corner with toBePlaced as the least overlap with innerBounds. In the case of a
|
||||
* tie, the tie-breaker is first in this order: Bottom Right, Bottom Left, Top Right, Top Left.
|
||||
*/
|
||||
static class LeastOverlapCornerPopupWindowPlacer extends PopupWindowPlacer {
|
||||
public LeastOverlapCornerPopupWindowPlacer() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Rectangle getMyPlacement(Dimension toBePlaced, Rectangle context, Rectangle outer) {
|
||||
|
||||
Rectangle bestRectangle = null;
|
||||
int bestArea = Integer.MAX_VALUE;
|
||||
|
||||
Rectangle rectangle;
|
||||
Rectangle intersection;
|
||||
int area;
|
||||
|
||||
int top = getLesserLocation(context.y, context.height, toBePlaced.height);
|
||||
int bottom = getGreaterLocation(context.y, context.height, toBePlaced.height);
|
||||
int left = getLesserLocation(context.x, context.width, toBePlaced.width);
|
||||
int right = getGreaterLocation(context.x, context.width, toBePlaced.width);
|
||||
|
||||
int topShifted = getLesserBoundedLocation(top, outer.y);
|
||||
int bottomShifted =
|
||||
getGreaterBoundedLocation(bottom, toBePlaced.height, outer.y, outer.height);
|
||||
int leftShifted = getLesserBoundedLocation(left, outer.x);
|
||||
int rightShifted =
|
||||
getGreaterBoundedLocation(right, toBePlaced.width, outer.x, outer.width);
|
||||
|
||||
if (bottomShifted < topShifted || rightShifted < leftShifted) {
|
||||
return null; // no solution fits within outer bounds
|
||||
}
|
||||
|
||||
// Bottom Right
|
||||
rectangle = new Rectangle(new Point(rightShifted, bottomShifted), toBePlaced);
|
||||
intersection = rectangle.intersection(context);
|
||||
area = intersection.width * intersection.height;
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
bestRectangle = rectangle;
|
||||
}
|
||||
|
||||
// Bottom Left
|
||||
rectangle = new Rectangle(new Point(leftShifted, bottomShifted), toBePlaced);
|
||||
intersection = rectangle.intersection(context);
|
||||
area = intersection.width * intersection.height;
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
bestRectangle = rectangle;
|
||||
}
|
||||
|
||||
// Top Right
|
||||
rectangle = new Rectangle(new Point(rightShifted, topShifted), toBePlaced);
|
||||
intersection = rectangle.intersection(context);
|
||||
area = intersection.width * intersection.height;
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
bestRectangle = rectangle;
|
||||
}
|
||||
|
||||
// Top Left
|
||||
rectangle = new Rectangle(new Point(leftShifted, topShifted), toBePlaced);
|
||||
intersection = rectangle.intersection(context);
|
||||
area = intersection.width * intersection.height;
|
||||
if (area < bestArea) {
|
||||
bestArea = area;
|
||||
bestRectangle = rectangle;
|
||||
}
|
||||
|
||||
return bestRectangle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer that throws an AssertException because no solution has
|
||||
* been found by the time this placer is tried. This is intended to be used when the client
|
||||
* has already guaranteed that there is a solution (i.e., this placer is been used and the
|
||||
* pop-up area will fit within the outer bounds).
|
||||
*/
|
||||
static class ThrowsAssertExceptionPlacer extends PopupWindowPlacer {
|
||||
@Override
|
||||
public Rectangle getMyPlacement(Dimension toBePlaced, Rectangle innerBounds,
|
||||
Rectangle outerBounds) {
|
||||
throw new AssertException("Unexpected popup placement error.");
|
||||
}
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Size and Shape Classes
|
||||
//==================================================================================================
|
||||
|
||||
private static abstract class PositionableDimension extends Dimension {
|
||||
|
||||
public PositionableDimension(Dimension dimension) {
|
||||
super(dimension);
|
||||
}
|
||||
|
||||
abstract int getMajorLength();
|
||||
|
||||
abstract void setMajorLength(int length);
|
||||
|
||||
abstract int getMinorLength();
|
||||
|
||||
abstract void setMinorLength(int length);
|
||||
}
|
||||
|
||||
private static class HorizontalMajorDimension extends PositionableDimension {
|
||||
|
||||
public HorizontalMajorDimension(Dimension dimension) {
|
||||
super(dimension);
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMajorLength() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMajorLength(int length) {
|
||||
this.width = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinorLength() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMinorLength(int length) {
|
||||
this.height = length;
|
||||
}
|
||||
}
|
||||
|
||||
private static class VerticalMajorDimension extends PositionableDimension {
|
||||
|
||||
public VerticalMajorDimension(Dimension dimension) {
|
||||
super(dimension);
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMajorLength() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMajorLength(int length) {
|
||||
this.height = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinorLength() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMinorLength(int length) {
|
||||
this.width = length;
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class PositionableRectangle extends Rectangle {
|
||||
|
||||
PositionableRectangle() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PositionableRectangle(Rectangle rectangle) {
|
||||
super(rectangle);
|
||||
}
|
||||
|
||||
public void set(int majorCoordinate, int minorCoordinate, PositionableDimension dimension) {
|
||||
setMajorCoordinate(majorCoordinate);
|
||||
setMinorCoordinate(minorCoordinate);
|
||||
setSize(dimension);
|
||||
}
|
||||
|
||||
abstract int getMajorCoordinate();
|
||||
|
||||
abstract void setMajorCoordinate(int coordinate);
|
||||
|
||||
abstract int getMinorCoordinate();
|
||||
|
||||
abstract void setMinorCoordinate(int coordinate);
|
||||
|
||||
abstract int getMajorLength();
|
||||
|
||||
abstract void setMajorLength(int length);
|
||||
|
||||
abstract int getMinorLength();
|
||||
|
||||
abstract void setMinorLength(int length);
|
||||
}
|
||||
|
||||
private static class HorizontalMajorRectangle extends PositionableRectangle {
|
||||
|
||||
HorizontalMajorRectangle() {
|
||||
super();
|
||||
}
|
||||
|
||||
public HorizontalMajorRectangle(Rectangle rectangle) {
|
||||
super(rectangle);
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMajorCoordinate() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMajorCoordinate(int coordinate) {
|
||||
this.x = coordinate;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinorCoordinate() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMinorCoordinate(int coordinate) {
|
||||
this.y = coordinate;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMajorLength() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMajorLength(int length) {
|
||||
this.width = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinorLength() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMinorLength(int length) {
|
||||
this.height = length;
|
||||
}
|
||||
}
|
||||
|
||||
private static class VerticalMajorRectangle extends PositionableRectangle {
|
||||
|
||||
VerticalMajorRectangle() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VerticalMajorRectangle(Rectangle rectangle) {
|
||||
super(rectangle);
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMajorCoordinate() {
|
||||
return this.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMajorCoordinate(int coordinate) {
|
||||
this.y = coordinate;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinorCoordinate() {
|
||||
return this.x;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMinorCoordinate(int coordinate) {
|
||||
this.x = coordinate;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMajorLength() {
|
||||
return this.height;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMajorLength(int length) {
|
||||
this.height = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getMinorLength() {
|
||||
return this.width;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setMinorLength(int length) {
|
||||
this.width = length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.widgets.shapes;
|
||||
|
||||
import docking.widgets.shapes.PopupWindowPlacer.*;
|
||||
|
||||
/**
|
||||
* This class builds a PopWindowPlacer that can have subsequent PopWindowPlacers.
|
||||
* <p>
|
||||
* General categories of placers available are <B>edge</B> placers, <B>overlapped-corner</B>
|
||||
* placers, and a clean-up <B>assert</B> placer. Additionally, there are <B>rotational</B> placers
|
||||
* that are composed of edge placers.
|
||||
* <p>
|
||||
* <BR>
|
||||
* <BR>
|
||||
*
|
||||
* <H1>Edge Placers</H1>
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* flexibility in making the placement. One such cell is the TOP Location of the rightEdge,
|
||||
* specified by <code>rightEdge(Location.TOP)</code>. If the placement does not quite fit this
|
||||
* cell because the optimal placement extend above the top of the screen, the placement may be
|
||||
* shifted down by a allowed amount so that it still fits. If more than the allowed amount is
|
||||
* needed, the placement fails.
|
||||
* <p>
|
||||
* Each edge placer takes a variable number of Location arguments. These arguments work in the
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* <H2>Two or More Location Arguments</H2>
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* the second argument specifies the nominal placement cell and the third argument specifies how
|
||||
* 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>).
|
||||
* <p>
|
||||
*
|
||||
* <H2>One Location Argument</H2>
|
||||
*
|
||||
* <p>
|
||||
* 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,
|
||||
* <code>rightEdge(Location.TOP)</code> is the same as
|
||||
* <code>rightEdge(Location.TOP, Location.CENTER)</code>. When the single argument is
|
||||
* <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.
|
||||
* <p>
|
||||
*
|
||||
* <H2>No Arguments</H2>
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
*
|
||||
* <H2>Examples</H2>
|
||||
*
|
||||
* <p>
|
||||
* 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:
|
||||
* <pre>
|
||||
* PopupWindowPlacer placer =
|
||||
* new PopupWindowPlacerBuilder()
|
||||
* .rightEdge(Location.BOTTOM,Location.BOTTOM)
|
||||
* .rightEdge(Location.TOP, Location.TOP)
|
||||
* .topEdge(Location.CENTER, Location.CENTER)
|
||||
* .build();</pre>
|
||||
* Builds a placer that attempts a placement on the right edge from bottom to top, followed by
|
||||
* the top edge from center to right, then center to left:
|
||||
* <pre>
|
||||
* PopupWindowPlacer placer =
|
||||
* new PopupWindowPlacerBuilder()
|
||||
* .rightEdge()
|
||||
* .topEdge(Location.CENTER);
|
||||
* .build();</pre>
|
||||
* <p>
|
||||
* <BR>
|
||||
* <BR>
|
||||
*
|
||||
* <H1>Rotational Placers</H1>
|
||||
*
|
||||
* <p>
|
||||
* There are clockwise and counter-clockwise rotational placers that built up from edge placers.
|
||||
* These are:
|
||||
* <pre>
|
||||
* rotateClockwise(Location major, Location minor)
|
||||
* rotateCounterClockwise(Location major, Location minor)
|
||||
* thenRotateClockwise()
|
||||
* thenRotateCounterClockwise()</pre>
|
||||
* The first two of these take two Location arguments the specify the starting cell. For instance,
|
||||
* <code>rotateClockwise(Location.BOTTOM, Location.RIGHT)</code>. This specifies a set of edge
|
||||
* placers that attempt placement starting from the specified cell, and making attempt in a
|
||||
* clockwise fashion until the starting cell is revisited, at which time the attempt fails if a
|
||||
* viable placement has not been found. The <code>rotateCounterClockwise</code> placer works the
|
||||
* same, but in a counter-clockwise fashion. The <code>thenRotateClockwise</code> and
|
||||
* <code>thenRotateCounterClockwise</code> placers are the same as the previous two placers
|
||||
* 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.
|
||||
* <p>
|
||||
* <BR>
|
||||
* <BR>
|
||||
*
|
||||
* <H1>Overlapping Corner Placer</H1>
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* as necessary to fit the screen bounds. The corner that overlaps the context area the least is
|
||||
* chosen as the solution placement corner. In case of a tie (e.g., no overlap on some corners),
|
||||
* the placement order chosen in this preference order: bottom right, bottom left, top right, and
|
||||
* top left. Unless ill-constructed (sized of context area, screen, and pop-up dimension), this
|
||||
* placer should always find a solution.
|
||||
* <p>
|
||||
* <BR>
|
||||
* <BR>
|
||||
*
|
||||
* <H1>Assert Placer</H1>
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* it is believed that a placement should have already been found, such as after the
|
||||
* <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.
|
||||
* <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
|
||||
* 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:
|
||||
* <pre>
|
||||
* PopupWindowPlacer placer =
|
||||
* new PopupWindowPlacerBuilder()
|
||||
* .rightEdge()
|
||||
* .leftEdge()
|
||||
* .topEdge()
|
||||
* .bottomEdge()
|
||||
* .leastOverlapCorner()
|
||||
* .throwsAssertException()
|
||||
* .build();</pre>
|
||||
* <p>
|
||||
* Builds a placer that first attempts each of the four major corners in a specific order, with no
|
||||
* shifting, followed by an assertion failure:
|
||||
* <pre>
|
||||
* PopupWindowPlacer placer =
|
||||
* new PopupWindowPlacerBuilder()
|
||||
* .rightEdge(Location.BOTTOM, Location.BOTTOM)
|
||||
* .leftEdge(Location.TOP, Location.TOP)
|
||||
* .rightEdge(Location.TOP, Location.TOP)
|
||||
* .leftEdge(Location.BOTTOM, Location.BOTTOM)
|
||||
* .throwsAssertException()
|
||||
* .build();</pre>
|
||||
* <p>
|
||||
* Builds a placer that attempt to make a placement at the bottom right corner, first shifting up
|
||||
* to the center location then shifting left to the center location, then failing only with a
|
||||
* null return:
|
||||
* <pre>
|
||||
* PopupWindowPlacer placer =
|
||||
* new PopupWindowPlacerBuilder()
|
||||
* .rightEdge(Location.BOTTOM)
|
||||
* .bottomEdge(Location.RIGHT)
|
||||
* .build();</pre>
|
||||
* <p>
|
||||
* Builds a placer that attempts a placement at the top, left corner, the tries to make a placement
|
||||
* in a clockwise fashion, followed by a failure assert:
|
||||
* <pre>
|
||||
* PopupWindowPlacer placer =
|
||||
* new PopupWindowPlacerBuilder()
|
||||
* .topEdge(Location.LEFT, Location.LEFT)
|
||||
* .thenRotateClockwise()
|
||||
* .throwsAssertException()
|
||||
* .build();</pre>
|
||||
*
|
||||
* @see PopupWindowPlacer
|
||||
*/
|
||||
public class PopupWindowPlacerBuilder {
|
||||
|
||||
private PopupWindowPlacer head = null;
|
||||
private PopupWindowPlacer current = null;
|
||||
|
||||
/**
|
||||
* Builds the final PopupWindowPlacer.
|
||||
* @return the PopupWindowPlacer
|
||||
*/
|
||||
public PopupWindowPlacer build() {
|
||||
return head;
|
||||
}
|
||||
|
||||
private void add(PopupWindowPlacer next) {
|
||||
if (current == null) {
|
||||
current = next;
|
||||
head = current;
|
||||
}
|
||||
else {
|
||||
current.setNext(next);
|
||||
current = next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement at the right
|
||||
* edge of the inner bounds (context) without exceeding outer bounds (screen), using
|
||||
* an ordered, preferred placements on that edge. Invalid values will error.
|
||||
* @param minors the ordered, preferred placements on the edge. If not specified, goes from
|
||||
* greater-valued end of the edge to the lesser-valued end of the edge.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder rightEdge(Location... minors) {
|
||||
return edge(Location.RIGHT, minors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement at the left
|
||||
* edge of the inner bounds (context) without exceeding outer bounds (screen), using
|
||||
* an ordered, preferred placements on that edge. Invalid values will error.
|
||||
* @param minors the ordered, preferred placements on the edge. If not specified, goes from
|
||||
* greater-valued end of the edge to the lesser-valued end of the edge.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder leftEdge(Location... minors) {
|
||||
return edge(Location.LEFT, minors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement at the bottom
|
||||
* edge of the inner bounds (context) without exceeding outer bounds (screen), using
|
||||
* an ordered, preferred placements on that edge. Invalid values will error.
|
||||
* @param minors the ordered, preferred placements on the edge. If not specified, goes from
|
||||
* greater-valued end of the edge to the lesser-valued end of the edge.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder bottomEdge(Location... minors) {
|
||||
return edge(Location.BOTTOM, minors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement at the top
|
||||
* edge of the inner bounds (context) without exceeding outer bounds (screen), using
|
||||
* an ordered, preferred placements on that edge. Invalid values will error.
|
||||
* @param minors the ordered, preferred placements on the edge. If not specified, goes from
|
||||
* greater-valued end of the edge to the lesser-valued end of the edge.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder topEdge(Location... minors) {
|
||||
return edge(Location.TOP, minors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement on the major
|
||||
* edge of the inner bounds (context) without exceeding outer bounds (screen), using
|
||||
* an ordered, preferred placements on that edge. Invalid values will error.
|
||||
* @param major the major edge of the context area
|
||||
* @param minors the ordered, preferred placements on the edge. If not specified, goes from
|
||||
* greater-valued end of the edge to the lesser-valued end of the edge.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder edge(Location major, Location... minors) {
|
||||
if (minors.length > 3) {
|
||||
throw new IllegalArgumentException("Too many preferred Locations: " + minors);
|
||||
}
|
||||
for (Location minor : minors) {
|
||||
if (!major.validMinor(minor)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Preferred Location " + minor + " is not valid for " + major + " edge.");
|
||||
}
|
||||
}
|
||||
|
||||
if (minors.length == 0) {
|
||||
// We are defaulting this as greater to lesser
|
||||
if (major.isHorizontal()) {
|
||||
add(new EdgePopupPlacer(major, Location.BOTTOM, Location.TOP));
|
||||
}
|
||||
else {
|
||||
add(new EdgePopupPlacer(major, Location.RIGHT, Location.LEFT));
|
||||
}
|
||||
}
|
||||
else if (minors.length == 1) {
|
||||
if (minors[0] == Location.CENTER) {
|
||||
// Trying center to greater and then center to lesser.
|
||||
if (major.isHorizontal()) {
|
||||
add(new EdgePopupPlacer(major, minors[0], Location.BOTTOM));
|
||||
add(new EdgePopupPlacer(major, minors[0], Location.TOP));
|
||||
}
|
||||
else {
|
||||
add(new EdgePopupPlacer(major, minors[0], Location.RIGHT));
|
||||
add(new EdgePopupPlacer(major, minors[0], Location.LEFT));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Only looking from greater/lesser to the the center.
|
||||
add(new EdgePopupPlacer(major, minors[0], Location.CENTER));
|
||||
}
|
||||
}
|
||||
else { // Since we tested minors.length > 3 above, then we know we must have 2 or 3
|
||||
for (int i = 0; i < minors.length - 1; i++) {
|
||||
add(new EdgePopupPlacer(major, minors[i], minors[i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement by starting at
|
||||
* the last-used {@code majorBegin} and {@code minorBegin} and continues clockwise
|
||||
* to find a solution. If there was no last-used location set, then BOTTOM, RIGHT is used.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder thenRotateClockwise() {
|
||||
if (current == null) {
|
||||
return rotateClockwise(Location.BOTTOM, Location.RIGHT);
|
||||
}
|
||||
return rotateClockwise(current.major, current.minorBegin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement by starting at
|
||||
* a point specified by {@code majorBegin} and {@code minorBegin} and continues
|
||||
* clockwise to find a solution.
|
||||
* @param majorBegin the major coordinate location of the starting point
|
||||
* @param minorBegin the minor coordinate location of the starting point
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder rotateClockwise(Location majorBegin, Location minorBegin) {
|
||||
Location major = majorBegin;
|
||||
Location minor = minorBegin;
|
||||
do {
|
||||
add(new EdgePopupPlacer(major, minor, major.clockwise()));
|
||||
minor = major;
|
||||
major = major.clockwise();
|
||||
}
|
||||
while (major != majorBegin);
|
||||
if (minor != minorBegin) {
|
||||
// Does remaining portion of initial edge, but repeats first location.
|
||||
// So if starting at BOTTOM CENTER, will repeat that location in the last partial edge
|
||||
add(new EdgePopupPlacer(major, minor, minorBegin));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement by starting at
|
||||
* the last-used {@code majorBegin} and {@code minorBegin} and continues counter-clockwise
|
||||
* to find a solution. If there was no last-used location set, then RIGHT, BOTTOM is used.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder thenRotateCounterClockwise() {
|
||||
if (current == null) {
|
||||
return rotateCounterClockwise(Location.RIGHT, Location.BOTTOM);
|
||||
}
|
||||
return rotateCounterClockwise(current.major, current.minorBegin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement by starting at
|
||||
* a point specified by {@code majorBegin} and {@code minorBegin} and continues
|
||||
* counter-clockwise to find a solution.
|
||||
* @param majorBegin the major coordinate location of the starting point
|
||||
* @param minorBegin the minor coordinate location of the starting point
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder rotateCounterClockwise(Location majorBegin,
|
||||
Location minorBegin) {
|
||||
Location major = majorBegin;
|
||||
Location minor = minorBegin;
|
||||
do {
|
||||
add(new EdgePopupPlacer(major, minor, major.counterClockwise()));
|
||||
minor = major;
|
||||
major = major.counterClockwise();
|
||||
}
|
||||
while (major != majorBegin);
|
||||
if (minor != minorBegin) {
|
||||
// Does remaining portion of initial edge, but repeats first location.
|
||||
// So if starting at BOTTOM CENTER, will repeat that location in the last partial edge
|
||||
add(new EdgePopupPlacer(major, minor, minorBegin));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer to be one that tries to make the placement that is
|
||||
* allowed to overlap the inner bounds, but with the least overlap area. Tie-breaker
|
||||
* order is first in this order: Bottom Right, Bottom Left, Top Right, Top Left.
|
||||
* <p>
|
||||
* Should never return null, except if using impractical parameters, such as using
|
||||
* outer bounds that are smaller than inner bounds.
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder leastOverlapCorner() {
|
||||
add(new LeastOverlapCornerPopupWindowPlacer());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the next PopupWindowPlacer that throws an AssertException because no solution has
|
||||
* been found by the time this placer is tried. This is intended to be used when the coder
|
||||
* has already guaranteed that there is a solution (i.e., the {@link #leastOverlapCorner()}
|
||||
* placer has been used and the pop-up area will fit within the outer bounds).
|
||||
* @return this builder
|
||||
*/
|
||||
public PopupWindowPlacerBuilder throwsAssertException() {
|
||||
add(new ThrowsAssertExceptionPlacer());
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package docking.widgets.shapes;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
public class PopupWindowPlacerTest extends AbstractGenericTest {
|
||||
|
||||
private Rectangle screen = new Rectangle(0, 0, 2000, 1000);
|
||||
|
||||
// This is overly large for testing with a common context
|
||||
private Rectangle center = new Rectangle(200, 200, 1600, 600);
|
||||
private Dimension popup = new Dimension(100, 100);
|
||||
private Dimension popupBig = new Dimension(250, 250);
|
||||
private Dimension popupHugeWidth = new Dimension(screen.width - 100, 100);
|
||||
private Dimension popupHugeHeight = new Dimension(100, screen.height - 100);
|
||||
|
||||
@Test
|
||||
public void testLeftmostTop() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().topEdge(Location.LEFT).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x - popup.width, center.y - popup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperLeft() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leftEdge(Location.TOP).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x - popup.width, center.y - popup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeftmostBottom() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().bottomEdge(Location.LEFT).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x - popup.width, center.y + center.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerLeft() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leftEdge(Location.BOTTOM).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x - popup.width, center.y + center.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightmostTop() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().topEdge(Location.RIGHT).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x + center.width, center.y - popup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperRight() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().rightEdge(Location.TOP).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x + center.width, center.y - popup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightmostBottom() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.RIGHT).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x + center.width, center.y + center.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerRight() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.BOTTOM).build();
|
||||
Rectangle placement = placer.getPlacement(popup, center, screen);
|
||||
Point expected = new Point(center.x + center.width, center.y + center.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeftmostTopNeedsShift() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().topEdge(Location.LEFT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
int x = Integer.max(center.x - widePopup.width, screen.x);
|
||||
Point expected = new Point(x, center.y - widePopup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, widePopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeftmostTopNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().topEdge(Location.LEFT, Location.LEFT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperLeftNeedsShift() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leftEdge(Location.TOP).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
int y = Integer.max(center.y - tallPopup.height, screen.y);
|
||||
Point expected = new Point(center.x - tallPopup.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, tallPopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperLeftNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().leftEdge(Location.TOP, Location.TOP).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeftmostBottomNeedsShift() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().bottomEdge(Location.LEFT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
int x = Integer.max(center.x - widePopup.width, screen.x);
|
||||
Point expected = new Point(x, center.y + center.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, widePopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeftmostBottomNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.LEFT, Location.LEFT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerLeftNeedsShift() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leftEdge(Location.BOTTOM).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
int y = Integer.min(center.y + center.height, screen.y + screen.height - tallPopup.height);
|
||||
Point expected = new Point(center.x - tallPopup.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, tallPopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerLeftNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().leftEdge(Location.BOTTOM, Location.BOTTOM).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightmostTopNeedsShift() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().topEdge(Location.RIGHT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
int x = Integer.min(center.x + center.width, screen.x + screen.width - widePopup.width);
|
||||
Point expected = new Point(x, center.y - widePopup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, widePopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightmostTopNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().topEdge(Location.RIGHT, Location.RIGHT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperRightNeedsShift() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().rightEdge(Location.TOP).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
int y = Integer.max(center.y - tallPopup.height, screen.y);
|
||||
Point expected = new Point(center.x + center.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, tallPopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpperRightNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.TOP, Location.TOP).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightmostBottomNeedsShift() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.RIGHT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
int x = Integer.min(center.x + center.width, screen.x + screen.width - widePopup.width);
|
||||
Point expected = new Point(x, center.y + center.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, widePopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightmostBottomNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.RIGHT, Location.RIGHT).build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerRightNeedsShift() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.BOTTOM).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
int y = Integer.min(center.y + center.height, screen.y + screen.height - tallPopup.height);
|
||||
Point expected = new Point(center.x + center.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, tallPopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLowerRightNeedsShiftButCannot() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.BOTTOM, Location.BOTTOM).build();
|
||||
int delta = 200;
|
||||
Dimension tallPopup = new Dimension(popup.width, popup.height + delta);
|
||||
Rectangle placement = placer.getPlacement(tallPopup, center, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterTopNeedsShiftLeft() {
|
||||
int deltaX = 100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().topEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
int x = Integer.min(skewed.x + (skewed.width - popupHugeWidth.width) / 2,
|
||||
screen.x + screen.width - popupHugeWidth.width);
|
||||
Point expected = new Point(x, skewed.y - popupHugeWidth.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeWidth), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterTopNeedsShiftLeftButCannot() {
|
||||
int deltaX = 100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().topEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterTopNeedsShiftRight() {
|
||||
int deltaX = -100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().topEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
int x = Integer.max(skewed.x + (skewed.width - popupHugeWidth.width) / 2, screen.x);
|
||||
Point expected = new Point(x, skewed.y - popupHugeWidth.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeWidth), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterTopNeedsShiftRightButCannot() {
|
||||
int deltaX = -100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().topEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterBottomNeedsShiftLeft() {
|
||||
int deltaX = 100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
int x = Integer.min(skewed.x + (skewed.width - popupHugeWidth.width) / 2,
|
||||
screen.x + screen.width - popupHugeWidth.width);
|
||||
Point expected = new Point(x, skewed.y + skewed.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeWidth), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterBottomNeedsShiftLeftDownButCannot() {
|
||||
int deltaX = 100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterBottomNeedsShiftRight() {
|
||||
int deltaX = -100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
int x = Integer.max(skewed.x + (skewed.width - popupHugeWidth.width) / 2, screen.x);
|
||||
Point expected = new Point(x, center.y + center.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeWidth), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterBottomNeedsShiftRightDownButCannot() {
|
||||
int deltaX = -100;
|
||||
int deltaY = 0;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().bottomEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeWidth, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterLeftNeedsShiftUp() {
|
||||
int deltaX = 0;
|
||||
int deltaY = 100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leftEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
int y = Integer.min(skewed.y + (skewed.height - popupHugeHeight.height) / 2,
|
||||
screen.y + screen.height - popupHugeHeight.height);
|
||||
Point expected = new Point(center.x - popupHugeHeight.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeHeight), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterLeftNeedsShiftUpDownButCannot() {
|
||||
int deltaX = 0;
|
||||
int deltaY = 100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().leftEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterLeftNeedsShiftDown() {
|
||||
int deltaX = 0;
|
||||
int deltaY = -100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leftEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
int y = Integer.max(skewed.y + (skewed.height - popupHugeHeight.height) / 2, screen.y);
|
||||
Point expected = new Point(center.x - popupHugeHeight.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeHeight), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterLeftNeedsShiftDownDownButCannot() {
|
||||
int deltaX = 0;
|
||||
int deltaY = -100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().leftEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterRightNeedsShiftUp() {
|
||||
int deltaX = 0;
|
||||
int deltaY = 100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
int y = Integer.min(skewed.y + (skewed.height - popupHugeHeight.height) / 2,
|
||||
screen.y + screen.height - popupHugeHeight.height);
|
||||
Point expected = new Point(center.x + center.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeHeight), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterRightNeedsShiftUpDownButCannot() {
|
||||
int deltaX = 0;
|
||||
int deltaY = 100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterRightNeedsShiftDown() {
|
||||
int deltaX = 0;
|
||||
int deltaY = -100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
int y = Integer.max(skewed.y + (skewed.height - popupHugeHeight.height) / 2, screen.y);
|
||||
Point expected = new Point(center.x + center.width, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupHugeHeight), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCenterRightNeedsShiftDownButCannot() {
|
||||
int deltaX = 0;
|
||||
int deltaY = -100;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().rightEdge(Location.CENTER, Location.CENTER).build();
|
||||
Rectangle placement = placer.getPlacement(popupHugeHeight, skewed, screen);
|
||||
assertTrue(placement == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeastOverlapCornerTopLeft() {
|
||||
int deltaX = 1;
|
||||
int deltaY = 1;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leastOverlapCorner().build();
|
||||
Rectangle placement = placer.getPlacement(popupBig, skewed, screen);
|
||||
int x = Integer.max(skewed.x - popupBig.width, screen.x);
|
||||
int y = Integer.max(skewed.y - popupBig.height, screen.y);
|
||||
Point expected = new Point(x, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupBig), placement);
|
||||
}
|
||||
|
||||
// Overlapping corner tests
|
||||
@Test
|
||||
public void testLeastOverlapCornerBottomLeft() {
|
||||
int deltaX = 1;
|
||||
int deltaY = -1;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leastOverlapCorner().build();
|
||||
Rectangle placement = placer.getPlacement(popupBig, skewed, screen);
|
||||
int x = Integer.max(skewed.x - popupBig.width, screen.x);
|
||||
int y = Integer.min(skewed.y + skewed.height, screen.y + screen.height - popupBig.height);
|
||||
Point expected = new Point(x, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupBig), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeastOverlapCornerTopRight() {
|
||||
int deltaX = -1;
|
||||
int deltaY = 1;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leastOverlapCorner().build();
|
||||
Rectangle placement = placer.getPlacement(popupBig, skewed, screen);
|
||||
int x = Integer.min(skewed.x + skewed.width, screen.x + screen.width - popupBig.width);
|
||||
int y = Integer.max(skewed.y - popupBig.height, screen.y);
|
||||
Point expected = new Point(x, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupBig), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLeastOverlapCornerBottomRight() {
|
||||
int deltaX = -1;
|
||||
int deltaY = -1;
|
||||
Rectangle skewed =
|
||||
new Rectangle(center.x + deltaX, center.y + deltaY, center.width, center.height);
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().leastOverlapCorner().build();
|
||||
Rectangle placement = placer.getPlacement(popupBig, skewed, screen);
|
||||
int x = Integer.min(skewed.x + skewed.width, screen.x + screen.width - popupBig.width);
|
||||
int y = Integer.min(skewed.y + skewed.height, screen.y + screen.height - popupBig.height);
|
||||
Point expected = new Point(x, y);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, popupBig), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrowsAssertException() {
|
||||
PopupWindowPlacer placer = new PopupWindowPlacerBuilder().throwsAssertException().build();
|
||||
try {
|
||||
// Choice of context area and other parameters does not matter for this test
|
||||
placer.getPlacement(popup, center, screen);
|
||||
fail("Should not get here");
|
||||
}
|
||||
catch (AssertException e) {
|
||||
assertTrue("Unexpected popup placement error.".equals(e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// Some Combination tests
|
||||
@Test
|
||||
public void testLeftmostTopFixedRightmostTopShift() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().topEdge(Location.LEFT, Location.LEFT)
|
||||
.topEdge(Location.RIGHT)
|
||||
.build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
int x = Integer.min(center.x + center.width, screen.x + screen.width - widePopup.width);
|
||||
Point expected = new Point(x, center.y - widePopup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, widePopup), placement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRightmostTopFixedLeftmostTopShift() {
|
||||
PopupWindowPlacer placer =
|
||||
new PopupWindowPlacerBuilder().topEdge(Location.RIGHT, Location.RIGHT)
|
||||
.topEdge(Location.LEFT)
|
||||
.build();
|
||||
int delta = 200;
|
||||
Dimension widePopup = new Dimension(popup.width + delta, popup.height);
|
||||
Rectangle placement = placer.getPlacement(widePopup, center, screen);
|
||||
int x = Integer.max(center.x - widePopup.width, screen.x);
|
||||
Point expected = new Point(x, center.y - widePopup.height);
|
||||
assertTrue(screen.contains(placement));
|
||||
assertEquals(new Rectangle(expected, widePopup), placement);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user