Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -26,6 +26,7 @@
package javax.swing.filechooser;

import java.awt.Image;
import java.awt.image.AbstractMultiResolutionImage;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileNotFoundException;
Expand Down Expand Up @@ -255,6 +256,54 @@ public Icon getSystemIcon(File f) {
}
}

/**
* Icon for a file, directory, or folder as it would be displayed in
* a system file browser for the requested size.
*
* The default implementation gets information from the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* The default implementation gets information from the
* <p>The default implementation gets information from the

* <code>ShellFolder</code> class. Whenever possible the icon
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* <code>ShellFolder</code> class. Whenever possible the icon
* <code>ShellFolder</code> class. Whenever possible, the icon

Do we continue using <code> elements? I thought that {@code} is the preferred way to markup class names.

* returned will be a multi-resolution icon image,
* which will allow better scaling with different
* magnification factors.
*
* Example: <pre>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Example: <pre>
* <p>Example: <pre>

* FileSystemView fsv = FileSystemView.getFileSystemView();
* Icon icon = fsv.getSystemIcon("application.exe", 64);
* JLabel label = new JLabel(icon);
* </pre>
*
* @param f a <code>File</code> object
* @param size width and height of the icon in pixels
* @return an icon as it would be displayed by a native file chooser
* or null if invalid parameters are passed such as pointer to a
* non-existing file.
* @see JFileChooser#getIcon
* @see AbstractMultiResolutionImage
* @since 16
*/
public Icon getSystemIcon(File f, int size) {
if (f == null) {
return null;
}

ShellFolder sf;

try {
sf = ShellFolder.getShellFolder(f);
} catch (FileNotFoundException e) {
return null;
}

Image img = sf.getIcon(size);

if (img != null) {
return new ImageIcon(img, sf.getFolderType());
} else {
return UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon"
: "FileView.fileIcon");
}
}

/**
* On Windows, a file can appear in multiple folders, other than its
* parent directory in the filesystem. Folder could for example be the
Expand Down
11 changes: 10 additions & 1 deletion src/java.desktop/share/classes/sun/awt/shell/ShellFolder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -201,6 +201,15 @@ public Image getIcon(boolean getLargeIcon) {
return null;
}

/**
* Returns the icon of the specified size used to display this shell folder.
*
* @param size size of the icon > 0(Valid range: 1 to 256)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param size size of the icon > 0(Valid range: 1 to 256)
* @param size size of the icon > 0 (Valid range: 1 to 256)

* @return The icon of the specified size used to display this shell folder
*/
public Image getIcon(int size) {
return null;
}

// Static

