-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JDK-8015739: Background of JInternalFrame is located out of JInternalFrame #10274
Conversation
👋 Welcome back honkar! A progress list of the required criteria for merging this PR into |
@honkar-jdk The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
@@ -217,16 +239,27 @@ public Insets getBorderInsets(Component c, Insets newInsets) { | |||
*/ | |||
@SuppressWarnings("serial") // Superclass is not serializable across versions | |||
public static class InternalFrameBorder extends AbstractBorder implements UIResource { | |||
private static final int corner = 14; | |||
private static int corner = 14; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before it was final, now it is not and it is static - means you are changing it down the line. What will happen if two instances will try to change it in a different ways - i.e. if there are more than one window and they are on a screens with different scale factors? I do not really get the logic - either this value is a local one and being calculated for each instance or it is a final and constant for all. And seeing how you update it continuously - will it not accumulate this change is paintBorder will be called multiple times?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@azuev-java Thank you for reviewing and bringing up - "multiple windows on multiple screens with different scaling" scenario. I missed accounting for the above scenario in the present fix. Yes, I believe that corner
should be local variable that is calculated depending on the screen's scale on which it is displayed.
I was also thinking if we can - may be precompute & store all the corner values in a HashMap
for all the scale values that we can expect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was also thinking if we can - may be precompute & store all the corner values in a
HashMap
for all the scale values that we can expect?
It depends on how often this value is being computed and how complicated the computation is. Since we request the current transform value anyways on every paint i do not see how accessing the HashMap would be more beneficial than the simple floating point multiplication and a rounding up the result. Just make the static final constant storing the default corner size and a local variable that stores the calculated current corner. You can try to use HashMap but i doubt it will make much difference performance wise. It is not a complex calculation. I might be wrong so you can try both ways and check for the performance impact.
return newInsets; | ||
} | ||
public Insets getBorderInsets(Component c, Insets newInsets) { | ||
newInsets.set(5, 5, 5, 5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default code inset is 4 so this formatting looks incorrect. Plus, i understand that you were trying to better indent the code but usually if code is not changed functionally we avoid changing the lines to make code annotation more informative.
g.drawLine( 0, 1, 0, h-2); | ||
g.drawLine( w-1, 1, w-1, h-2); | ||
g.drawLine( 1, h-1, w-2, h-1); | ||
Graphics2D g2d = (Graphics2D) g; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regardless, i see that absolutely similar code down this file for FrameBorder and DialogBorder. Are these UI elements suffer from the same issue or is it only limited to InternalFrame? If they do suffer from the same issue i would suggest either fixing it all together or at least submitting new bugs tracking existence of the problem. Of course problem might not be present for some reason in these components but it is worth checking it out while you are working on the code and remember all the details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@azuev-java Sure. I'll check if there is any observable distortion in other components and either make changes as part of this PR or track it under JDK-8282958. Currently there exists a generic umbrella issue created for border scaling issue, all the related issues are linked to it. The plan is to create a utility method (for common steps) that can be reused across components where this border rendering issue is observed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@azuev-java Tested FrameBorder and DialogBorder, they have a similar problem to JInternalFrameBorder (border scaling issue). I have created a new JBS issue - JDK-8294484 to track Frame and Dialog Borders of Metal LAF as there is a plan to create a common utility method and refactor the common steps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @honkar-jdk. Once the border handling is unified, fixing FrameBorder
and DialogBorder
will be easier and code duplication will be avoided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactoring to create a common utility method is being tracked by - JDK-8294680
// border and corner scaling | ||
corner = (int) Math.round(corner * at.getScaleX()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm likely missing something from the big picture but wouldn't this accumulate on every paintBorder()
invocation (into a static field)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stanio Hi, @azuev-java did bring up the same point earlier - #10274 (comment). In the recent commit, I have update the code to use a different local variable - scaledCorner
to compute and store the scaled value for each new instance.
* | ||
* @run main/othervm -Dsun.java2d.uiScale=1 InternalFrameBorderTest | ||
* @run main/othervm -Dsun.java2d.uiScale=2 InternalFrameBorderTest | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there any differences for the test instructions on mac and linux? If not then why do we keep two identical test headers instead of one that covers both mac and linux with tag @requires (os.family == "linux" | os.family == "mac")
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@azuev-java Thanks for catching it. Removed the redundant header.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me
@honkar-jdk This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 599 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@azuev-java, @alisenchung, @aivanov-jdk) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
import sun.swing.StringUIClientPropertyKey; | ||
import sun.swing.SwingUtilities2; | ||
|
||
import javax.swing.AbstractButton; | ||
import javax.swing.ButtonModel; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure if we all agree on the order of imports in client area, however, the common order is
import java.*;
// optional blank line
import javax.*;
// blank line
import sun.*; // other packages
// blank line
import static *; // if applicable
g.drawLine( 0, 0, width-1, 0); | ||
g.drawLine( 0, 1, 0, height-1); | ||
g.drawLine( width-1, 1, width-1, height-1); | ||
g.drawLine( 1, height-1, width-1, height-1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest removing the space after the opening parenthesis.
There's usually a space on either side of binary operators, the -
in this case.
I believe this is existing code which was just moved around, so maybe its style is not worth touching.
|
||
} | ||
// border and corner scaling | ||
int scaledCorner = (int) Math.round(corner * at.getScaleX()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a suggestion, you can call this variable corner
but make the constant above upper case CORNER
. This way the old code would remain basically the same and continue using corner
(which was previously a field).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java
Outdated
Show resolved
Hide resolved
} | ||
// for debugging purpose, saves screen capture when test fails. | ||
private static void saveScreenCapture(String filename) { | ||
BufferedImage image = robot.createScreenCapture(jFrame.getBounds()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we lose pixel details here. If uiScale > 1, createScreenCapture
scales down the captured image. Do we want to preserve the original image too? Robot.createMultiResolutionScreenCapture
can be useful.
test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java
Outdated
Show resolved
Hide resolved
Out of curiosity, can this test use |
src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java
Outdated
Show resolved
Hide resolved
g.drawLine( 1, height-1, width-1, height-1); | ||
|
||
// Draw the bulk of the border | ||
for (int i = 1; i <= loopCount; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we redo the drawing of this border to use Graphics2D fillRect instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was my initial thought too. But, as the loopCount won't be really large (Eg. it is 15 for 300% scaling) and it wouldn't affect the performance drastically, I have retained the original approach to draw the bulk of the border.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let us leave it as is at this time. There are enough changes.
At the same time, it's a good suggestion for optimization. We should consider it in a separate CR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, this code can be improved further using fillRect() (as a separate PR).
if (c instanceof JInternalFrame && ((JInternalFrame)c).isResizable()) { | ||
// Draw the Long highlight lines | ||
g.setColor(highlight); | ||
g.drawLine(scaledCorner + 1, midPoint+stkWidth, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spacing here and when drawing shadow lines is a bit inconsistent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alisenchung I tried by increasing and decreasing the line position a bit, the current combination provided optimal positioning of both the shadow and highlight line within the border for all the scales. The slight inconsistency in spacing is probably due to rounding losses in loop count, stroke width and corner. I'll check it again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I meant the actual code spacing, theres a space between scaledCorner and 1, but not space between midPoint and stkWidth
@aivanov-jdk Thank you for reviewing. I wanted to clarify whether you meant saving just the JInternalFrame into BufferedImage? |
@aivanov-jdk Regarding saving the screen capture -
|
Yes, just |
Could be… It shouldn't. Anyway, I have no problem with capturing the entire
This is definitely not what we want. It just up-scales or down-scales the current image stored in When you run in a HiDPI environment or with At the same time, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good to me, except for the comments I left on the coding style.
Yet there's one thing which I noticed in the rendered border. The top left corner has one pixel which is painted over the title bar. It's present in the original painting code. The top right corner has such a pixel too.
The updated code paints the top left corner with that pixel. Yet in the top right corner, it moved to left.
import javax.imageio.ImageIO; | ||
import javax.swing.JFrame; | ||
import javax.swing.JInternalFrame; | ||
import javax.swing.JLabel; | ||
import javax.swing.SwingUtilities; | ||
import javax.swing.UIManager; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather stick to the same order of imports in sources and tests. However, swing components used are more important.
if (Color.RED.equals(robot.getPixelColor( | ||
isVertical ? i : (iFrameLoc.x + MIDPOINT), | ||
isHorizontal ? i : (iFrameLoc.y + MIDPOINT)))) { | ||
saveScreenCapture(borderDirection + "_" + uiScale + ".png"); | ||
errorLog.append("uiScale: "+ uiScale + | ||
" Red background color" + " detected at " | ||
+ borderDirection + " border\n"); | ||
isVertical ? i : (iFrameLoc.x + MIDPOINT), | ||
isHorizontal ? i : (iFrameLoc.y + MIDPOINT)))) { | ||
saveScreenCapture(borderDirection + "_" + uiScale + ".png", uiScale); | ||
errorLog.append("At uiScale: "+ uiScale + | ||
", Red background color detected at " | ||
+ borderDirection + " border\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't look right either. The old code had only the body of if
incorrectly indented.
if (Color.RED.equals(robot.getPixelColor(
isVertical ? i : (iFrameLoc.x + MIDPOINT),
isHorizontal ? i : (iFrameLoc.y + MIDPOINT)))) {
saveScreenCapture(borderDirection + "_" + uiScale + ".png");
errorLog.append("uiScale: " + uiScale
+ " Red background color detected at "
+ borderDirection + " border\n");
Continuation lines are indented twice as much or to align with a level they're wrapped at. The example for the second case:
errorLog.append("uiScale: " + uiScale
+ " Red background color detected at "
+ borderDirection + " border\n");
Here, the operators on the wrapped lines align to the following column of the opening parenthesis for parameters of append
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my example, I put the operators on the continuation line, it's what Java Coding Style recommends. However, leaving the operators on the previous line is very common.
Here, you're mixing two styles: the first continuation line with append
parameter had +
at the end of the line, but the second line wrapped the +
operator to the next line.
test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java
Outdated
Show resolved
Hide resolved
test/jdk/javax/swing/JInternalFrame/InternalFrameBorderTest.java
Outdated
Show resolved
Hide resolved
Graphics2D g2d = (Graphics2D) scaledImage.getGraphics(); | ||
g2d.scale(scale, scale); | ||
g2d.drawImage(image, 0, 0, null); | ||
g2d.dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not make sense, it up-scales a down-scaled image. Either revert to the previous version or use createMultiResolutionScreenCapture
and save all resolution variants from the MultiResolutionImage
.
@aivanov-jdk In addition to review changes, the following changes were made to the test case:
|
errorLog.append("At uiScale: "+ uiScale + ", Red background color" | ||
+ " detected at " + cornerLocation + " corner\n"); | ||
|
||
for (int i = 0; i < 5; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (int i = 0; i < 5; i++) { | |
for (int i = 0; i < BORDER_THICKNESS; i++) { |
Is it the intention?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you update it to use BORDER_THICKNESS
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aivanov-jdk I was waiting on testing results to make the final update along with the above change.
* @param d number to be rounded | ||
* @return the rounded value | ||
*/ | ||
private static int roundHalfDown(double d) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sun.java2d.pipe.Region#clipRound() seems have the same purpose, but it also clip the double to int.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sun.java2d.pipe.Region#clipRound() seems have the same purpose, but it also clip the double to int.
Thank you, @mrserb. It's better to re-use the existing implementation. I tested with it, it gives the same result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mrserb Thank you for the suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess roundHalfDown
has been replaced with Region.clipRound
, so it needs to be removed.
Two edge cases issues were identified while testing with different frame sizes and on different platforms-
|
With the current update, on some scales the highlight and shadow lines are not positioned exactly at the middle of the border. This is being investigated currently. |
width = Region.clipRound(at.getScaleX() * w); | ||
height = Region.clipRound(at.getScaleY() * h); | ||
xtranslation = Region.clipRound(at.getScaleX() * x + at.getTranslateX()); | ||
ytranslation = Region.clipRound(at.getScaleY() * y + at.getTranslateY()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably should be refactored to something like this:
w = Region.dimAdd(x, Region.clipScale(w, scaleX)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mrserb Thank for the suggestion. Are you suggesting that same transformations be applied for width, height and additionally to x&y translations?
x = constrainX = (int) transform.getTranslateX(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The methods like Rigion.dimAdd/clipScale should be used everywhere the overflow is possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @mrserb, for your suggestion. I didn't know about those methods.
We already have JDK-8294680 to refactor this code. If you don't object, I'd rather postpone updating the calculations to a later time. We should absolutely re-use the existing code if it does the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @mrserb, for your suggestion. I didn't know about those methods.
We already have JDK-8294680 to refactor this code. If you don't object, I'd rather postpone updating the calculations to a later time. We should absolutely re-use the existing code if it does the same thing.
@aivanov-jdk I think we decided to retain roundHalfDown and make the changes when refactoring the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @mrserb, for your suggestion. I didn't know about those methods.
We already have JDK-8294680 to refactor this code. If you don't object, I'd rather postpone updating the calculations to a later time. We should absolutely re-use the existing code if it does the same thing.@aivanov-jdk I think we decided to retain roundHalfDown and make the changes when refactoring the code.
The comment above is about Rigion.dimAdd
and Rigion.clipScale
.
As for roundHalfDown
, you had replaced it with Region.clipRound
. When I was reading the code, I got the impression that you used Region.clipRound
everywhere but one place. Eventually, I see you've reverted that change, and you're using roundHalfDown
with no trace of clipRound
left to be found.
What I saw must have been coming from code comments rather than the real code.
The comment above confirms that using Region.clipRound
gives the same result:
The sun.java2d.pipe.Region#clipRound() seems have the same purpose, but it also clip the double to int.
… It's better to re-use the existing implementation. I tested with it, it gives the same result.
It went into #10681.
I just missed that you had reverted it back.
I guess it's not worth re-doing it once again. We'll handle it in JDK-8294680 too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I'll have it updated during JDK-8294680 changes.
g.drawLine( 0, 1, 0, h-2); | ||
g.drawLine( w-1, 1, w-1, h-2); | ||
g.drawLine( 1, h-1, w-2, h-1); | ||
Graphics2D g2d = (Graphics2D) g; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could be a DebugGraphics which is not a Graphics2D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the code will be refactored please double-check that the blind cast will not be used, "g" could DebugGraphics which is not a Graphics2D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for catching it. Made a note of it , to add it during refactoring changes - JDK-8294680
// skip resetting the transform | ||
resetTransform = (at.getShearX() == 0) && (at.getShearY() == 0); | ||
|
||
if (resetTransform) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please clarify why we should reset the current transform? How it will work if the user sets a transform and render this to the BufferedImage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On fractional scaling, painting of lines and borders have issues (lines seem to be separated or look uneven) as shown above, #10274 (comment). In order to paint them uniformly and next to each other, the idea is to render at 1x scaling, apply the required thickness/scaling to the lines at this point (when transform is reset) and then the original (old) transform is restored at the end.
This idea was referenced from a similar issue #7449 (comment).
While testing, following issues were observed -
|
@aivanov-jdk Fix has been updated since last approval. Can you please re-review. |
JInternalFrame background color seems to overflow into the border region. This issue is more prominently seen on Windows - Metal LAF (with fractional scaling, as shown below). The primary reason is border scaling issue as observed in - JDK-8279614
The fix involves a similar approach as described here #7449 (comment). The test checks the midpoint and corners of borders to check if the internal frame's background color is located out of JInternalFrame.
Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/10274/head:pull/10274
$ git checkout pull/10274
Update a local copy of the PR:
$ git checkout pull/10274
$ git pull https://git.openjdk.org/jdk pull/10274/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 10274
View PR using the GUI difftool:
$ git pr show -t 10274
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/10274.diff