1 /*
   2  * Copyright 1997-2007 Sun Microsystems, Inc.  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.  Sun designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  22  * CA 95054 USA or visit www.sun.com if you need additional information or
  23  * have any questions.
  24  */
  25 
  26 package sun.java2d;
  27 
  28 import java.awt.Color;
  29 import java.awt.Font;
  30 import java.awt.Graphics2D;
  31 import java.awt.GraphicsConfiguration;
  32 import java.awt.GraphicsDevice;
  33 import java.awt.GraphicsEnvironment;
  34 import java.awt.Insets;
  35 import java.awt.Rectangle;
  36 import java.awt.Toolkit;
  37 import java.awt.font.TextAttribute;
  38 import java.awt.image.BufferedImage;
  39 import java.io.BufferedReader;
  40 import java.io.File;
  41 import java.io.FileInputStream;
  42 import java.io.FilenameFilter;
  43 import java.io.InputStreamReader;
  44 import java.io.IOException;
  45 import java.text.AttributedCharacterIterator;
  46 import java.util.ArrayList;
  47 import java.util.HashSet;
  48 import java.util.Iterator;
  49 import java.util.Locale;
  50 import java.util.Map;
  51 import java.util.NoSuchElementException;
  52 import java.util.Set;
  53 import java.util.StringTokenizer;
  54 import java.util.TreeMap;
  55 import java.util.Vector;
  56 import java.util.concurrent.ConcurrentHashMap;
  57 import java.util.logging.Level;
  58 import java.util.logging.Logger;
  59 import sun.awt.AppContext;
  60 import sun.awt.DisplayChangedListener;
  61 import sun.awt.FontConfiguration;
  62 import sun.awt.SunDisplayChanger;
  63 import sun.font.CompositeFontDescriptor;
  64 import sun.font.Font2D;
  65 import sun.font.FontManager;
  66 import sun.font.NativeFont;
  67 
  68 /**
  69  * This is an implementation of a GraphicsEnvironment object for the
  70  * default local GraphicsEnvironment.
  71  *
  72  * @see GraphicsDevice
  73  * @see GraphicsConfiguration
  74  */
  75 
  76 public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
  77     implements FontSupport, DisplayChangedListener {
  78 
  79     public static boolean isLinux;
  80     public static boolean isSolaris;
  81     public static boolean isWindows;
  82     public static boolean noType1Font;
  83     private static Font defaultFont;
  84     private static String defaultFontFileName;
  85     private static String defaultFontName;
  86     public static final String lucidaFontName = "Lucida Sans Regular";
  87     public static final String lucidaFileName = "LucidaSansRegular.ttf";
  88     public static boolean debugFonts = false;
  89     protected static Logger logger = null;
  90     private static ArrayList badFonts;
  91     public static String jreLibDirName;
  92     public static String jreFontDirName;
  93     private static HashSet<String> missingFontFiles = null;
  94 
  95     private FontConfiguration fontConfig;
  96 
  97     /* fontPath is the location of all fonts on the system, excluding the
  98      * JRE's own font directory but including any path specified using the
  99      * sun.java2d.fontpath property. Together with that property,  it is
 100      * initialised by the getPlatformFontPath() method
 101      * This call must be followed by a call to registerFontDirs(fontPath)
 102      * once any extra debugging path has been appended.
 103      */
 104     protected String fontPath;
 105 
 106     /* discoveredAllFonts is set to true when all fonts on the font path are
 107      * discovered. This usually also implies opening, validating and
 108      * registering, but an implementation may be optimized to avold this.
 109      * So see also "loadedAllFontFiles"
 110      */
 111     private boolean discoveredAllFonts = false;
 112 
 113     /* loadedAllFontFiles is set to true when all fonts on the font path are
 114      * actually opened, validated and registered. This always implies
 115      * discoveredAllFonts is true.
 116      */
 117     private boolean loadedAllFontFiles = false;
 118 
 119     protected HashSet registeredFontFiles = new HashSet();
 120     public static String eudcFontFileName; /* Initialised only on windows */
 121 
 122     private static boolean isOpenJDK;
 123     /**
 124      * A few things in Java 2D code are different in OpenJDK,
 125      * so need a way to tell which implementation this is.
 126      * The absence of Lucida Sans Regular is the simplest way for now.
 127      */
 128     public static boolean isOpenJDK() {
 129         return isOpenJDK;
 130     }
 131 
 132     static {
 133         java.security.AccessController.doPrivileged(
 134                                     new java.security.PrivilegedAction() {
 135             public Object run() {
 136 
 137                 jreLibDirName = System.getProperty("java.home","") +
 138                     File.separator + "lib";
 139                 jreFontDirName = jreLibDirName + File.separator + "fonts";
 140                 File lucidaFile =
 141                     new File(jreFontDirName + File.separator + lucidaFileName);
 142                 isOpenJDK = !lucidaFile.exists();
 143 
 144                 String debugLevel =
 145                     System.getProperty("sun.java2d.debugfonts");
 146 
 147                 if (debugLevel != null && !debugLevel.equals("false")) {
 148                     debugFonts = true;
 149                     logger = Logger.getLogger("sun.java2d");
 150                     if (debugLevel.equals("warning")) {
 151                         logger.setLevel(Level.WARNING);
 152                     } else if (debugLevel.equals("severe")) {
 153                         logger.setLevel(Level.SEVERE);
 154                     }
 155                 }
 156                 return null;
 157             }
 158         });
 159     };
 160 
 161     public SunGraphicsEnvironment() {
 162         java.security.AccessController.doPrivileged(
 163                                     new java.security.PrivilegedAction() {
 164             public Object run() {
 165                 String osName = System.getProperty("os.name");
 166                 if ("Linux".equals(osName)) {
 167                     isLinux = true;
 168                 } else if ("SunOS".equals(osName)) {
 169                     isSolaris = true;
 170                 } else if ("Windows".equals(osName)) {
 171                     isWindows = true;
 172                 }
 173 
 174                 noType1Font = "true".
 175                     equals(System.getProperty("sun.java2d.noType1Font"));
 176 
 177                 if (isOpenJDK()) {
 178                     String[] fontInfo = FontManager.getDefaultPlatformFont();
 179                     defaultFontName = fontInfo[0];
 180                     defaultFontFileName = fontInfo[1];
 181                 } else {
 182                     defaultFontName = lucidaFontName;
 183                     if (useAbsoluteFontFileNames()) {
 184                         defaultFontFileName =
 185                             jreFontDirName + File.separator + lucidaFileName;
 186                     } else {
 187                         defaultFontFileName = lucidaFileName;
 188                     }
 189                 }
 190 
 191                 File badFontFile =
 192                     new File(jreFontDirName + File.separator + "badfonts.txt");
 193                 if (badFontFile.exists()) {
 194                     FileInputStream fis = null;
 195                     try {
 196                         badFonts = new ArrayList();
 197                         fis = new FileInputStream(badFontFile);
 198                         InputStreamReader isr = new InputStreamReader(fis);
 199                         BufferedReader br = new BufferedReader(isr);
 200                         while (true) {
 201                             String name = br.readLine();
 202                             if (name == null) {
 203                                 break;
 204                             } else {
 205                                 if (debugFonts) {
 206                                     logger.warning("read bad font: " + name);
 207                                 }
 208                                 badFonts.add(name);
 209                             }
 210                         }
 211                     } catch (IOException e) {
 212                         try {
 213                             if (fis != null) {
 214                                 fis.close();
 215                             }
 216                         } catch (IOException ioe) {
 217                         }
 218                     }
 219                 }
 220 
 221                 /* Here we get the fonts in jre/lib/fonts and register them
 222                  * so they are always available and preferred over other fonts.
 223                  * This needs to be registered before the composite fonts as
 224                  * otherwise some native font that corresponds may be found
 225                  * as we don't have a way to handle two fonts of the same
 226                  * name, so the JRE one must be the first one registered.
 227                  * Pass "true" to registerFonts method as on-screen these
 228                  * JRE fonts always go through the T2K rasteriser.
 229                  */
 230                 if (isLinux) {
 231                     /* Linux font configuration uses these fonts */
 232                     registerFontDir(jreFontDirName);
 233                 }
 234                 registerFontsInDir(jreFontDirName, true, Font2D.JRE_RANK,
 235                                    true, false);
 236 
 237                 /* Register the JRE fonts so that the native platform can
 238                  * access them. This is used only on Windows so that when
 239                  * printing the printer driver can access the fonts.
 240                  */
 241                 registerJREFontsWithPlatform(jreFontDirName);
 242 
 243                 /* Create the font configuration and get any font path
 244                  * that might be specified.
 245                  */
 246                 fontConfig = createFontConfiguration();
 247                 getPlatformFontPathFromFontConfig();
 248 
 249                 String extraFontPath = fontConfig.getExtraFontPath();
 250 
 251                 /* In prior releases the debugging font path replaced
 252                  * all normally located font directories except for the
 253                  * JRE fonts dir. This directory is still always located and
 254                  * placed at the head of the path but as an augmentation
 255                  * to the previous behaviour the
 256                  * changes below allow you to additionally append to
 257                  * the font path by starting with append: or prepend by
 258                  * starting with a prepend: sign. Eg: to append
 259                  * -Dsun.java2d.fontpath=append:/usr/local/myfonts
 260                  * and to prepend
 261                  * -Dsun.java2d.fontpath=prepend:/usr/local/myfonts Disp
 262                  *
 263                  * If there is an appendedfontpath it in the font configuration
 264                  * it is used instead of searching the system for dirs.
 265                  * The behaviour of append and prepend is then similar
 266                  * to the normal case. ie it goes after what
 267                  * you prepend and * before what you append. If the
 268                  * sun.java2d.fontpath property is used, but it
 269                  * neither the append or prepend syntaxes is used then as
 270                  * except for the JRE dir the path is replaced and it is
 271                  * up to you to make sure that all the right directories
 272                  * are located. This is platform and locale-specific so
 273                  * its almost impossible to get right, so it should be
 274                  * used with caution.
 275                  */
 276                 boolean prependToPath = false;
 277                 boolean appendToPath = false;
 278                 String dbgFontPath = System.getProperty("sun.java2d.fontpath");
 279 
 280                 if (dbgFontPath != null) {
 281                     if (dbgFontPath.startsWith("prepend:")) {
 282                         prependToPath = true;
 283                         dbgFontPath =
 284                             dbgFontPath.substring("prepend:".length());
 285                     } else if (dbgFontPath.startsWith("append:")) {
 286                         appendToPath = true;
 287                         dbgFontPath =
 288                             dbgFontPath.substring("append:".length());
 289                     }
 290                 }
 291 
 292                 if (debugFonts) {
 293                     logger.info("JRE font directory: " + jreFontDirName);
 294                     logger.info("Extra font path: " + extraFontPath);
 295                     logger.info("Debug font path: " + dbgFontPath);
 296                 }
 297 
 298                 if (dbgFontPath != null) {
 299                     /* In debugging mode we register all the paths
 300                      * Caution: this is a very expensive call on Solaris:-
 301                      */
 302                     fontPath = getPlatformFontPath(noType1Font);
 303 
 304                     if (extraFontPath != null) {
 305                         fontPath =
 306                             extraFontPath + File.pathSeparator + fontPath;
 307                     }
 308                     if (appendToPath) {
 309                         fontPath = fontPath + File.pathSeparator + dbgFontPath;
 310                     } else if (prependToPath) {
 311                         fontPath = dbgFontPath + File.pathSeparator + fontPath;
 312                     } else {
 313                         fontPath = dbgFontPath;
 314                     }
 315                     registerFontDirs(fontPath);
 316                 } else if (extraFontPath != null) {
 317                     /* If the font configuration contains an "appendedfontpath"
 318                      * entry, it is interpreted as a set of locations that
 319                      * should always be registered.
 320                      * It may be additional to locations normally found for
 321                      * that place, or it may be locations that need to have
 322                      * all their paths registered to locate all the needed
 323                      * platform names.
 324                      * This is typically when the same .TTF file is referenced
 325                      * from multiple font.dir files and all of these must be
 326                      * read to find all the native (XLFD) names for the font,
 327                      * so that X11 font APIs can be used for as many code
 328                      * points as possible.
 329                      */
 330                     registerFontDirs(extraFontPath);
 331                 }
 332 
 333                 /* On Solaris, we need to register the Japanese TrueType
 334                  * directory so that we can find the corresponding bitmap
 335                  * fonts. This could be done by listing the directory in
 336                  * the font configuration file, but we don't want to
 337                  * confuse users with this quirk. There are no bitmap fonts
 338                  * for other writing systems that correspond to TrueType
 339                  * fonts and have matching XLFDs. We need to register the
 340                  * bitmap fonts only in environments where they're on the
 341                  * X font path, i.e., in the Japanese locale.
 342                  * Note that if the X Toolkit is in use the font path isn't
 343                  * set up by JDK, but users of a JA locale should have it
 344                  * set up already by their login environment.
 345                  */
 346                 if (isSolaris && Locale.JAPAN.equals(Locale.getDefault())) {
 347                     registerFontDir("/usr/openwin/lib/locale/ja/X11/fonts/TT");
 348                 }
 349 
 350                 initCompositeFonts(fontConfig, null);
 351 
 352                 /* Establish the default font to be used by SG2D etc */
 353                 defaultFont = new Font(Font.DIALOG, Font.PLAIN, 12);
 354 
 355                 return null;
 356             }
 357         });
 358     }
 359 
 360     protected GraphicsDevice[] screens;
 361 
 362     /**
 363      * Returns an array of all of the screen devices.
 364      */
 365     public synchronized GraphicsDevice[] getScreenDevices() {
 366         GraphicsDevice[] ret = screens;
 367         if (ret == null) {
 368             int num = getNumScreens();
 369             ret = new GraphicsDevice[num];
 370             for (int i = 0; i < num; i++) {
 371                 ret[i] = makeScreenDevice(i);
 372             }
 373             screens = ret;
 374         }
 375         return ret;
 376     }
 377 
 378     protected abstract int getNumScreens();
 379     protected abstract GraphicsDevice makeScreenDevice(int screennum);
 380 
 381     /**
 382      * Returns the default screen graphics device.
 383      */
 384     public GraphicsDevice getDefaultScreenDevice() {
 385         return getScreenDevices()[0];
 386     }
 387 
 388     /**
 389      * Returns a Graphics2D object for rendering into the
 390      * given BufferedImage.
 391      * @throws NullPointerException if BufferedImage argument is null
 392      */
 393     public Graphics2D createGraphics(BufferedImage img) {
 394         if (img == null) {
 395             throw new NullPointerException("BufferedImage cannot be null");
 396         }
 397         SurfaceData sd = SurfaceData.getPrimarySurfaceData(img);
 398         return new SunGraphics2D(sd, Color.white, Color.black, defaultFont);
 399     }
 400 
 401     /* A call to this method should be followed by a call to
 402      * registerFontDirs(..)
 403      */
 404     protected String getPlatformFontPath(boolean noType1Font) {
 405         if (fontPath == null) {
 406             fontPath = FontManager.getFontPath(noType1Font);
 407         }
 408         return fontPath;
 409     }
 410 
 411     private String[] platformFontDirs;
 412     /**
 413      * Get all directories which contain installed fonts.
 414      */
 415     public String[] getPlatformFontDirs() {
 416         if (platformFontDirs == null) {
 417             String path = getPlatformFontPath(noType1Font);
 418             StringTokenizer parser =
 419                 new StringTokenizer(path, File.pathSeparator);;
 420             ArrayList<String> pathList = new ArrayList<String>();
 421             try {
 422                 while (parser.hasMoreTokens()) {
 423                     pathList.add(parser.nextToken());
 424                 }
 425             } catch (NoSuchElementException e) {
 426             }
 427             platformFontDirs = pathList.toArray(new String[0]);
 428         }
 429         return platformFontDirs;
 430     }
 431 
 432     /**
 433      * Whether registerFontFile expects absolute or relative
 434      * font file names.
 435      */
 436     protected boolean useAbsoluteFontFileNames() {
 437         return true;
 438     }
 439 
 440     /**
 441      * Returns file name for default font, either absolute
 442      * or relative as needed by registerFontFile.
 443      */
 444     public String getDefaultFontFile() {
 445         return defaultFontFileName;
 446     }
 447 
 448     /**
 449      * Returns face name for default font, or null if
 450      * no face names are used for CompositeFontDescriptors
 451      * for this platform.
 452      */
 453     public String getDefaultFontFaceName() {
 454         return defaultFontName;
 455     }
 456 
 457     public void loadFonts() {
 458         if (discoveredAllFonts) {
 459             return;
 460         }
 461         /* Use lock specific to the font system */
 462         synchronized (FontManager.class) {
 463             if (debugFonts) {
 464                 Thread.dumpStack();
 465                 logger.info("SunGraphicsEnvironment.loadFonts() called");
 466             }
 467             FontManager.initialiseDeferredFonts();
 468 
 469             java.security.AccessController.doPrivileged(
 470                                     new java.security.PrivilegedAction() {
 471                 public Object run() {
 472                     if (fontPath == null) {
 473                         fontPath = getPlatformFontPath(noType1Font);
 474                         registerFontDirs(fontPath);
 475                     }
 476                     if (fontPath != null) {
 477                         // this will find all fonts including those already
 478                         // registered. But we have checks in place to prevent
 479                         // double registration.
 480                         if (!FontManager.gotFontsFromPlatform()) {
 481                             registerFontsOnPath(fontPath, false,
 482                                                 Font2D.UNKNOWN_RANK,
 483                                                 false, true);
 484                             loadedAllFontFiles = true;
 485                         }
 486                     }
 487                     FontManager.registerOtherFontFiles(registeredFontFiles);
 488                     discoveredAllFonts = true;
 489                     return null;
 490                 }
 491             });
 492         }
 493     }
 494 
 495 
 496     public void loadFontFiles() {
 497         loadFonts();
 498         if (loadedAllFontFiles) {
 499             return;
 500         }
 501         /* Use lock specific to the font system */
 502         synchronized (FontManager.class) {
 503             if (debugFonts) {
 504                 Thread.dumpStack();
 505                 logger.info("loadAllFontFiles() called");
 506             }
 507             java.security.AccessController.doPrivileged(
 508                                     new java.security.PrivilegedAction() {
 509                 public Object run() {
 510                     if (fontPath == null) {
 511                         fontPath = getPlatformFontPath(noType1Font);
 512                     }
 513                     if (fontPath != null) {
 514                         // this will find all fonts including those already
 515                         // registered. But we have checks in place to prevent
 516                         // double registration.
 517                         registerFontsOnPath(fontPath, false,
 518                                             Font2D.UNKNOWN_RANK,
 519                                             false, true);
 520                     }
 521                     loadedAllFontFiles = true;
 522                     return null;
 523                 }
 524             });
 525         }
 526     }
 527 
 528     /*
 529      * This is for use only within getAllFonts().
 530      * Fonts listed in the fontconfig files for windows were all
 531      * on the "deferred" initialisation list. They were registered
 532      * either in the course of the application, or in the call to
 533      * loadFonts() within getAllFonts(). The fontconfig file specifies
 534      * the names of the fonts using the English names. If there's a
 535      * different name in the execution locale, then the platform will
 536      * report that, and we will construct the font with both names, and
 537      * thereby enumerate it twice. This happens for Japanese fonts listed
 538      * in the windows fontconfig, when run in the JA locale. The solution
 539      * is to rely (in this case) on the platform's font->file mapping to
 540      * determine that this name corresponds to a file we already registered.
 541      * This works because
 542      * - we know when we get here all deferred fonts are already initialised
 543      * - when we register a font file, we register all fonts in it.
 544      * - we know the fontconfig fonts are all in the windows registry
 545      */
 546     private boolean isNameForRegisteredFile(String fontName) {
 547         String fileName = FontManager.getFileNameForFontName(fontName);
 548         if (fileName == null) {
 549             return false;
 550         }
 551         return registeredFontFiles.contains(fileName);
 552     }
 553 
 554     private Font[] allFonts;
 555 
 556     /**
 557      * Returns all fonts installed in this environment.
 558      */
 559     public Font[] getAllInstalledFonts() {
 560         if (allFonts == null) {
 561             loadFonts();
 562             TreeMap fontMapNames = new TreeMap();
 563             /* warning: the number of composite fonts could change dynamically
 564              * if applications are allowed to create them. "allfonts" could
 565              * then be stale.
 566              */
 567 
 568             Font2D[] allfonts = FontManager.getRegisteredFonts();
 569             for (int i=0; i < allfonts.length; i++) {
 570                 if (!(allfonts[i] instanceof NativeFont)) {
 571                     fontMapNames.put(allfonts[i].getFontName(null),
 572                                      allfonts[i]);
 573                 }
 574             }
 575 
 576             String[] platformNames =  FontManager.getFontNamesFromPlatform();
 577             if (platformNames != null) {
 578                 for (int i=0; i<platformNames.length; i++) {
 579                     if (!isNameForRegisteredFile(platformNames[i])) {
 580                         fontMapNames.put(platformNames[i], null);
 581                     }
 582                 }
 583             }
 584 
 585             String[] fontNames = null;
 586             if (fontMapNames.size() > 0) {
 587                 fontNames = new String[fontMapNames.size()];
 588                 Object [] keyNames = fontMapNames.keySet().toArray();
 589                 for (int i=0; i < keyNames.length; i++) {
 590                     fontNames[i] = (String)keyNames[i];
 591                 }
 592             }
 593             Font[] fonts = new Font[fontNames.length];
 594             for (int i=0; i < fontNames.length; i++) {
 595                 fonts[i] = new Font(fontNames[i], Font.PLAIN, 1);
 596                 Font2D f2d = (Font2D)fontMapNames.get(fontNames[i]);
 597                 if (f2d  != null) {
 598                     FontManager.setFont2D(fonts[i], f2d.handle);
 599                 }
 600             }
 601             allFonts = fonts;
 602         }
 603 
 604         Font []copyFonts = new Font[allFonts.length];
 605         System.arraycopy(allFonts, 0, copyFonts, 0, allFonts.length);
 606         return copyFonts;
 607     }
 608 
 609      /**
 610      * Returns all fonts available in this environment.
 611      */
 612     public Font[] getAllFonts() {
 613         Font[] installedFonts = getAllInstalledFonts();
 614         Font[] created = FontManager.getCreatedFonts();
 615         if (created == null || created.length == 0) {
 616             return installedFonts;
 617         } else {
 618             int newlen = installedFonts.length + created.length;
 619             Font [] fonts = java.util.Arrays.copyOf(installedFonts, newlen);
 620             System.arraycopy(created, 0, fonts,
 621                              installedFonts.length, created.length);
 622             return fonts;
 623         }
 624     }
 625 
 626     /**
 627      * Default locale can be changed but we need to know the initial locale
 628      * as that is what is used by native code. Changing Java default locale
 629      * doesn't affect that.
 630      * Returns the locale in use when using native code to communicate
 631      * with platform APIs. On windows this is known as the "system" locale,
 632      * and it is usually the same as the platform locale, but not always,
 633      * so this method also checks an implementation property used only
 634      * on windows and uses that if set.
 635      */
 636     private static Locale systemLocale = null;
 637     public static Locale getSystemStartupLocale() {
 638         if (systemLocale == null) {
 639             systemLocale = (Locale)
 640                 java.security.AccessController.doPrivileged(
 641                                     new java.security.PrivilegedAction() {
 642             public Object run() {
 643                 /* On windows the system locale may be different than the
 644                  * user locale. This is an unsupported configuration, but
 645                  * in that case we want to return a dummy locale that will
 646                  * never cause a match in the usage of this API. This is
 647                  * important because Windows documents that the family
 648                  * names of fonts are enumerated using the language of
 649                  * the system locale. BY returning a dummy locale in that
 650                  * case we do not use the platform API which would not
 651                  * return us the names we want.
 652                  */
 653                 String fileEncoding = System.getProperty("file.encoding", "");
 654                 String sysEncoding = System.getProperty("sun.jnu.encoding");
 655                 if (sysEncoding != null && !sysEncoding.equals(fileEncoding)) {
 656                     return Locale.ROOT;
 657                 }
 658 
 659                 String language = System.getProperty("user.language", "en");
 660                 String country  = System.getProperty("user.country","");
 661                 String variant  = System.getProperty("user.variant","");
 662                 return new Locale(language, country, variant);
 663             }
 664         });
 665         }
 666         return systemLocale;
 667     }
 668 
 669     /* Really we need only the JRE fonts family names, but there's little
 670      * overhead in doing this the easy way by adding all the currently
 671      * known fonts.
 672      */
 673     protected void getJREFontFamilyNames(TreeMap<String,String> familyNames,
 674                                          Locale requestedLocale) {
 675         FontManager.registerDeferredJREFonts(jreFontDirName);
 676         Font2D[] physicalfonts = FontManager.getPhysicalFonts();
 677         for (int i=0; i < physicalfonts.length; i++) {
 678             if (!(physicalfonts[i] instanceof NativeFont)) {
 679                 String name =
 680                     physicalfonts[i].getFamilyName(requestedLocale);
 681                 familyNames.put(name.toLowerCase(requestedLocale), name);
 682             }
 683         }
 684     }
 685 
 686     private String[] allFamilies; // cache for default locale only
 687     private Locale lastDefaultLocale;
 688 
 689     public String[] getInstalledFontFamilyNames(Locale requestedLocale) {
 690         if (requestedLocale == null) {
 691             requestedLocale = Locale.getDefault();
 692         }
 693         if (allFamilies != null && lastDefaultLocale != null &&
 694             requestedLocale.equals(lastDefaultLocale)) {
 695                 String[] copyFamilies = new String[allFamilies.length];
 696                 System.arraycopy(allFamilies, 0, copyFamilies,
 697                                  0, allFamilies.length);
 698                 return copyFamilies;
 699         }
 700 
 701         TreeMap<String,String> familyNames = new TreeMap<String,String>();
 702         //  these names are always there and aren't localised
 703         String str;
 704         str = Font.SERIF;         familyNames.put(str.toLowerCase(), str);
 705         str = Font.SANS_SERIF;    familyNames.put(str.toLowerCase(), str);
 706         str = Font.MONOSPACED;    familyNames.put(str.toLowerCase(), str);
 707         str = Font.DIALOG;        familyNames.put(str.toLowerCase(), str);
 708         str = Font.DIALOG_INPUT;  familyNames.put(str.toLowerCase(), str);
 709 
 710         /* Platform APIs may be used to get the set of available family
 711          * names for the current default locale so long as it is the same
 712          * as the start-up system locale, rather than loading all fonts.
 713          */
 714         if (requestedLocale.equals(getSystemStartupLocale()) &&
 715             FontManager.getFamilyNamesFromPlatform(familyNames,
 716                                                     requestedLocale)) {
 717             /* Augment platform names with JRE font family names */
 718             getJREFontFamilyNames(familyNames, requestedLocale);
 719         } else {
 720             loadFontFiles();
 721             Font2D[] physicalfonts = FontManager.getPhysicalFonts();
 722             for (int i=0; i < physicalfonts.length; i++) {
 723                 if (!(physicalfonts[i] instanceof NativeFont)) {
 724                     String name =
 725                         physicalfonts[i].getFamilyName(requestedLocale);
 726                     familyNames.put(name.toLowerCase(requestedLocale), name);
 727                 }
 728             }
 729         }
 730 
 731         String[] retval =  new String[familyNames.size()];
 732         Object [] keyNames = familyNames.keySet().toArray();
 733         for (int i=0; i < keyNames.length; i++) {
 734             retval[i] = (String)familyNames.get(keyNames[i]);
 735         }
 736         if (requestedLocale.equals(Locale.getDefault())) {
 737             lastDefaultLocale = requestedLocale;
 738             allFamilies = new String[retval.length];
 739             System.arraycopy(retval, 0, allFamilies, 0, allFamilies.length);
 740         }
 741         return retval;
 742     }
 743 
 744     public String[] getAvailableFontFamilyNames(Locale requestedLocale) {
 745         String[] installed = getInstalledFontFamilyNames(requestedLocale);
 746         /* Use a new TreeMap as used in getInstalledFontFamilyNames
 747          * and insert all the keys in lower case, so that the sort order
 748          * is the same as the installed families. This preserves historical
 749          * behaviour and inserts new families in the right place.
 750          * It would have been marginally more efficient to directly obtain
 751          * the tree map and just insert new entries, but not so much as
 752          * to justify the extra internal interface.
 753          */
 754         TreeMap<String, String> map = FontManager.getCreatedFontFamilyNames();
 755         if (map == null || map.size() == 0) {
 756             return installed;
 757         } else {
 758             for (int i=0; i<installed.length; i++) {
 759                 map.put(installed[i].toLowerCase(requestedLocale),
 760                         installed[i]);
 761             }
 762             String[] retval =  new String[map.size()];
 763             Object [] keyNames = map.keySet().toArray();
 764             for (int i=0; i < keyNames.length; i++) {
 765                 retval[i] = (String)map.get(keyNames[i]);
 766             }
 767             return retval;
 768         }
 769     }
 770 
 771     public String[] getAvailableFontFamilyNames() {
 772         return getAvailableFontFamilyNames(Locale.getDefault());
 773     }
 774 
 775     /**
 776      * Returns a file name for the physical font represented by this platform
 777      * font name. The default implementation tries to obtain the file name
 778      * from the font configuration.
 779      * Subclasses may override to provide information from other sources.
 780      */
 781     protected String getFileNameFromPlatformName(String platformFontName) {
 782         return fontConfig.getFileNameFromPlatformName(platformFontName);
 783     }
 784 
 785     public static class TTFilter implements FilenameFilter {
 786         public boolean accept(File dir,String name) {
 787             /* all conveniently have the same suffix length */
 788             int offset = name.length()-4;
 789             if (offset <= 0) { /* must be at least A.ttf */
 790                 return false;
 791             } else {
 792                 return(name.startsWith(".ttf", offset) ||
 793                        name.startsWith(".TTF", offset) ||
 794                        name.startsWith(".ttc", offset) ||
 795                        name.startsWith(".TTC", offset));
 796             }
 797         }
 798     }
 799 
 800     public static class T1Filter implements FilenameFilter {
 801         public boolean accept(File dir,String name) {
 802             if (noType1Font) {
 803                 return false;
 804             }
 805             /* all conveniently have the same suffix length */
 806             int offset = name.length()-4;
 807             if (offset <= 0) { /* must be at least A.pfa */
 808                 return false;
 809             } else {
 810                 return(name.startsWith(".pfa", offset) ||
 811                        name.startsWith(".pfb", offset) ||
 812                        name.startsWith(".PFA", offset) ||
 813                        name.startsWith(".PFB", offset));
 814             }
 815         }
 816     }
 817 
 818      public static class TTorT1Filter implements FilenameFilter {
 819         public boolean accept(File dir, String name) {
 820 
 821             /* all conveniently have the same suffix length */
 822             int offset = name.length()-4;
 823             if (offset <= 0) { /* must be at least A.ttf or A.pfa */
 824                 return false;
 825             } else {
 826                 boolean isTT =
 827                     name.startsWith(".ttf", offset) ||
 828                     name.startsWith(".TTF", offset) ||
 829                     name.startsWith(".ttc", offset) ||
 830                     name.startsWith(".TTC", offset);
 831                 if (isTT) {
 832                     return true;
 833                 } else if (noType1Font) {
 834                     return false;
 835                 } else {
 836                     return(name.startsWith(".pfa", offset) ||
 837                            name.startsWith(".pfb", offset) ||
 838                            name.startsWith(".PFA", offset) ||
 839                            name.startsWith(".PFB", offset));
 840                 }
 841             }
 842         }
 843     }
 844 
 845     /* No need to keep consing up new instances - reuse a singleton.
 846      * The trade-off is that these objects don't get GC'd.
 847      */
 848     public static final TTFilter ttFilter = new TTFilter();
 849     public static final T1Filter t1Filter = new T1Filter();
 850 
 851     /* The majority of the register functions in this class are
 852      * registering platform fonts in the JRE's font maps.
 853      * The next one is opposite in function as it registers the JRE
 854      * fonts as platform fonts. If subsequent to calling this
 855      * your implementation enumerates platform fonts in a way that
 856      * would return these fonts too you may get duplicates.
 857      * This function is primarily used to install the JRE fonts
 858      * so that the native platform can access them.
 859      * It is intended to be overridden by platform subclasses
 860      * Currently minimal use is made of this as generally
 861      * Java 2D doesn't need the platform to be able to
 862      * use its fonts and platforms which already have matching
 863      * fonts registered (possibly even from other different JRE
 864      * versions) generally can't be guaranteed to use the
 865      * one registered by this JRE version in response to
 866      * requests from this JRE.
 867      */
 868     protected void registerJREFontsWithPlatform(String pathName) {
 869         return;
 870     }
 871 
 872     /* Called from FontManager - has Solaris specific implementation */
 873     public void register1dot0Fonts() {
 874         java.security.AccessController.doPrivileged(
 875                             new java.security.PrivilegedAction() {
 876             public Object run() {
 877                 String type1Dir = "/usr/openwin/lib/X11/fonts/Type1";
 878                 registerFontsInDir(type1Dir, true, Font2D.TYPE1_RANK,
 879                                    false, false);
 880                 return null;
 881             }
 882         });
 883     }
 884 
 885     protected void registerFontDirs(String pathName) {
 886         return;
 887     }
 888 
 889     /* Called to register fall back fonts */
 890     public void registerFontsInDir(String dirName) {
 891         registerFontsInDir(dirName, true, Font2D.JRE_RANK, true, false);
 892     }
 893 
 894     private void registerFontsInDir(String dirName, boolean useJavaRasterizer,
 895                                     int fontRank,
 896                                     boolean defer, boolean resolveSymLinks) {
 897         File pathFile = new File(dirName);
 898         addDirFonts(dirName, pathFile, ttFilter,
 899                     FontManager.FONTFORMAT_TRUETYPE, useJavaRasterizer,
 900                     fontRank==Font2D.UNKNOWN_RANK ?
 901                     Font2D.TTF_RANK : fontRank,
 902                     defer, resolveSymLinks);
 903         addDirFonts(dirName, pathFile, t1Filter,
 904                     FontManager.FONTFORMAT_TYPE1, useJavaRasterizer,
 905                     fontRank==Font2D.UNKNOWN_RANK ?
 906                     Font2D.TYPE1_RANK : fontRank,
 907                     defer, resolveSymLinks);
 908     }
 909 
 910     private void registerFontsOnPath(String pathName,
 911                                      boolean useJavaRasterizer, int fontRank,
 912                                      boolean defer, boolean resolveSymLinks) {
 913 
 914         StringTokenizer parser = new StringTokenizer(pathName,
 915                                                      File.pathSeparator);
 916         try {
 917             while (parser.hasMoreTokens()) {
 918                 registerFontsInDir(parser.nextToken(),
 919                                    useJavaRasterizer, fontRank,
 920                                    defer, resolveSymLinks);
 921             }
 922         } catch (NoSuchElementException e) {
 923         }
 924     }
 925 
 926     protected void registerFontFile(String fontFileName, String[] nativeNames,
 927                                     int fontRank, boolean defer) {
 928         // REMIND: case compare depends on platform
 929         if (registeredFontFiles.contains(fontFileName)) {
 930             return;
 931         }
 932         int fontFormat;
 933         if (ttFilter.accept(null, fontFileName)) {
 934             fontFormat = FontManager.FONTFORMAT_TRUETYPE;
 935         } else if (t1Filter.accept(null, fontFileName)) {
 936             fontFormat = FontManager.FONTFORMAT_TYPE1;
 937         } else {
 938             fontFormat = FontManager.FONTFORMAT_NATIVE;
 939         }
 940         registeredFontFiles.add(fontFileName);
 941         if (defer) {
 942             FontManager.registerDeferredFont(fontFileName,
 943                                              fontFileName, nativeNames,
 944                                              fontFormat, false, fontRank);
 945         } else {
 946             FontManager.registerFontFile(fontFileName, nativeNames,
 947                                          fontFormat, false, fontRank);
 948         }
 949     }
 950 
 951     protected void registerFontDir(String path) {
 952     }
 953 
 954     protected String[] getNativeNames(String fontFileName,
 955                                       String platformName) {
 956         return null;
 957     }
 958 
 959     /*
 960      * helper function for registerFonts
 961      */
 962     private void addDirFonts(String dirName, File dirFile,
 963                              FilenameFilter filter,
 964                              int fontFormat, boolean useJavaRasterizer,
 965                              int fontRank,
 966                              boolean defer, boolean resolveSymLinks) {
 967         String[] ls = dirFile.list(filter);
 968         if (ls == null || ls.length == 0) {
 969             return;
 970         }
 971         String[] fontNames = new String[ls.length];
 972         String[][] nativeNames = new String[ls.length][];
 973         int fontCount = 0;
 974 
 975         for (int i=0; i < ls.length; i++ ) {
 976             File theFile = new File(dirFile, ls[i]);
 977             String fullName = null;
 978             if (resolveSymLinks) {
 979                 try {
 980                     fullName = theFile.getCanonicalPath();
 981                 } catch (IOException e) {
 982                 }
 983             }
 984             if (fullName == null) {
 985                 fullName = dirName + File.separator + ls[i];
 986             }
 987 
 988             // REMIND: case compare depends on platform
 989             if (registeredFontFiles.contains(fullName)) {
 990                 continue;
 991             }
 992 
 993             if (badFonts != null && badFonts.contains(fullName)) {
 994                 if (debugFonts) {
 995                     logger.warning("skip bad font " + fullName);
 996                 }
 997                 continue; // skip this font file.
 998             }
 999 
1000             registeredFontFiles.add(fullName);
1001 
1002             if (debugFonts && logger.isLoggable(Level.INFO)) {
1003                 String message = "Registering font " + fullName;
1004                 String[] natNames = getNativeNames(fullName, null);
1005                 if (natNames == null) {
1006                     message += " with no native name";
1007                 } else {
1008                     message += " with native name(s) " + natNames[0];
1009                     for (int nn = 1; nn < natNames.length; nn++) {
1010                         message += ", " + natNames[nn];
1011                     }
1012                 }
1013                 logger.info(message);
1014             }
1015             fontNames[fontCount] = fullName;
1016             nativeNames[fontCount++] = getNativeNames(fullName, null);
1017         }
1018         FontManager.registerFonts(fontNames, nativeNames, fontCount,
1019                                   fontFormat, useJavaRasterizer, fontRank,
1020                                   defer);
1021         return;
1022     }
1023 
1024     /*
1025      * A GE may verify whether a font file used in a fontconfiguration
1026      * exists. If it doesn't then either we may substitute the default
1027      * font, or perhaps elide it altogether from the composite font.
1028      * This makes some sense on windows where the font file is only
1029      * likely to be in one place. But on other OSes, eg Linux, the file
1030      * can move around depending. So there we probably don't want to assume
1031      * its missing and so won't add it to this list.
1032      * If this list - missingFontFiles - is non-null then the composite
1033      * font initialisation logic tests to see if a font file is in that
1034      * set.
1035      * Only one thread should be able to add to this set so we don't
1036      * synchronize.
1037      */
1038     protected void addToMissingFontFileList(String fileName) {
1039         if (missingFontFiles == null) {
1040             missingFontFiles = new HashSet<String>();
1041         }
1042         missingFontFiles.add(fileName);
1043     }
1044 
1045     /**
1046      * Creates this environment's FontConfiguration.
1047      */
1048     protected abstract FontConfiguration createFontConfiguration();
1049 
1050     public abstract FontConfiguration
1051         createFontConfiguration(boolean preferLocaleFonts,
1052                                 boolean preferPropFonts);
1053 
1054     /*
1055      * This method asks the font configuration API for all platform names
1056      * used as components of composite/logical fonts and iterates over these
1057      * looking up their corresponding file name and registers these fonts.
1058      * It also ensures that the fonts are accessible via platform APIs.
1059      * The composites themselves are then registered.
1060      */
1061     private void
1062         initCompositeFonts(FontConfiguration fontConfig,
1063                            ConcurrentHashMap<String, Font2D>  altNameCache) {
1064 
1065         int numCoreFonts = fontConfig.getNumberCoreFonts();
1066         String[] fcFonts = fontConfig.getPlatformFontNames();
1067         for (int f=0; f<fcFonts.length; f++) {
1068             String platformFontName = fcFonts[f];
1069             String fontFileName =
1070                 getFileNameFromPlatformName(platformFontName);
1071             String[] nativeNames = null;
1072             if (fontFileName == null) {
1073                 /* No file located, so register using the platform name,
1074                  * i.e. as a native font.
1075                  */
1076                 fontFileName = platformFontName;
1077             } else {
1078                 if (f < numCoreFonts) {
1079                     /* If platform APIs also need to access the font, add it
1080                      * to a set to be registered with the platform too.
1081                      * This may be used to add the parent directory to the X11
1082                      * font path if its not already there. See the docs for the
1083                      * subclass implementation.
1084                      * This is now mainly for the benefit of X11-based AWT
1085                      * But for historical reasons, 2D initialisation code
1086                      * makes these calls.
1087                      * If the fontconfiguration file is properly set up
1088                      * so that all fonts are mapped to files and all their
1089                      * appropriate directories are specified, then this
1090                      * method will be low cost as it will return after
1091                      * a test that finds a null lookup map.
1092                      */
1093                     addFontToPlatformFontPath(platformFontName);
1094                 }
1095                 nativeNames = getNativeNames(fontFileName, platformFontName);
1096             }
1097             /* Uncomment these two lines to "generate" the XLFD->filename
1098              * mappings needed to speed start-up on Solaris.
1099              * Augment this with the appendedpathname and the mappings
1100              * for native (F3) fonts
1101              */
1102             //String platName = platformFontName.replaceAll(" ", "_");
1103             //System.out.println("filename."+platName+"="+fontFileName);
1104             registerFontFile(fontFileName, nativeNames,
1105                              Font2D.FONT_CONFIG_RANK, true);
1106 
1107 
1108         }
1109         /* This registers accumulated paths from the calls to
1110          * addFontToPlatformFontPath(..) and any specified by
1111          * the font configuration. Rather than registering
1112          * the fonts it puts them in a place and form suitable for
1113          * the Toolkit to pick up and use if a toolkit is initialised,
1114          * and if it uses X11 fonts.
1115          */
1116         registerPlatformFontsUsedByFontConfiguration();
1117 
1118         CompositeFontDescriptor[] compositeFontInfo
1119                 = fontConfig.get2DCompositeFontInfo();
1120         for (int i = 0; i < compositeFontInfo.length; i++) {
1121             CompositeFontDescriptor descriptor = compositeFontInfo[i];
1122             String[] componentFileNames = descriptor.getComponentFileNames();
1123             String[] componentFaceNames = descriptor.getComponentFaceNames();
1124 
1125             /* It would be better eventually to handle this in the
1126              * FontConfiguration code which should also remove duplicate slots
1127              */
1128             if (missingFontFiles != null) {
1129                 for (int ii=0; ii<componentFileNames.length; ii++) {
1130                     if (missingFontFiles.contains(componentFileNames[ii])) {
1131                         componentFileNames[ii] = getDefaultFontFile();
1132                         componentFaceNames[ii] = getDefaultFontFaceName();
1133                     }
1134                 }
1135             }
1136 
1137             /* FontConfiguration needs to convey how many fonts it has added
1138              * as fallback component fonts which should not affect metrics.
1139              * The core component count will be the number of metrics slots.
1140              * This does not preclude other mechanisms for adding
1141              * fall back component fonts to the composite.
1142              */
1143             if (altNameCache != null) {
1144                 FontManager.registerCompositeFont(
1145                     descriptor.getFaceName(),
1146                     componentFileNames, componentFaceNames,
1147                     descriptor.getCoreComponentCount(),
1148                     descriptor.getExclusionRanges(),
1149                     descriptor.getExclusionRangeLimits(),
1150                     true,
1151                     altNameCache);
1152             } else {
1153                 FontManager.registerCompositeFont(
1154                     descriptor.getFaceName(),
1155                     componentFileNames, componentFaceNames,
1156                     descriptor.getCoreComponentCount(),
1157                     descriptor.getExclusionRanges(),
1158                     descriptor.getExclusionRangeLimits(),
1159                     true);
1160             }
1161             if (debugFonts) {
1162                 logger.info("registered " + descriptor.getFaceName());
1163             }
1164         }
1165     }
1166 
1167     /**
1168      * Notifies graphics environment that the logical font configuration
1169      * uses the given platform font name. The graphics environment may
1170      * use this for platform specific initialization.
1171      */
1172     protected void addFontToPlatformFontPath(String platformFontName) {
1173     }
1174 
1175     protected void registerPlatformFontsUsedByFontConfiguration() {
1176     }
1177 
1178     /**
1179      * Determines whether the given font is a logical font.
1180      */
1181     public static boolean isLogicalFont(Font f) {
1182         return FontConfiguration.isLogicalFontFamilyName(f.getFamily());
1183     }
1184 
1185     /**
1186      * Return the default font configuration.
1187      */
1188     public FontConfiguration getFontConfiguration() {
1189         return fontConfig;
1190     }
1191 
1192     /**
1193      * Return the bounds of a GraphicsDevice, less its screen insets.
1194      * See also java.awt.GraphicsEnvironment.getUsableBounds();
1195      */
1196     public static Rectangle getUsableBounds(GraphicsDevice gd) {
1197         GraphicsConfiguration gc = gd.getDefaultConfiguration();
1198         Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
1199         Rectangle usableBounds = gc.getBounds();
1200 
1201         usableBounds.x += insets.left;
1202         usableBounds.y += insets.top;
1203         usableBounds.width -= (insets.left + insets.right);
1204         usableBounds.height -= (insets.top + insets.bottom);
1205 
1206         return usableBounds;
1207     }
1208 
1209     /**
1210      * This method is provided for internal and exclusive use by Swing.
1211      * This method should no longer be called, instead directly call
1212      * FontManager.fontSupportsDefaultEncoding(Font).
1213      * This method will be removed once Swing is updated to no longer
1214      * call it.
1215      */
1216     public static boolean fontSupportsDefaultEncoding(Font font) {
1217         return FontManager.fontSupportsDefaultEncoding(font);
1218     }
1219 
1220     public static void useAlternateFontforJALocales() {
1221         FontManager.useAlternateFontforJALocales();
1222     }
1223 
1224     /*
1225      * This invocation is not in a privileged block because
1226      * all privileged operations (reading files and properties)
1227      * was conducted on the creation of the GE
1228      */
1229     public void
1230         createCompositeFonts(ConcurrentHashMap<String, Font2D> altNameCache,
1231                              boolean preferLocale,
1232                              boolean preferProportional) {
1233 
1234         FontConfiguration fontConfig =
1235             createFontConfiguration(preferLocale, preferProportional);
1236         initCompositeFonts(fontConfig, altNameCache);
1237     }
1238 
1239     /* If (as we do on X11) need to set a platform font path,
1240      * then the needed path may be specified by the platform
1241      * specific FontConfiguration class & data file. Such platforms
1242      * (ie X11) need to override this method to retrieve this information
1243      * into suitable data structures.
1244      */
1245     protected void getPlatformFontPathFromFontConfig() {
1246     }
1247 
1248     /**
1249      * From the DisplayChangedListener interface; called
1250      * when the display mode has been changed.
1251      */
1252     public void displayChanged() {
1253         // notify screens in device array to do display update stuff
1254         for (GraphicsDevice gd : getScreenDevices()) {
1255             if (gd instanceof DisplayChangedListener) {
1256                 ((DisplayChangedListener) gd).displayChanged();
1257             }
1258         }
1259 
1260         // notify SunDisplayChanger list (e.g. VolatileSurfaceManagers and
1261         // SurfaceDataProxies) about the display change event
1262         displayChanger.notifyListeners();
1263     }
1264 
1265     /**
1266      * Part of the DisplayChangedListener interface:
1267      * propagate this event to listeners
1268      */
1269     public void paletteChanged() {
1270         displayChanger.notifyPaletteChanged();
1271     }
1272 
1273     /*
1274      * ----DISPLAY CHANGE SUPPORT----
1275      */
1276 
1277     protected SunDisplayChanger displayChanger = new SunDisplayChanger();
1278 
1279     /**
1280      * Add a DisplayChangeListener to be notified when the display settings
1281      * are changed.
1282      */
1283     public void addDisplayChangedListener(DisplayChangedListener client) {
1284         displayChanger.add(client);
1285     }
1286 
1287     /**
1288      * Remove a DisplayChangeListener from Win32GraphicsEnvironment
1289      */
1290     public void removeDisplayChangedListener(DisplayChangedListener client) {
1291         displayChanger.remove(client);
1292     }
1293 
1294     /*
1295      * ----END DISPLAY CHANGE SUPPORT----
1296      */
1297 }