Expand Down
172 changes: 126 additions & 46 deletions src/java.desktop/windows/classes/sun/awt/shell/Win32ShellFolder2.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -74,6 +74,16 @@
@SuppressWarnings("serial") // JDK-implementation class
final class Win32ShellFolder2 extends ShellFolder {

static final int SMALL_ICON_SIZE = 16;
static final int LARGE_ICON_SIZE = 32;
static final int MIN_QUALITY_ICON = 16;
static final int MAX_QUALITY_ICON = 256;
private final static int[] ICON_RESOLUTIONS
= {16, 24, 32, 48, 64, 72, 96, 128, 256};

static final int FILE_ICON_ID = 1;
static final int FOLDER_ICON_ID = 4;

private static native void initIDs();

static {
Expand Down Expand Up @@ -982,7 +992,10 @@ public String getExecutableType() {

// NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
private static native long extractIcon(long parentIShellFolder, long relativePIDL,
boolean getLargeIcon, boolean getDefaultIcon);
int size, boolean getDefaultIcon);

// NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details
private static native boolean hiResIconAvailable(long parentIShellFolder, long relativePIDL);

// Returns an icon from the Windows system icon list in the form of an HICON
private static native long getSystemIcon(int iconID);
Expand All @@ -1009,20 +1022,17 @@ private long getIShellIcon() {
return pIShellIcon;
}

private static Image makeIcon(long hIcon, boolean getLargeIcon) {
private static Image makeIcon(long hIcon, int size) {
if (hIcon != 0L && hIcon != -1L) {
// Get the bits. This has the side effect of setting the imageHash value for this object.
final int[] iconBits = getIconBits(hIcon);
if (iconBits != null) {
// icons are always square
final int size = (int) Math.sqrt(iconBits.length);
final int baseSize = getLargeIcon ? 32 : 16;
final int iconSize = (int) Math.sqrt(iconBits.length);
final BufferedImage img =
new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
img.setRGB(0, 0, size, size, iconBits, 0, size);
return size == baseSize
? img
: new MultiResolutionIconImage(baseSize, img);
new BufferedImage(iconSize, iconSize, BufferedImage.TYPE_INT_ARGB);
img.setRGB(0, 0, iconSize, iconSize, iconBits, 0, iconSize);
return img;
Comment on lines +1033 to +1035
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are cases where the size of the buffered image is different from the requested size. It could affect the layout because it breaks the assumption that the returned image has the requested size but it may be larger.

}
}
return null;
Expand All @@ -1034,11 +1044,13 @@ private static Image makeIcon(long hIcon, boolean getLargeIcon) {
*/
public Image getIcon(final boolean getLargeIcon) {
Image icon = getLargeIcon ? largeIcon : smallIcon;
int size = getLargeIcon ? LARGE_ICON_SIZE : SMALL_ICON_SIZE;
if (icon == null) {
icon =
invoke(new Callable<Image>() {
public Image call() {
Image newIcon = null;
Image newIcon2 = null;
if (isLink()) {
Win32ShellFolder2 folder = getLinkLocation(false);
if (folder != null && folder.isLibrary()) {
Expand All @@ -1063,33 +1075,40 @@ public Image call() {
newIcon = imageCache.get(Integer.valueOf(index));
if (newIcon == null) {
long hIcon = getIcon(getAbsolutePath(), getLargeIcon);
newIcon = makeIcon(hIcon, getLargeIcon);
newIcon = makeIcon(hIcon, getLargeIcon ? LARGE_ICON_SIZE : SMALL_ICON_SIZE);
disposeIcon(hIcon);
if (newIcon != null) {
imageCache.put(Integer.valueOf(index), newIcon);
}
}
}
}

if (newIcon == null) {
// These are only cached per object
long hIcon = extractIcon(getParentIShellFolder(),
getRelativePIDL(), getLargeIcon, false);
// E_PENDING: loading can take time so get the default
if(hIcon <= 0) {
hIcon = extractIcon(getParentIShellFolder(),
getRelativePIDL(), getLargeIcon, true);
if(hIcon <= 0) {
if (isDirectory()) {
return getShell32Icon(4, getLargeIcon);
if (newIcon != null) {
if (isLink()) {
imageCache = getLargeIcon ? smallLinkedSystemImages
: largeLinkedSystemImages;
} else {
return getShell32Icon(1, getLargeIcon);
imageCache = getLargeIcon ? smallSystemImages : largeSystemImages;
}
newIcon2 = imageCache.get(Integer.valueOf(index));
if (newIcon2 == null) {
long hIcon = getIcon(getAbsolutePath(), !getLargeIcon);
newIcon2 = makeIcon(hIcon, getLargeIcon ? SMALL_ICON_SIZE
: LARGE_ICON_SIZE);
disposeIcon(hIcon);
}
}

if (newIcon2 != null) {
Map<Integer, Image> bothIcons = new HashMap<>(2);
bothIcons.put(getLargeIcon? LARGE_ICON_SIZE : SMALL_ICON_SIZE, newIcon);
bothIcons.put(getLargeIcon? SMALL_ICON_SIZE : LARGE_ICON_SIZE, newIcon2);
newIcon = new MultiResolutionIconImage(getLargeIcon ? LARGE_ICON_SIZE
: SMALL_ICON_SIZE, bothIcons);
Comment on lines +1091 to +1105
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to refactor this code into a separate method which always fetches small and large icon and puts into a corresponding cache.

However, I still think there's not much value in getting smaller icon size in addition to larger one. Or does it provide an alternative image for the case where the system scaling is 200% and the window is moved to a monitor with scaling of 100%?

}
}
newIcon = makeIcon(hIcon, getLargeIcon);
disposeIcon(hIcon);
}

if (hiResIconAvailable(getParentIShellFolder(), getRelativePIDL()) || newIcon == null) {
newIcon = getIcon(getLargeIcon ? LARGE_ICON_SIZE : SMALL_ICON_SIZE);
}

if (newIcon == null) {
Expand All @@ -1098,33 +1117,73 @@ public Image call() {
return newIcon;
}
});
if (getLargeIcon) {
largeIcon = icon;
} else {
smallIcon = icon;
}
}
return icon;
}

/**
* @return The icon image of specified size used to display this shell folder
*/
public Image getIcon(int size) {
return invoke(() -> {
Image newIcon = null;
if (isLink()) {
Win32ShellFolder2 folder = getLinkLocation(false);
if (folder != null && folder.isLibrary()) {
return folder.getIcon(size);
}
}
Map<Integer, Image> multiResolutionIcon = new HashMap<>();
int start = size > MAX_QUALITY_ICON ? ICON_RESOLUTIONS.length - 1 : 0;
int increment = size > MAX_QUALITY_ICON ? -1 : 1;
int end = size > MAX_QUALITY_ICON ? -1 : ICON_RESOLUTIONS.length;
for (int i = start; i != end; i+=increment) {
int s = ICON_RESOLUTIONS[i];
if (size < MIN_QUALITY_ICON || size > MAX_QUALITY_ICON
|| (s >= size/2 && s <= size*2)) {
long hIcon = extractIcon(getParentIShellFolder(),
getRelativePIDL(), s, false);

// E_PENDING: loading can take time so get the default
if (hIcon <= 0) {
hIcon = extractIcon(getParentIShellFolder(),
getRelativePIDL(), s, true);
if (hIcon <= 0) {
if (isDirectory()) {
return getShell32Icon(FOLDER_ICON_ID, size);
} else {
return getShell32Icon(FILE_ICON_ID, size);
}
}
}
newIcon = makeIcon(hIcon, s);
disposeIcon(hIcon);

multiResolutionIcon.put(s, newIcon);
if (size < 8 || size > 256) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (size < 8 || size > 256) {
if (size < MIN_QUALITY_ICON || size > MAX_QUALITY_ICON) {

break;
}
}
}
return new MultiResolutionIconImage(size, multiResolutionIcon);
});
}

/**
* Gets an icon from the Windows system icon list as an {@code Image}
*/
static Image getSystemIcon(SystemIcon iconType) {
long hIcon = getSystemIcon(iconType.getIconID());
Image icon = makeIcon(hIcon, true);
Image icon = makeIcon(hIcon, LARGE_ICON_SIZE);
disposeIcon(hIcon);
return icon;
}

/**
* Gets an icon from the Windows system icon list as an {@code Image}
*/
static Image getShell32Icon(int iconID, boolean getLargeIcon) {
static Image getShell32Icon(int iconID, int size) {
boolean useVGAColors = true; // Will be ignored on XP and later

int size = getLargeIcon ? 32 : 16;

Toolkit toolkit = Toolkit.getDefaultToolkit();
String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP");
if (shellIconBPP != null) {
Expand All @@ -1133,7 +1192,7 @@ static Image getShell32Icon(int iconID, boolean getLargeIcon) {

long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors);
if (hIcon != 0) {
Image icon = makeIcon(hIcon, getLargeIcon);
Image icon = makeIcon(hIcon, size);
disposeIcon(hIcon);
return icon;
}
Expand Down Expand Up @@ -1316,13 +1375,17 @@ public List<KnownFolderDefinition> call() throws Exception {
}

static class MultiResolutionIconImage extends AbstractMultiResolutionImage {

final int baseSize;
final Image resolutionVariant;
final Map<Integer, Image> resolutionVariants = new HashMap<>();

public MultiResolutionIconImage(int baseSize, Image resolutionVariant) {
public MultiResolutionIconImage(int baseSize, Map<Integer, Image> resolutionVariants) {
this.baseSize = baseSize;
this.resolutionVariant = resolutionVariant;
this.resolutionVariants.putAll(resolutionVariants);
}

public MultiResolutionIconImage(int baseSize, Image image) {
this.baseSize = baseSize;
this.resolutionVariants.put(baseSize, image);
}

@Override
Expand All @@ -1337,17 +1400,34 @@ public int getHeight(ImageObserver observer) {

@Override
protected Image getBaseImage() {
return resolutionVariant;
return getResolutionVariant(baseSize, baseSize);
}

@Override
public Image getResolutionVariant(double width, double height) {
return resolutionVariant;
int dist = 0;
Image retVal = null;
// We only care about width since we don't support non-rectangular icons
int w = (int) width;
int retindex = 0;
for (Integer i: resolutionVariants.keySet()) {
if (retVal == null || dist > Math.abs(i - w)
|| (dist == Math.abs(i - w) && i > retindex)) {
retindex = i;
dist = Math.abs(i - w);
retVal = resolutionVariants.get(i);
if (i == w) {
break;
}
}
}
return retVal;
}

@Override
public List<Image> getResolutionVariants() {
return Arrays.asList(resolutionVariant);
return Collections.unmodifiableList(
new ArrayList<Image>(resolutionVariants.values()));
}
}
}
Loading