Home | History | Annotate | Download | only in editors
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 
     18 package com.android.ide.eclipse.adt.internal.editors;
     19 
     20 import com.android.ide.eclipse.adt.AdtPlugin;
     21 import com.android.sdklib.SdkConstants;
     22 
     23 import org.eclipse.jface.resource.ImageDescriptor;
     24 import org.eclipse.swt.SWT;
     25 import org.eclipse.swt.graphics.Color;
     26 import org.eclipse.swt.graphics.Font;
     27 import org.eclipse.swt.graphics.FontData;
     28 import org.eclipse.swt.graphics.GC;
     29 import org.eclipse.swt.graphics.Image;
     30 import org.eclipse.swt.graphics.ImageData;
     31 import org.eclipse.swt.graphics.Point;
     32 import org.eclipse.swt.graphics.RGB;
     33 import org.eclipse.swt.widgets.Display;
     34 import org.eclipse.ui.plugin.AbstractUIPlugin;
     35 
     36 import java.net.URL;
     37 import java.util.HashMap;
     38 
     39 /**
     40  * Factory to generate icons for Android Editors.
     41  * <p/>
     42  * Icons are kept here and reused.
     43  */
     44 public class IconFactory {
     45     public static final int COLOR_RED     = SWT.COLOR_DARK_RED;
     46     public static final int COLOR_GREEN   = SWT.COLOR_DARK_GREEN;
     47     public static final int COLOR_BLUE    = SWT.COLOR_DARK_BLUE;
     48     public static final int COLOR_DEFAULT = SWT.COLOR_BLACK;
     49 
     50     public static final int SHAPE_CIRCLE  = 'C';
     51     public static final int SHAPE_RECT    = 'R';
     52     public static final int SHAPE_DEFAULT = SHAPE_CIRCLE;
     53 
     54     private static IconFactory sInstance;
     55 
     56     private HashMap<String, Image> mIconMap = new HashMap<String, Image>();
     57     private HashMap<URL, Image> mUrlMap = new HashMap<URL, Image>();
     58     private HashMap<String, ImageDescriptor> mImageDescMap = new HashMap<String, ImageDescriptor>();
     59 
     60     private IconFactory() {
     61     }
     62 
     63     public static synchronized IconFactory getInstance() {
     64         if (sInstance == null) {
     65             sInstance = new IconFactory();
     66         }
     67         return sInstance;
     68     }
     69 
     70     public void dispose() {
     71         // Dispose icons
     72         for (Image icon : mIconMap.values()) {
     73             // The map can contain null values
     74             if (icon != null) {
     75                 icon.dispose();
     76             }
     77         }
     78         mIconMap.clear();
     79         for (Image icon : mUrlMap.values()) {
     80             // The map can contain null values
     81             if (icon != null) {
     82                 icon.dispose();
     83             }
     84         }
     85         mUrlMap.clear();
     86     }
     87 
     88     /**
     89      * Returns an Image for a given icon name.
     90      * <p/>
     91      * Callers should not dispose it.
     92      *
     93      * @param osName The leaf name, without the extension, of an existing icon in the
     94      *        editor's "icons" directory. If it doesn't exists, a default icon will be
     95      *        generated automatically based on the name.
     96      */
     97     public Image getIcon(String osName) {
     98         return getIcon(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
     99     }
    100 
    101     /**
    102      * Returns an Image for a given icon name.
    103      * <p/>
    104      * Callers should not dispose it.
    105      *
    106      * @param osName The leaf name, without the extension, of an existing icon in the
    107      *        editor's "icons" directory. If it doesn't exists, a default icon will be
    108      *        generated automatically based on the name.
    109      * @param color The color of the text in the automatically generated icons,
    110      *        one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
    111      * @param shape The shape of the icon in the automatically generated icons,
    112      *        one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
    113      */
    114     public Image getIcon(String osName, int color, int shape) {
    115         String key = Character.toString((char) shape) + Integer.toString(color) + osName;
    116         Image icon = mIconMap.get(key);
    117         if (icon == null && !mIconMap.containsKey(key)) {
    118             ImageDescriptor id = getImageDescriptor(osName, color, shape);
    119             if (id != null) {
    120                 icon = id.createImage();
    121             }
    122             // Note that we store null references in the icon map, to avoid looking them
    123             // up every time. If it didn't exist once, it will not exist later.
    124             mIconMap.put(key, icon);
    125         }
    126         return icon;
    127     }
    128 
    129     /**
    130      * Returns an ImageDescriptor for a given icon name.
    131      * <p/>
    132      * Callers should not dispose it.
    133      *
    134      * @param osName The leaf name, without the extension, of an existing icon in the
    135      *        editor's "icons" directory. If it doesn't exists, a default icon will be
    136      *        generated automatically based on the name.
    137      */
    138     public ImageDescriptor getImageDescriptor(String osName) {
    139         return getImageDescriptor(osName, COLOR_DEFAULT, SHAPE_DEFAULT);
    140     }
    141 
    142     /**
    143      * Returns an ImageDescriptor for a given icon name.
    144      * <p/>
    145      * Callers should not dispose it.
    146      *
    147      * @param osName The leaf name, without the extension, of an existing icon in the
    148      *        editor's "icons" directory. If it doesn't exists, a default icon will be
    149      *        generated automatically based on the name.
    150      * @param color The color of the text in the automatically generated icons.
    151      *        one of COLOR_DEFAULT, COLOR_RED, COLOR_BLUE or COLOR_RED.
    152      * @param shape The shape of the icon in the automatically generated icons,
    153      *        one of SHAPE_DEFAULT, SHAPE_CIRCLE or SHAPE_RECT.
    154      */
    155     public ImageDescriptor getImageDescriptor(String osName, int color, int shape) {
    156         String key = Character.toString((char) shape) + Integer.toString(color) + osName;
    157         ImageDescriptor id = mImageDescMap.get(key);
    158         if (id == null && !mImageDescMap.containsKey(key)) {
    159             id = AbstractUIPlugin.imageDescriptorFromPlugin(
    160                     AdtPlugin.PLUGIN_ID,
    161                     String.format("/icons/%1$s.png", osName)); //$NON-NLS-1$
    162 
    163             if (id == null) {
    164                 id = new LetterImageDescriptor(osName.charAt(0), color, shape);
    165             }
    166 
    167             // Note that we store null references in the icon map, to avoid looking them
    168             // up every time. If it didn't exist once, it will not exist later.
    169             mImageDescMap.put(key, id);
    170         }
    171         return id;
    172     }
    173 
    174     /**
    175      * Returns the image indicated by the given URL
    176      *
    177      * @param url the url to the image resources
    178      * @return the image for the url, or null if it cannot be initialized
    179      */
    180     public Image getIcon(URL url) {
    181         Image image = mUrlMap.get(url);
    182         if (image == null) {
    183             ImageDescriptor descriptor = ImageDescriptor.createFromURL(url);
    184             image = descriptor.createImage();
    185             mUrlMap.put(url, image);
    186         }
    187 
    188         return image;
    189     }
    190 
    191     /**
    192      * A simple image description that generates a 16x16 image which consists
    193      * of a colored letter inside a black & white circle.
    194      */
    195     private static class LetterImageDescriptor extends ImageDescriptor {
    196 
    197         private final char mLetter;
    198         private final int mColor;
    199         private final int mShape;
    200 
    201         public LetterImageDescriptor(char letter, int color, int shape) {
    202             mLetter = Character.toUpperCase(letter);
    203             mColor = color;
    204             mShape = shape;
    205         }
    206 
    207         @Override
    208         public ImageData getImageData() {
    209 
    210             final int SX = 15;
    211             final int SY = 15;
    212             final int RX = 4;
    213             final int RY = 4;
    214 
    215             Display display = Display.getCurrent();
    216             if (display == null) {
    217                 return null;
    218             }
    219 
    220             Image image = new Image(display, SX, SY);
    221 
    222             GC gc = new GC(image);
    223             gc.setAdvanced(true);
    224             gc.setAntialias(SWT.ON);
    225             gc.setTextAntialias(SWT.ON);
    226 
    227             // image.setBackground() does not appear to have any effect; we must explicitly
    228             // paint into the image the background color we want masked out later.
    229             // HOWEVER, alpha transparency does not work; we only get to mark a single color
    230             // as transparent. You might think we could pick a system color (to avoid having
    231             // to allocate and dispose the color), or a wildly unique color (to make sure we
    232             // don't accidentally pick up any extra pixels in the image as transparent), but
    233             // this has the very unfortunate side effect of making neighbor pixels in the
    234             // antialiased rendering of the circle pick up shades of that alternate color,
    235             // which looks bad. Therefore we pick a color which is similar to one of our
    236             // existing colors but hopefully different from most pixels. A visual check
    237             // confirms that this seems to work pretty well:
    238             RGB backgroundRgb = new RGB(254, 254, 254);
    239             Color backgroundColor = new Color(display, backgroundRgb);
    240             gc.setBackground(backgroundColor);
    241             gc.fillRectangle(0, 0, SX, SY);
    242 
    243             gc.setBackground(display.getSystemColor(SWT.COLOR_WHITE));
    244             if (mShape == SHAPE_CIRCLE) {
    245                 gc.fillOval(0, 0, SX - 1, SY - 1);
    246             } else if (mShape == SHAPE_RECT) {
    247                 gc.fillRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
    248             }
    249 
    250             gc.setForeground(display.getSystemColor(SWT.COLOR_BLACK));
    251             gc.setLineWidth(1);
    252             if (mShape == SHAPE_CIRCLE) {
    253                 gc.drawOval(0, 0, SX - 1, SY - 1);
    254             } else if (mShape == SHAPE_RECT) {
    255                 gc.drawRoundRectangle(0, 0, SX - 1, SY - 1, RX, RY);
    256             }
    257 
    258             // Get a bold version of the default system font, if possible.
    259             Font font = display.getSystemFont();
    260             FontData[] fds = font.getFontData();
    261             fds[0].setStyle(SWT.BOLD);
    262             // use 3/4th of the circle diameter for the font size (in pixels)
    263             // and convert it to "font points" (font points in SWT are hardcoded in an
    264             // arbitrary 72 dpi and then converted in real pixels using whatever is
    265             // indicated by getDPI -- at least that's how it works under Win32).
    266             fds[0].setHeight((int) ((SY + 1) * 3./4. * 72./display.getDPI().y));
    267             // Note: win32 implementation always uses fds[0] so we change just that one.
    268             // getFontData indicates that the array of fd is really an unusual thing for X11.
    269             font = new Font(display, fds);
    270             gc.setFont(font);
    271             gc.setForeground(display.getSystemColor(mColor));
    272 
    273             // Text measurement varies so slightly depending on the platform
    274             int ofx = 0;
    275             int ofy = 0;
    276             if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_WINDOWS) {
    277                 ofx = +1;
    278                 ofy = -1;
    279             } else if (SdkConstants.CURRENT_PLATFORM == SdkConstants.PLATFORM_DARWIN) {
    280                 // Tweak pixel positioning of some letters that don't look good on the Mac
    281                 if (mLetter != 'T' && mLetter != 'V') {
    282                     ofy = -1;
    283                 }
    284                 if (mLetter == 'I') {
    285                     ofx = -2;
    286                 }
    287             }
    288 
    289             String s = Character.toString(mLetter);
    290             Point p = gc.textExtent(s);
    291             int tx = (SX + ofx - p.x) / 2;
    292             int ty = (SY + ofy - p.y) / 2;
    293             gc.drawText(s, tx, ty, true /* isTransparent */);
    294 
    295             font.dispose();
    296             gc.dispose();
    297 
    298             ImageData data = image.getImageData();
    299             image.dispose();
    300             backgroundColor.dispose();
    301 
    302             // Set transparent pixel in the palette such that on paint (over palette,
    303             // which has a background of SWT.COLOR_WIDGET_BACKGROUND, and over the tree
    304             // which has a white background) we will substitute the background in for
    305             // the backgroundPixel.
    306             int backgroundPixel = data.palette.getPixel(backgroundRgb);
    307             data.transparentPixel = backgroundPixel;
    308 
    309             return data;
    310         }
    311     }
    312 
    313 }
    314