1 /* 2 * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.awt.shell; 27 28 import java.awt.Image; 29 import java.awt.Toolkit; 30 import java.awt.image.AbstractMultiResolutionImage; 31 import java.awt.image.BufferedImage; 32 import java.awt.image.ImageObserver; 33 import java.io.File; 34 import java.io.FileNotFoundException; 35 import java.io.IOException; 36 import java.util.*; 37 import java.util.concurrent.*; 38 import javax.swing.SwingConstants; 39 40 // NOTE: This class supersedes Win32ShellFolder, which was removed from 41 // distribution after version 1.4.2. 42 43 /** 44 * Win32 Shell Folders 45 * <P> 46 * <BR> 47 * There are two fundamental types of shell folders : file system folders 48 * and non-file system folders. File system folders are relatively easy 49 * to deal with. Non-file system folders are items such as My Computer, 50 * Network Neighborhood, and the desktop. Some of these non-file system 51 * folders have special values and properties. 52 * <P> 53 * <BR> 54 * Win32 keeps two basic data structures for shell folders. The first 55 * of these is called an ITEMIDLIST. Usually a pointer, called an 56 * LPITEMIDLIST, or more frequently just "PIDL". This structure holds 57 * a series of identifiers and can be either relative to the desktop 58 * (an absolute PIDL), or relative to the shell folder that contains them. 59 * Some Win32 functions can take absolute or relative PIDL values, and 60 * others can only accept relative values. 61 * <BR> 62 * The second data structure is an IShellFolder COM interface. Using 63 * this interface, one can enumerate the relative PIDLs in a shell 64 * folder, get attributes, etc. 65 * <BR> 66 * All Win32ShellFolder2 objects which are folder types (even non-file 67 * system folders) contain an IShellFolder object. Files are named in 68 * directories via relative PIDLs. 69 * 70 * @author Michael Martak 71 * @author Leif Samuelsson 72 * @author Kenneth Russell 73 * @since 1.4 */ 74 @SuppressWarnings("serial") // JDK-implementation class 75 final class Win32ShellFolder2 extends ShellFolder { 76 77 private static native void initIDs(); 78 79 static { 80 initIDs(); 81 } 82 83 // Win32 Shell Folder Constants 84 public static final int DESKTOP = 0x0000; 85 public static final int INTERNET = 0x0001; 86 public static final int PROGRAMS = 0x0002; 87 public static final int CONTROLS = 0x0003; 88 public static final int PRINTERS = 0x0004; 89 public static final int PERSONAL = 0x0005; 90 public static final int FAVORITES = 0x0006; 91 public static final int STARTUP = 0x0007; 92 public static final int RECENT = 0x0008; 93 public static final int SENDTO = 0x0009; 94 public static final int BITBUCKET = 0x000a; 95 public static final int STARTMENU = 0x000b; 96 public static final int DESKTOPDIRECTORY = 0x0010; 97 public static final int DRIVES = 0x0011; 98 public static final int NETWORK = 0x0012; 99 public static final int NETHOOD = 0x0013; 100 public static final int FONTS = 0x0014; 101 public static final int TEMPLATES = 0x0015; 102 public static final int COMMON_STARTMENU = 0x0016; 103 public static final int COMMON_PROGRAMS = 0X0017; 104 public static final int COMMON_STARTUP = 0x0018; 105 public static final int COMMON_DESKTOPDIRECTORY = 0x0019; 106 public static final int APPDATA = 0x001a; 107 public static final int PRINTHOOD = 0x001b; 108 public static final int ALTSTARTUP = 0x001d; 109 public static final int COMMON_ALTSTARTUP = 0x001e; 110 public static final int COMMON_FAVORITES = 0x001f; 111 public static final int INTERNET_CACHE = 0x0020; 112 public static final int COOKIES = 0x0021; 113 public static final int HISTORY = 0x0022; 114 115 // Win32 shell folder attributes 116 public static final int ATTRIB_CANCOPY = 0x00000001; 117 public static final int ATTRIB_CANMOVE = 0x00000002; 118 public static final int ATTRIB_CANLINK = 0x00000004; 119 public static final int ATTRIB_CANRENAME = 0x00000010; 120 public static final int ATTRIB_CANDELETE = 0x00000020; 121 public static final int ATTRIB_HASPROPSHEET = 0x00000040; 122 public static final int ATTRIB_DROPTARGET = 0x00000100; 123 public static final int ATTRIB_LINK = 0x00010000; 124 public static final int ATTRIB_SHARE = 0x00020000; 125 public static final int ATTRIB_READONLY = 0x00040000; 126 public static final int ATTRIB_GHOSTED = 0x00080000; 127 public static final int ATTRIB_HIDDEN = 0x00080000; 128 public static final int ATTRIB_FILESYSANCESTOR = 0x10000000; 129 public static final int ATTRIB_FOLDER = 0x20000000; 130 public static final int ATTRIB_FILESYSTEM = 0x40000000; 131 public static final int ATTRIB_HASSUBFOLDER = 0x80000000; 132 public static final int ATTRIB_VALIDATE = 0x01000000; 133 public static final int ATTRIB_REMOVABLE = 0x02000000; 134 public static final int ATTRIB_COMPRESSED = 0x04000000; 135 public static final int ATTRIB_BROWSABLE = 0x08000000; 136 public static final int ATTRIB_NONENUMERATED = 0x00100000; 137 public static final int ATTRIB_NEWCONTENT = 0x00200000; 138 139 // IShellFolder::GetDisplayNameOf constants 140 public static final int SHGDN_NORMAL = 0; 141 public static final int SHGDN_INFOLDER = 1; 142 public static final int SHGDN_INCLUDE_NONFILESYS= 0x2000; 143 public static final int SHGDN_FORADDRESSBAR = 0x4000; 144 public static final int SHGDN_FORPARSING = 0x8000; 145 146 /** The referent to be registered with the Disposer. */ 147 private Object disposerReferent = new Object(); 148 149 // Values for system call LoadIcon() 150 public enum SystemIcon { 151 IDI_APPLICATION(32512), 152 IDI_HAND(32513), 153 IDI_ERROR(32513), 154 IDI_QUESTION(32514), 155 IDI_EXCLAMATION(32515), 156 IDI_WARNING(32515), 157 IDI_ASTERISK(32516), 158 IDI_INFORMATION(32516), 159 IDI_WINLOGO(32517); 160 161 private final int iconID; 162 163 SystemIcon(int iconID) { 164 this.iconID = iconID; 165 } 166 167 public int getIconID() { 168 return iconID; 169 } 170 } 171 172 // Known Folder data 173 static class KnownFolderDefinition { 174 String guid; 175 int category; 176 String name; 177 String description; 178 String parent; 179 String relativePath; 180 String parsingName; 181 String tooltip; 182 String localizedName; 183 String icon; 184 String security; 185 long attributes; 186 int defenitionFlags; 187 String ftidType; 188 String path; 189 String saveLocation; 190 static final List<KnownFolderDefinition> libraries = getLibraries(); 191 } 192 193 static class FolderDisposer implements sun.java2d.DisposerRecord { 194 /* 195 * This is cached as a concession to getFolderType(), which needs 196 * an absolute PIDL. 197 */ 198 long absolutePIDL; 199 /* 200 * We keep track of shell folders through the IShellFolder 201 * interface of their parents plus their relative PIDL. 202 */ 203 long pIShellFolder; 204 long relativePIDL; 205 206 boolean disposed; 207 public void dispose() { 208 if (disposed) return; 209 invoke(new Callable<Void>() { 210 public Void call() { 211 if (relativePIDL != 0) { 212 releasePIDL(relativePIDL); 213 } 214 if (absolutePIDL != 0) { 215 releasePIDL(absolutePIDL); 216 } 217 if (pIShellFolder != 0) { 218 releaseIShellFolder(pIShellFolder); 219 } 220 return null; 221 } 222 }); 223 disposed = true; 224 } 225 } 226 FolderDisposer disposer = new FolderDisposer(); 227 private void setIShellFolder(long pIShellFolder) { 228 disposer.pIShellFolder = pIShellFolder; 229 } 230 private void setRelativePIDL(long relativePIDL) { 231 disposer.relativePIDL = relativePIDL; 232 } 233 /* 234 * The following are for caching various shell folder properties. 235 */ 236 private long pIShellIcon = -1L; 237 private String folderType = null; 238 private String displayName = null; 239 private Image smallIcon = null; 240 private Image largeIcon = null; 241 private Boolean isDir = null; 242 private final boolean isLib; 243 private static final String FNAME = COLUMN_NAME; 244 private static final String FSIZE = COLUMN_SIZE; 245 private static final String FTYPE = "FileChooser.fileTypeHeaderText"; 246 private static final String FDATE = COLUMN_DATE; 247 248 /* 249 * The following is to identify the My Documents folder as being special 250 */ 251 private boolean isPersonal; 252 253 private static String composePathForCsidl(int csidl) throws IOException, InterruptedException { 254 String path = getFileSystemPath(csidl); 255 return path == null 256 ? ("ShellFolder: 0x" + Integer.toHexString(csidl)) 257 : path; 258 } 259 260 /** 261 * Create a system special shell folder, such as the 262 * desktop or Network Neighborhood. 263 */ 264 Win32ShellFolder2(final int csidl) throws IOException, InterruptedException { 265 // Desktop is parent of DRIVES and NETWORK, not necessarily 266 // other special shell folders. 267 super(null, composePathForCsidl(csidl)); 268 isLib = false; 269 270 invoke(new Callable<Void>() { 271 public Void call() throws InterruptedException { 272 if (csidl == DESKTOP) { 273 initDesktop(); 274 } else { 275 initSpecial(getDesktop().getIShellFolder(), csidl); 276 // At this point, the native method initSpecial() has set our relativePIDL 277 // relative to the Desktop, which may not be our immediate parent. We need 278 // to traverse this ID list and break it into a chain of shell folders from 279 // the top, with each one having an immediate parent and a relativePIDL 280 // relative to that parent. 281 long pIDL = disposer.relativePIDL; 282 parent = getDesktop(); 283 while (pIDL != 0) { 284 // Get a child pidl relative to 'parent' 285 long childPIDL = copyFirstPIDLEntry(pIDL); 286 if (childPIDL != 0) { 287 // Get a handle to the rest of the ID list 288 // i,e, parent's grandchilren and down 289 pIDL = getNextPIDLEntry(pIDL); 290 if (pIDL != 0) { 291 // Now we know that parent isn't immediate to 'this' because it 292 // has a continued ID list. Create a shell folder for this child 293 // pidl and make it the new 'parent'. 294 parent = createShellFolder((Win32ShellFolder2) parent, childPIDL); 295 } else { 296 // No grandchildren means we have arrived at the parent of 'this', 297 // and childPIDL is directly relative to parent. 298 disposer.relativePIDL = childPIDL; 299 } 300 } else { 301 break; 302 } 303 } 304 } 305 return null; 306 } 307 }, InterruptedException.class); 308 309 sun.java2d.Disposer.addObjectRecord(disposerReferent, disposer); 310 } 311 312 313 /** 314 * Create a system shell folder 315 */ 316 Win32ShellFolder2(Win32ShellFolder2 parent, long pIShellFolder, long relativePIDL, String path, boolean isLib) { 317 super(parent, (path != null) ? path : "ShellFolder: "); 318 this.isLib = isLib; 319 this.disposer.pIShellFolder = pIShellFolder; 320 this.disposer.relativePIDL = relativePIDL; 321 sun.java2d.Disposer.addObjectRecord(disposerReferent, disposer); 322 } 323 324 325 /** 326 * Creates a shell folder with a parent and relative PIDL 327 */ 328 static Win32ShellFolder2 createShellFolder(Win32ShellFolder2 parent, long pIDL) 329 throws InterruptedException { 330 String path = invoke(new Callable<String>() { 331 public String call() { 332 return getFileSystemPath(parent.getIShellFolder(), pIDL); 333 } 334 }, RuntimeException.class); 335 String libPath = resolveLibrary(path); 336 if (libPath == null) { 337 return new Win32ShellFolder2(parent, 0, pIDL, path, false); 338 } else { 339 return new Win32ShellFolder2(parent, 0, pIDL, libPath, true); 340 } 341 } 342 343 // Initializes the desktop shell folder 344 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 345 private native void initDesktop(); 346 347 // Initializes a special, non-file system shell folder 348 // from one of the above constants 349 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 350 private native void initSpecial(long desktopIShellFolder, int csidl); 351 352 /** Marks this folder as being the My Documents (Personal) folder */ 353 public void setIsPersonal() { 354 isPersonal = true; 355 } 356 357 /** 358 * This method is implemented to make sure that no instances 359 * of {@code ShellFolder} are ever serialized. If {@code isFileSystem()} returns 360 * {@code true}, then the object is representable with an instance of 361 * {@code java.io.File} instead. If not, then the object depends 362 * on native PIDL state and should not be serialized. 363 * 364 * @return a {@code java.io.File} replacement object. If the folder 365 * is a not a normal directory, then returns the first non-removable 366 * drive (normally "C:\"). 367 */ 368 protected Object writeReplace() throws java.io.ObjectStreamException { 369 return invoke(new Callable<File>() { 370 public File call() { 371 if (isFileSystem()) { 372 return new File(getPath()); 373 } else { 374 Win32ShellFolder2 drives = Win32ShellFolderManager2.getDrives(); 375 if (drives != null) { 376 File[] driveRoots = drives.listFiles(); 377 if (driveRoots != null) { 378 for (int i = 0; i < driveRoots.length; i++) { 379 if (driveRoots[i] instanceof Win32ShellFolder2) { 380 Win32ShellFolder2 sf = (Win32ShellFolder2) driveRoots[i]; 381 if (sf.isFileSystem() && !sf.hasAttribute(ATTRIB_REMOVABLE)) { 382 return new File(sf.getPath()); 383 } 384 } 385 } 386 } 387 } 388 // Ouch, we have no hard drives. Return something "valid" anyway. 389 return new File("C:\\"); 390 } 391 } 392 }); 393 } 394 395 396 /** 397 * Finalizer to clean up any COM objects or PIDLs used by this object. 398 */ 399 protected void dispose() { 400 disposer.dispose(); 401 } 402 403 404 // Given a (possibly multi-level) relative PIDL (with respect to 405 // the desktop, at least in all of the usage cases in this code), 406 // return a pointer to the next entry. Does not mutate the PIDL in 407 // any way. Returns 0 if the null terminator is reached. 408 // Needs to be accessible to Win32ShellFolderManager2 409 static native long getNextPIDLEntry(long pIDL); 410 411 // Given a (possibly multi-level) relative PIDL (with respect to 412 // the desktop, at least in all of the usage cases in this code), 413 // copy the first entry into a newly-allocated PIDL. Returns 0 if 414 // the PIDL is at the end of the list. 415 // Needs to be accessible to Win32ShellFolderManager2 416 static native long copyFirstPIDLEntry(long pIDL); 417 418 // Given a parent's absolute PIDL and our relative PIDL, build an absolute PIDL 419 private static native long combinePIDLs(long ppIDL, long pIDL); 420 421 // Release a PIDL object 422 // Needs to be accessible to Win32ShellFolderManager2 423 static native void releasePIDL(long pIDL); 424 425 // Release an IShellFolder object 426 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 427 private static native void releaseIShellFolder(long pIShellFolder); 428 429 /** 430 * Accessor for IShellFolder 431 */ 432 private long getIShellFolder() { 433 if (disposer.pIShellFolder == 0) { 434 try { 435 disposer.pIShellFolder = invoke(new Callable<Long>() { 436 public Long call() { 437 assert(isDirectory()); 438 assert(parent != null); 439 long parentIShellFolder = getParentIShellFolder(); 440 if (parentIShellFolder == 0) { 441 throw new InternalError("Parent IShellFolder was null for " 442 + getAbsolutePath()); 443 } 444 // We are a directory with a parent and a relative PIDL. 445 // We want to bind to the parent so we get an 446 // IShellFolder instance associated with us. 447 long pIShellFolder = bindToObject(parentIShellFolder, 448 disposer.relativePIDL); 449 if (pIShellFolder == 0) { 450 throw new InternalError("Unable to bind " 451 + getAbsolutePath() + " to parent"); 452 } 453 return pIShellFolder; 454 } 455 }, RuntimeException.class); 456 } catch (InterruptedException e) { 457 // Ignore error 458 } 459 } 460 return disposer.pIShellFolder; 461 } 462 463 /** 464 * Get the parent ShellFolder's IShellFolder interface 465 */ 466 public long getParentIShellFolder() { 467 Win32ShellFolder2 parent = (Win32ShellFolder2)getParentFile(); 468 if (parent == null) { 469 // Parent should only be null if this is the desktop, whose 470 // relativePIDL is relative to its own IShellFolder. 471 return getIShellFolder(); 472 } 473 return parent.getIShellFolder(); 474 } 475 476 /** 477 * Accessor for relative PIDL 478 */ 479 public long getRelativePIDL() { 480 if (disposer.relativePIDL == 0) { 481 throw new InternalError("Should always have a relative PIDL"); 482 } 483 return disposer.relativePIDL; 484 } 485 486 private long getAbsolutePIDL() { 487 if (parent == null) { 488 // This is the desktop 489 return getRelativePIDL(); 490 } else { 491 if (disposer.absolutePIDL == 0) { 492 disposer.absolutePIDL = combinePIDLs(((Win32ShellFolder2)parent).getAbsolutePIDL(), getRelativePIDL()); 493 } 494 495 return disposer.absolutePIDL; 496 } 497 } 498 499 /** 500 * Helper function to return the desktop 501 */ 502 public Win32ShellFolder2 getDesktop() { 503 return Win32ShellFolderManager2.getDesktop(); 504 } 505 506 /** 507 * Helper function to return the desktop IShellFolder interface 508 */ 509 public long getDesktopIShellFolder() { 510 return getDesktop().getIShellFolder(); 511 } 512 513 private static boolean pathsEqual(String path1, String path2) { 514 // Same effective implementation as Win32FileSystem 515 return path1.equalsIgnoreCase(path2); 516 } 517 518 /** 519 * Check to see if two ShellFolder objects are the same 520 */ 521 public boolean equals(Object o) { 522 if (o == null || !(o instanceof Win32ShellFolder2)) { 523 // Short-circuit circuitous delegation path 524 if (!(o instanceof File)) { 525 return super.equals(o); 526 } 527 return pathsEqual(getPath(), ((File) o).getPath()); 528 } 529 Win32ShellFolder2 rhs = (Win32ShellFolder2) o; 530 if ((parent == null && rhs.parent != null) || 531 (parent != null && rhs.parent == null)) { 532 return false; 533 } 534 535 if (isFileSystem() && rhs.isFileSystem()) { 536 // Only folders with identical parents can be equal 537 return (pathsEqual(getPath(), rhs.getPath()) && 538 (parent == rhs.parent || parent.equals(rhs.parent))); 539 } 540 541 if (parent == rhs.parent || parent.equals(rhs.parent)) { 542 try { 543 return pidlsEqual(getParentIShellFolder(), disposer.relativePIDL, rhs.disposer.relativePIDL); 544 } catch (InterruptedException e) { 545 return false; 546 } 547 } 548 549 return false; 550 } 551 552 private static boolean pidlsEqual(final long pIShellFolder, final long pidl1, final long pidl2) 553 throws InterruptedException { 554 return invoke(new Callable<Boolean>() { 555 public Boolean call() { 556 return compareIDs(pIShellFolder, pidl1, pidl2) == 0; 557 } 558 }, RuntimeException.class); 559 } 560 561 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 562 private static native int compareIDs(long pParentIShellFolder, long pidl1, long pidl2); 563 564 private volatile Boolean cachedIsFileSystem; 565 566 /** 567 * @return Whether this is a file system shell folder 568 */ 569 public boolean isFileSystem() { 570 if (cachedIsFileSystem == null) { 571 cachedIsFileSystem = hasAttribute(ATTRIB_FILESYSTEM); 572 } 573 574 return cachedIsFileSystem; 575 } 576 577 /** 578 * Return whether the given attribute flag is set for this object 579 */ 580 public boolean hasAttribute(final int attribute) { 581 Boolean result = invoke(new Callable<Boolean>() { 582 public Boolean call() { 583 // Caching at this point doesn't seem to be cost efficient 584 return (getAttributes0(getParentIShellFolder(), 585 getRelativePIDL(), attribute) 586 & attribute) != 0; 587 } 588 }); 589 590 return result != null && result; 591 } 592 593 /** 594 * Returns the queried attributes specified in attrsMask. 595 * 596 * Could plausibly be used for attribute caching but have to be 597 * very careful not to touch network drives and file system roots 598 * with a full attrsMask 599 * NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 600 */ 601 602 private static native int getAttributes0(long pParentIShellFolder, long pIDL, int attrsMask); 603 604 // Return the path to the underlying file system object 605 // Should be called from the COM thread 606 private static String getFileSystemPath(final long parentIShellFolder, final long relativePIDL) { 607 int linkedFolder = ATTRIB_LINK | ATTRIB_FOLDER; 608 if (parentIShellFolder == Win32ShellFolderManager2.getNetwork().getIShellFolder() && 609 getAttributes0(parentIShellFolder, relativePIDL, linkedFolder) == linkedFolder) { 610 611 String s = 612 getFileSystemPath(Win32ShellFolderManager2.getDesktop().getIShellFolder(), 613 getLinkLocation(parentIShellFolder, relativePIDL, false)); 614 if (s != null && s.startsWith("\\\\")) { 615 return s; 616 } 617 } 618 String path = getDisplayNameOf(parentIShellFolder, relativePIDL, 619 SHGDN_FORPARSING); 620 return path; 621 } 622 623 private static String resolveLibrary(String path) { 624 // if this is a library its default save location is taken as a path 625 // this is a temp fix until java.io starts support Libraries 626 if( path != null && path.startsWith("::{") && 627 path.toLowerCase().endsWith(".library-ms")) { 628 for (KnownFolderDefinition kf : KnownFolderDefinition.libraries) { 629 if (path.toLowerCase().endsWith( 630 "\\" + kf.relativePath.toLowerCase()) && 631 path.toUpperCase().startsWith( 632 kf.parsingName.substring(0, 40).toUpperCase())) { 633 return kf.saveLocation; 634 } 635 } 636 } 637 return null; 638 } 639 640 // Needs to be accessible to Win32ShellFolderManager2 641 static String getFileSystemPath(final int csidl) throws IOException, InterruptedException { 642 String path = invoke(new Callable<String>() { 643 public String call() throws IOException { 644 return getFileSystemPath0(csidl); 645 } 646 }, IOException.class); 647 if (path != null) { 648 SecurityManager security = System.getSecurityManager(); 649 if (security != null) { 650 security.checkRead(path); 651 } 652 } 653 return path; 654 } 655 656 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 657 private static native String getFileSystemPath0(int csidl) throws IOException; 658 659 // Return whether the path is a network root. 660 // Path is assumed to be non-null 661 private static boolean isNetworkRoot(String path) { 662 return (path.equals("\\\\") || path.equals("\\") || path.equals("//") || path.equals("/")); 663 } 664 665 /** 666 * @return The parent shell folder of this shell folder, null if 667 * there is no parent 668 */ 669 public File getParentFile() { 670 return parent; 671 } 672 673 public boolean isDirectory() { 674 if (isDir == null) { 675 // Folders with SFGAO_BROWSABLE have "shell extension" handlers and are 676 // not traversable in JFileChooser. 677 if (hasAttribute(ATTRIB_FOLDER) && !hasAttribute(ATTRIB_BROWSABLE)) { 678 isDir = Boolean.TRUE; 679 } else if (isLink()) { 680 ShellFolder linkLocation = getLinkLocation(false); 681 isDir = Boolean.valueOf(linkLocation != null && linkLocation.isDirectory()); 682 } else { 683 isDir = Boolean.FALSE; 684 } 685 } 686 return isDir.booleanValue(); 687 } 688 689 /* 690 * Functions for enumerating an IShellFolder's children 691 */ 692 // Returns an IEnumIDList interface for an IShellFolder. The value 693 // returned must be released using releaseEnumObjects(). 694 private long getEnumObjects(final boolean includeHiddenFiles) throws InterruptedException { 695 return invoke(new Callable<Long>() { 696 public Long call() { 697 boolean isDesktop = disposer.pIShellFolder == getDesktopIShellFolder(); 698 699 return getEnumObjects(disposer.pIShellFolder, isDesktop, includeHiddenFiles); 700 } 701 }, RuntimeException.class); 702 } 703 704 // Returns an IEnumIDList interface for an IShellFolder. The value 705 // returned must be released using releaseEnumObjects(). 706 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 707 private native long getEnumObjects(long pIShellFolder, boolean isDesktop, 708 boolean includeHiddenFiles); 709 // Returns the next sequential child as a relative PIDL 710 // from an IEnumIDList interface. The value returned must 711 // be released using releasePIDL(). 712 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 713 private native long getNextChild(long pEnumObjects); 714 // Releases the IEnumIDList interface 715 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 716 private native void releaseEnumObjects(long pEnumObjects); 717 718 // Returns the IShellFolder of a child from a parent IShellFolder 719 // and a relative PIDL. The value returned must be released 720 // using releaseIShellFolder(). 721 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 722 private static native long bindToObject(long parentIShellFolder, long pIDL); 723 724 /** 725 * @return An array of shell folders that are children of this shell folder 726 * object. The array will be empty if the folder is empty. Returns 727 * {@code null} if this shellfolder does not denote a directory. 728 */ 729 public File[] listFiles(final boolean includeHiddenFiles) { 730 SecurityManager security = System.getSecurityManager(); 731 if (security != null) { 732 security.checkRead(getPath()); 733 } 734 735 try { 736 return invoke(new Callable<File[]>() { 737 public File[] call() throws InterruptedException { 738 if (!isDirectory()) { 739 return null; 740 } 741 // Links to directories are not directories and cannot be parents. 742 // This does not apply to folders in My Network Places (NetHood) 743 // because they are both links and real directories! 744 if (isLink() && !hasAttribute(ATTRIB_FOLDER)) { 745 return new File[0]; 746 } 747 748 Win32ShellFolder2 desktop = Win32ShellFolderManager2.getDesktop(); 749 Win32ShellFolder2 personal = Win32ShellFolderManager2.getPersonal(); 750 751 // If we are a directory, we have a parent and (at least) a 752 // relative PIDL. We must first ensure we are bound to the 753 // parent so we have an IShellFolder to query. 754 long pIShellFolder = getIShellFolder(); 755 // Now we can enumerate the objects in this folder. 756 ArrayList<Win32ShellFolder2> list = new ArrayList<Win32ShellFolder2>(); 757 long pEnumObjects = getEnumObjects(includeHiddenFiles); 758 if (pEnumObjects != 0) { 759 try { 760 long childPIDL; 761 int testedAttrs = ATTRIB_FILESYSTEM | ATTRIB_FILESYSANCESTOR; 762 do { 763 childPIDL = getNextChild(pEnumObjects); 764 boolean releasePIDL = true; 765 if (childPIDL != 0 && 766 (getAttributes0(pIShellFolder, childPIDL, testedAttrs) & testedAttrs) != 0) { 767 Win32ShellFolder2 childFolder; 768 if (Win32ShellFolder2.this.equals(desktop) 769 && personal != null 770 && pidlsEqual(pIShellFolder, childPIDL, personal.disposer.relativePIDL)) { 771 childFolder = personal; 772 } else { 773 childFolder = createShellFolder(Win32ShellFolder2.this, childPIDL); 774 releasePIDL = false; 775 } 776 list.add(childFolder); 777 } 778 if (releasePIDL) { 779 releasePIDL(childPIDL); 780 } 781 } while (childPIDL != 0 && !Thread.currentThread().isInterrupted()); 782 } finally { 783 releaseEnumObjects(pEnumObjects); 784 } 785 } 786 return Thread.currentThread().isInterrupted() 787 ? new File[0] 788 : list.toArray(new ShellFolder[list.size()]); 789 } 790 }, InterruptedException.class); 791 } catch (InterruptedException e) { 792 return new File[0]; 793 } 794 } 795 796 797 /** 798 * Look for (possibly special) child folder by it's path 799 * 800 * @return The child shellfolder, or null if not found. 801 */ 802 Win32ShellFolder2 getChildByPath(final String filePath) throws InterruptedException { 803 return invoke(new Callable<Win32ShellFolder2>() { 804 public Win32ShellFolder2 call() throws InterruptedException { 805 long pIShellFolder = getIShellFolder(); 806 long pEnumObjects = getEnumObjects(true); 807 Win32ShellFolder2 child = null; 808 long childPIDL; 809 810 while ((childPIDL = getNextChild(pEnumObjects)) != 0) { 811 if (getAttributes0(pIShellFolder, childPIDL, ATTRIB_FILESYSTEM) != 0) { 812 String path = getFileSystemPath(pIShellFolder, childPIDL); 813 if(isLib) path = resolveLibrary( path ); 814 if (path != null && path.equalsIgnoreCase(filePath)) { 815 long childIShellFolder = bindToObject(pIShellFolder, childPIDL); 816 child = new Win32ShellFolder2(Win32ShellFolder2.this, 817 childIShellFolder, childPIDL, path, isLib); 818 break; 819 } 820 } 821 releasePIDL(childPIDL); 822 } 823 releaseEnumObjects(pEnumObjects); 824 return child; 825 } 826 }, InterruptedException.class); 827 } 828 829 private volatile Boolean cachedIsLink; 830 831 /** 832 * @return Whether this shell folder is a link 833 */ 834 public boolean isLink() { 835 if (cachedIsLink == null) { 836 cachedIsLink = hasAttribute(ATTRIB_LINK); 837 } 838 839 return cachedIsLink; 840 } 841 842 /** 843 * @return Whether this shell folder is marked as hidden 844 */ 845 public boolean isHidden() { 846 return hasAttribute(ATTRIB_HIDDEN); 847 } 848 849 850 // Return the link location of a shell folder 851 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 852 private static native long getLinkLocation(long parentIShellFolder, 853 long relativePIDL, boolean resolve); 854 855 /** 856 * @return The shell folder linked to by this shell folder, or null 857 * if this shell folder is not a link or is a broken or invalid link 858 */ 859 public ShellFolder getLinkLocation() { 860 return getLinkLocation(true); 861 } 862 863 private Win32ShellFolder2 getLinkLocation(final boolean resolve) { 864 return invoke(new Callable<Win32ShellFolder2>() { 865 public Win32ShellFolder2 call() { 866 if (!isLink()) { 867 return null; 868 } 869 870 Win32ShellFolder2 location = null; 871 long linkLocationPIDL = getLinkLocation(getParentIShellFolder(), 872 getRelativePIDL(), resolve); 873 if (linkLocationPIDL != 0) { 874 try { 875 location = 876 Win32ShellFolderManager2.createShellFolderFromRelativePIDL(getDesktop(), 877 linkLocationPIDL); 878 } catch (InterruptedException e) { 879 // Return null 880 } catch (InternalError e) { 881 // Could be a link to a non-bindable object, such as a network connection 882 // TODO: getIShellFolder() should throw FileNotFoundException instead 883 } 884 } 885 return location; 886 } 887 }); 888 } 889 890 // Parse a display name into a PIDL relative to the current IShellFolder. 891 long parseDisplayName(final String name) throws IOException, InterruptedException { 892 return invoke(new Callable<Long>() { 893 public Long call() throws IOException { 894 return parseDisplayName0(getIShellFolder(), name); 895 } 896 }, IOException.class); 897 } 898 899 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 900 private static native long parseDisplayName0(long pIShellFolder, String name) throws IOException; 901 902 // Return the display name of a shell folder 903 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 904 private static native String getDisplayNameOf(long parentIShellFolder, 905 long relativePIDL, 906 int attrs); 907 908 // Returns data of all Known Folders registered in the system 909 private static native KnownFolderDefinition[] loadKnownFolders(); 910 911 /** 912 * @return The name used to display this shell folder 913 */ 914 public String getDisplayName() { 915 if (displayName == null) { 916 displayName = 917 invoke(new Callable<String>() { 918 public String call() { 919 return getDisplayNameOf(getParentIShellFolder(), 920 getRelativePIDL(), SHGDN_NORMAL); 921 } 922 }); 923 } 924 return displayName; 925 } 926 927 // Return the folder type of a shell folder 928 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 929 private static native String getFolderType(long pIDL); 930 931 /** 932 * @return The type of shell folder as a string 933 */ 934 public String getFolderType() { 935 if (folderType == null) { 936 final long absolutePIDL = getAbsolutePIDL(); 937 folderType = 938 invoke(new Callable<String>() { 939 public String call() { 940 return getFolderType(absolutePIDL); 941 } 942 }); 943 } 944 return folderType; 945 } 946 947 // Return the executable type of a file system shell folder 948 private native String getExecutableType(String path); 949 950 /** 951 * @return The executable type as a string 952 */ 953 public String getExecutableType() { 954 if (!isFileSystem()) { 955 return null; 956 } 957 return getExecutableType(getAbsolutePath()); 958 } 959 960 961 962 // Icons 963 964 private static Map<Integer, Image> smallSystemImages = new HashMap<>(); 965 private static Map<Integer, Image> largeSystemImages = new HashMap<>(); 966 private static Map<Integer, Image> smallLinkedSystemImages = new HashMap<>(); 967 private static Map<Integer, Image> largeLinkedSystemImages = new HashMap<>(); 968 969 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 970 private static native long getIShellIcon(long pIShellFolder); 971 972 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 973 private static native int getIconIndex(long parentIShellIcon, long relativePIDL); 974 975 // Return the icon of a file system shell folder in the form of an HICON 976 private static native long getIcon(String absolutePath, boolean getLargeIcon); 977 978 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 979 private static native long extractIcon(long parentIShellFolder, long relativePIDL, 980 int size, boolean getDefaultIcon); 981 982 // Returns an icon from the Windows system icon list in the form of an HICON 983 private static native long getSystemIcon(int iconID); 984 private static native long getIconResource(String libName, int iconID, 985 int cxDesired, int cyDesired, 986 boolean useVGAColors); 987 // Note: useVGAColors is ignored on XP and later 988 989 // Return the bits from an HICON. This has a side effect of setting 990 // the imageHash variable for efficient caching / comparing. 991 private static native int[] getIconBits(long hIcon); 992 // Dispose the HICON 993 private static native void disposeIcon(long hIcon); 994 995 // Get buttons from native toolbar implementation. 996 static native int[] getStandardViewButton0(int iconIndex, boolean small); 997 998 // Should be called from the COM thread 999 private long getIShellIcon() { 1000 if (pIShellIcon == -1L) { 1001 pIShellIcon = getIShellIcon(getIShellFolder()); 1002 } 1003 1004 return pIShellIcon; 1005 } 1006 1007 private static Image makeIcon(long hIcon, int bsize) { 1008 if (hIcon != 0L && hIcon != -1L) { 1009 // Get the bits. This has the side effect of setting the imageHash value for this object. 1010 final int[] iconBits = getIconBits(hIcon); 1011 if (iconBits != null) { 1012 // icons are always square 1013 final int size = (int) Math.sqrt(iconBits.length); 1014 final BufferedImage img = 1015 new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); 1016 img.setRGB(0, 0, size, size, iconBits, 0, size); 1017 return size == bsize 1018 ? img 1019 : new MultiResolutionIconImage(bsize, img); 1020 } 1021 } 1022 return null; 1023 } 1024 1025 1026 /** 1027 * @return The icon image used to display this shell folder 1028 */ 1029 public Image getIcon(final boolean getLargeIcon) { 1030 Image icon = getLargeIcon ? largeIcon : smallIcon; 1031 int size = getLargeIcon ? 32 : 16; // large icon is 32 pixels else 16 pixels 1032 if (icon == null) { 1033 icon = 1034 invoke(new Callable<Image>() { 1035 public Image call() { 1036 Image newIcon = null; 1037 if (isLink()) { 1038 Win32ShellFolder2 folder = getLinkLocation(false); 1039 if (folder != null && folder.isLibrary()) { 1040 return folder.getIcon(getLargeIcon); 1041 } 1042 } 1043 if (isFileSystem() || isLibrary()) { 1044 long parentIShellIcon = (parent != null) 1045 ? ((Win32ShellFolder2) parent).getIShellIcon() 1046 : 0L; 1047 long relativePIDL = getRelativePIDL(); 1048 1049 // These are cached per type (using the index in the system image list) 1050 int index = getIconIndex(parentIShellIcon, relativePIDL); 1051 if (index > 0) { 1052 Map<Integer, Image> imageCache; 1053 if (isLink()) { 1054 imageCache = getLargeIcon ? largeLinkedSystemImages : smallLinkedSystemImages; 1055 } else { 1056 imageCache = getLargeIcon ? largeSystemImages : smallSystemImages; 1057 } 1058 newIcon = imageCache.get(Integer.valueOf(index)); 1059 if (newIcon == null) { 1060 long hIcon = getIcon(getAbsolutePath(), getLargeIcon); 1061 newIcon = makeIcon(hIcon, size); 1062 disposeIcon(hIcon); 1063 if (newIcon != null) { 1064 imageCache.put(Integer.valueOf(index), newIcon); 1065 } 1066 } 1067 } 1068 } 1069 1070 if (newIcon == null) { 1071 // These are only cached per object 1072 long hIcon = extractIcon(getParentIShellFolder(), 1073 getRelativePIDL(), size, false); 1074 // E_PENDING: loading can take time so get the default 1075 if(hIcon <= 0) { 1076 hIcon = extractIcon(getParentIShellFolder(), 1077 getRelativePIDL(), size, true); 1078 if(hIcon <= 0) { 1079 if (isDirectory()) { 1080 return getShell32Icon(4, size); // pick folder icon 1081 } else { 1082 return getShell32Icon(1, size); // pick file icon 1083 } 1084 } 1085 } 1086 newIcon = makeIcon(hIcon, size); 1087 disposeIcon(hIcon); 1088 } 1089 1090 if (newIcon == null) { 1091 newIcon = Win32ShellFolder2.super.getIcon(getLargeIcon); 1092 } 1093 return newIcon; 1094 } 1095 }); 1096 } 1097 return icon; 1098 } 1099 1100 public Image getIcon(int size) { 1101 return invoke(() -> { 1102 Image newIcon = null; 1103 if (isLink()) { 1104 Win32ShellFolder2 folder = getLinkLocation(false); 1105 if (folder != null && folder.isLibrary()) { 1106 return folder.getIcon(size); 1107 } 1108 } 1109 long hIcon = extractIcon(getParentIShellFolder(), 1110 getRelativePIDL(), size, false); 1111 1112 // E_PENDING: loading can take time so get the default 1113 if(hIcon <= 0) { 1114 hIcon = extractIcon(getParentIShellFolder(), 1115 getRelativePIDL(), size, true); 1116 if(hIcon <= 0) { 1117 if (isDirectory()) { 1118 return getShell32Icon(4, size); 1119 } else { 1120 return getShell32Icon(1, size); 1121 } 1122 } 1123 } 1124 newIcon = makeIcon(hIcon, size); 1125 disposeIcon(hIcon); 1126 return newIcon; 1127 }); 1128 } 1129 1130 /** 1131 * Gets an icon from the Windows system icon list as an {@code Image} 1132 */ 1133 static Image getSystemIcon(SystemIcon iconType) { 1134 long hIcon = getSystemIcon(iconType.getIconID()); 1135 Image icon = makeIcon(hIcon, 32); 1136 disposeIcon(hIcon); 1137 return icon; 1138 } 1139 1140 /** 1141 * Gets an icon from the Windows system icon list as an {@code Image} 1142 */ 1143 static Image getShell32Icon(int iconID, int size) { 1144 boolean useVGAColors = true; // Will be ignored on XP and later 1145 Toolkit toolkit = Toolkit.getDefaultToolkit(); 1146 String shellIconBPP = (String)toolkit.getDesktopProperty("win.icon.shellIconBPP"); 1147 if (shellIconBPP != null) { 1148 useVGAColors = shellIconBPP.equals("4"); 1149 } 1150 1151 long hIcon = getIconResource("shell32.dll", iconID, size, size, useVGAColors); 1152 if (hIcon != 0) { 1153 Image icon = makeIcon(hIcon, size); 1154 disposeIcon(hIcon); 1155 return icon; 1156 } 1157 return null; 1158 } 1159 1160 /** 1161 * Returns the canonical form of this abstract pathname. Equivalent to 1162 * <code>new Win32ShellFolder2(getParentFile(), this.{@link java.io.File#getCanonicalPath}())</code>. 1163 * 1164 * @see java.io.File#getCanonicalFile 1165 */ 1166 public File getCanonicalFile() throws IOException { 1167 return this; 1168 } 1169 1170 /* 1171 * Indicates whether this is a special folder (includes My Documents) 1172 */ 1173 public boolean isSpecial() { 1174 return isPersonal || !isFileSystem() || (this == getDesktop()); 1175 } 1176 1177 /** 1178 * Compares this object with the specified object for order. 1179 * 1180 * @see sun.awt.shell.ShellFolder#compareTo(File) 1181 */ 1182 public int compareTo(File file2) { 1183 if (!(file2 instanceof Win32ShellFolder2)) { 1184 if (isFileSystem() && !isSpecial()) { 1185 return super.compareTo(file2); 1186 } else { 1187 return -1; // Non-file shellfolders sort before files 1188 } 1189 } 1190 return Win32ShellFolderManager2.compareShellFolders(this, (Win32ShellFolder2) file2); 1191 } 1192 1193 // native constants from commctrl.h 1194 private static final int LVCFMT_LEFT = 0; 1195 private static final int LVCFMT_RIGHT = 1; 1196 private static final int LVCFMT_CENTER = 2; 1197 1198 public ShellFolderColumnInfo[] getFolderColumns() { 1199 ShellFolder library = resolveLibrary(); 1200 if (library != null) return library.getFolderColumns(); 1201 return invoke(new Callable<ShellFolderColumnInfo[]>() { 1202 public ShellFolderColumnInfo[] call() { 1203 ShellFolderColumnInfo[] columns = doGetColumnInfo(getIShellFolder()); 1204 1205 if (columns != null) { 1206 List<ShellFolderColumnInfo> notNullColumns = 1207 new ArrayList<ShellFolderColumnInfo>(); 1208 for (int i = 0; i < columns.length; i++) { 1209 ShellFolderColumnInfo column = columns[i]; 1210 if (column != null) { 1211 column.setAlignment(column.getAlignment() == LVCFMT_RIGHT 1212 ? SwingConstants.RIGHT 1213 : column.getAlignment() == LVCFMT_CENTER 1214 ? SwingConstants.CENTER 1215 : SwingConstants.LEADING); 1216 1217 column.setComparator(new ColumnComparator(Win32ShellFolder2.this, i)); 1218 1219 notNullColumns.add(column); 1220 } 1221 } 1222 columns = new ShellFolderColumnInfo[notNullColumns.size()]; 1223 notNullColumns.toArray(columns); 1224 } 1225 return columns; 1226 } 1227 }); 1228 } 1229 1230 public Object getFolderColumnValue(final int column) { 1231 if(!isLibrary()) { 1232 ShellFolder library = resolveLibrary(); 1233 if (library != null) return library.getFolderColumnValue(column); 1234 } 1235 return invoke(new Callable<Object>() { 1236 public Object call() { 1237 return doGetColumnValue(getParentIShellFolder(), getRelativePIDL(), column); 1238 } 1239 }); 1240 } 1241 1242 boolean isLibrary() { 1243 return isLib; 1244 } 1245 1246 private ShellFolder resolveLibrary() { 1247 for (ShellFolder f = this; f != null; f = f.parent) { 1248 if (!f.isFileSystem()) { 1249 if (f instanceof Win32ShellFolder2 && 1250 ((Win32ShellFolder2)f).isLibrary()) { 1251 try { 1252 return getShellFolder(new File(getPath())); 1253 } catch (FileNotFoundException e) { 1254 } 1255 } 1256 break; 1257 } 1258 } 1259 return null; 1260 } 1261 1262 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1263 private native ShellFolderColumnInfo[] doGetColumnInfo(long iShellFolder2); 1264 1265 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1266 private native Object doGetColumnValue(long parentIShellFolder2, long childPIDL, int columnIdx); 1267 1268 // NOTE: this method uses COM and must be called on the 'COM thread'. See ComInvoker for the details 1269 private static native int compareIDsByColumn(long pParentIShellFolder, long pidl1, long pidl2, int columnIdx); 1270 1271 1272 public void sortChildren(final List<? extends File> files) { 1273 // To avoid loads of synchronizations with Invoker and improve performance we 1274 // synchronize the whole code of the sort method once 1275 invoke(new Callable<Void>() { 1276 public Void call() { 1277 Collections.sort(files, new ColumnComparator(Win32ShellFolder2.this, 0)); 1278 1279 return null; 1280 } 1281 }); 1282 } 1283 1284 private static class ColumnComparator implements Comparator<File> { 1285 private final Win32ShellFolder2 shellFolder; 1286 1287 private final int columnIdx; 1288 1289 public ColumnComparator(Win32ShellFolder2 shellFolder, int columnIdx) { 1290 this.shellFolder = shellFolder; 1291 this.columnIdx = columnIdx; 1292 } 1293 1294 // compares 2 objects within this folder by the specified column 1295 public int compare(final File o, final File o1) { 1296 Integer result = invoke(new Callable<Integer>() { 1297 public Integer call() { 1298 if (o instanceof Win32ShellFolder2 1299 && o1 instanceof Win32ShellFolder2) { 1300 // delegates comparison to native method 1301 return compareIDsByColumn(shellFolder.getIShellFolder(), 1302 ((Win32ShellFolder2) o).getRelativePIDL(), 1303 ((Win32ShellFolder2) o1).getRelativePIDL(), 1304 columnIdx); 1305 } 1306 return 0; 1307 } 1308 }); 1309 1310 return result == null ? 0 : result; 1311 } 1312 } 1313 1314 // Extracts libraries and their default save locations from Known Folders list 1315 private static List<KnownFolderDefinition> getLibraries() { 1316 return invoke(new Callable<List<KnownFolderDefinition>>() { 1317 @Override 1318 public List<KnownFolderDefinition> call() throws Exception { 1319 KnownFolderDefinition[] all = loadKnownFolders(); 1320 List<KnownFolderDefinition> folders = new ArrayList<>(); 1321 if (all != null) { 1322 for (KnownFolderDefinition kf : all) { 1323 if (kf.relativePath == null || kf.parsingName == null || 1324 kf.saveLocation == null) { 1325 continue; 1326 } 1327 folders.add(kf); 1328 } 1329 } 1330 return folders; 1331 } 1332 }); 1333 } 1334 1335 static class MultiResolutionIconImage extends AbstractMultiResolutionImage { 1336 1337 final int baseSize; 1338 final Image resolutionVariant; 1339 1340 public MultiResolutionIconImage(int baseSize, Image resolutionVariant) { 1341 this.baseSize = baseSize; 1342 this.resolutionVariant = resolutionVariant; 1343 } 1344 1345 @Override 1346 public int getWidth(ImageObserver observer) { 1347 return baseSize; 1348 } 1349 1350 @Override 1351 public int getHeight(ImageObserver observer) { 1352 return baseSize; 1353 } 1354 1355 @Override 1356 protected Image getBaseImage() { 1357 return resolutionVariant; 1358 } 1359 1360 @Override 1361 public Image getResolutionVariant(double width, double height) { 1362 return resolutionVariant; 1363 } 1364 1365 @Override 1366 public List<Image> getResolutionVariants() { 1367 return Arrays.asList(resolutionVariant); 1368 } 1369 } 1370 }