| /* |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| package org.apache.batik.bridge; |
| |
| import java.awt.Cursor; |
| import java.awt.Dimension; |
| import java.awt.Image; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.Toolkit; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Point2D; |
| import java.awt.image.BufferedImage; |
| import java.awt.image.ColorModel; |
| import java.awt.image.Raster; |
| import java.awt.image.RenderedImage; |
| import java.awt.image.SampleModel; |
| import java.awt.image.WritableRaster; |
| import java.util.Hashtable; |
| import java.util.Map; |
| |
| import org.apache.batik.css.engine.SVGCSSEngine; |
| import org.apache.batik.css.engine.value.Value; |
| import org.apache.batik.dom.AbstractNode; |
| import org.apache.batik.dom.util.XLinkSupport; |
| import org.apache.batik.ext.awt.image.PadMode; |
| import org.apache.batik.ext.awt.image.renderable.AffineRable8Bit; |
| import org.apache.batik.ext.awt.image.renderable.Filter; |
| import org.apache.batik.ext.awt.image.renderable.PadRable8Bit; |
| import org.apache.batik.ext.awt.image.spi.BrokenLinkProvider; |
| import org.apache.batik.ext.awt.image.spi.ImageTagRegistry; |
| import org.apache.batik.gvt.GraphicsNode; |
| import org.apache.batik.util.ParsedURL; |
| import org.apache.batik.util.Platform; |
| import org.apache.batik.util.SVGConstants; |
| import org.apache.batik.util.SoftReferenceCache; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.css.CSSPrimitiveValue; |
| import org.w3c.dom.css.CSSValue; |
| import org.w3c.dom.svg.SVGDocument; |
| import org.w3c.dom.svg.SVGPreserveAspectRatio; |
| |
| |
| /** |
| * The CursorManager class is a helper class which preloads the cursors |
| * corresponding to the SVG built in cursors. |
| * |
| * @author <a href="mailto:vincent.hardy@sun.com">Vincent Hardy</a> |
| * @version $Id$ |
| */ |
| public class CursorManager implements SVGConstants, ErrorConstants { |
| /** |
| * Maps SVG Cursor Values to Java Cursors |
| */ |
| protected static Map cursorMap; |
| |
| /** |
| * Default cursor when value is not found |
| */ |
| public static final Cursor DEFAULT_CURSOR |
| = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); |
| |
| /** |
| * Cursor used over anchors |
| */ |
| public static final Cursor ANCHOR_CURSOR |
| = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); |
| |
| /** |
| * Cursor used over text |
| */ |
| public static final Cursor TEXT_CURSOR |
| = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); |
| |
| /** |
| * Default preferred cursor size, used for SVG images |
| */ |
| public static final int DEFAULT_PREFERRED_WIDTH = 32; |
| public static final int DEFAULT_PREFERRED_HEIGHT = 32; |
| |
| /** |
| * Static initialization of the cursorMap |
| */ |
| static { |
| Toolkit toolkit = Toolkit.getDefaultToolkit(); |
| cursorMap = new Hashtable(); |
| cursorMap.put(SVG_CROSSHAIR_VALUE, |
| Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); |
| cursorMap.put(SVG_DEFAULT_VALUE, |
| Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); |
| cursorMap.put(SVG_POINTER_VALUE, |
| Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| cursorMap.put(SVG_E_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); |
| cursorMap.put(SVG_NE_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR)); |
| cursorMap.put(SVG_NW_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR)); |
| cursorMap.put(SVG_N_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR)); |
| cursorMap.put(SVG_SE_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR)); |
| cursorMap.put(SVG_SW_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR)); |
| cursorMap.put(SVG_S_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); |
| cursorMap.put(SVG_W_RESIZE_VALUE, |
| Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR)); |
| cursorMap.put(SVG_TEXT_VALUE, |
| Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); |
| cursorMap.put(SVG_WAIT_VALUE, |
| Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); |
| Cursor moveCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); |
| if (Platform.isOSX) { |
| try { |
| Image img = toolkit.createImage |
| (CursorManager.class.getResource("resources/move.gif")); |
| moveCursor = toolkit.createCustomCursor |
| (img, new Point(11, 11), "move"); |
| } catch (Exception ex) { |
| } |
| } |
| cursorMap.put(SVG_MOVE_VALUE, moveCursor); |
| Cursor helpCursor; |
| try { |
| Image img = toolkit.createImage |
| (CursorManager.class.getResource("resources/help.gif")); |
| helpCursor = toolkit.createCustomCursor |
| (img, new Point(1, 3), "help"); |
| } catch (Exception ex) { |
| helpCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); |
| } |
| cursorMap.put(SVG_HELP_VALUE, helpCursor); |
| } |
| |
| /** |
| * BridgeContext associated with this CursorManager |
| */ |
| protected BridgeContext ctx; |
| |
| /** |
| * Cache used to hold references to cursors |
| */ |
| protected CursorCache cursorCache = new CursorCache(); |
| |
| /** |
| * Creates a new CursorManager object. |
| * |
| * @param ctx the BridgeContext associated to this CursorManager |
| */ |
| public CursorManager(BridgeContext ctx) { |
| this.ctx = ctx; |
| } |
| |
| /** |
| * Returns a Cursor object for a given cursor value. This initial |
| * implementation does not handle user-defined cursors, so it |
| * always uses the cursor at the end of the list |
| */ |
| public static Cursor getPredefinedCursor(String cursorName){ |
| return (Cursor)cursorMap.get(cursorName); |
| } |
| |
| /** |
| * Returns the Cursor corresponding to the input element's cursor property |
| * |
| * @param e the element on which the cursor property is set |
| */ |
| public Cursor convertCursor(Element e) { |
| Value cursorValue = CSSUtilities.getComputedStyle |
| (e, SVGCSSEngine.CURSOR_INDEX); |
| |
| String cursorStr = SVGConstants.SVG_AUTO_VALUE; |
| |
| if (cursorValue != null) { |
| if (cursorValue.getCssValueType() == CSSValue.CSS_PRIMITIVE_VALUE |
| && |
| cursorValue.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { |
| // Single Value : should be one of the predefined cursors or |
| // 'inherit' |
| cursorStr = cursorValue.getStringValue(); |
| return convertBuiltInCursor(e, cursorStr); |
| } else if (cursorValue.getCssValueType() == |
| CSSValue.CSS_VALUE_LIST) { |
| int nValues = cursorValue.getLength(); |
| if (nValues == 1) { |
| cursorValue = cursorValue.item(0); |
| if (cursorValue.getPrimitiveType() == |
| CSSPrimitiveValue.CSS_IDENT) { |
| cursorStr = cursorValue.getStringValue(); |
| return convertBuiltInCursor(e, cursorStr); |
| } |
| } else if (nValues > 1) { |
| // |
| // Look for the first cursor url we can handle. |
| // That would be a reference to a <cursor> element. |
| // |
| return convertSVGCursor(e, cursorValue); |
| } |
| } |
| } |
| |
| return convertBuiltInCursor(e, cursorStr); |
| } |
| |
| public Cursor convertBuiltInCursor(Element e, String cursorStr) { |
| Cursor cursor = null; |
| |
| // The CSS engine guarantees an non null, non empty string |
| // as the computed value for cursor. Therefore, the following |
| // test is safe. |
| if (cursorStr.charAt(0) == 'a') { |
| // |
| // Handle 'auto' value. |
| // |
| // - <a> The following sets the cursor for <a> element |
| // enclosing text nodes. Setting the proper cursor (i.e., |
| // depending on the children's 'cursor' property, is |
| // handled in the SVGAElementBridge so as to avoid going |
| // up the tree on mouseover events (looking for an anchor |
| // ancestor. |
| // |
| // - <image> The following does not change the cursor if |
| // the element's cursor property is set to |
| // 'auto'. Otherwise, it takes precedence over any child |
| // (in case of SVG content) cursor setting. This means |
| // that for images referencing SVG content, a cursor |
| // property set to 'auto' on the <image> element will not |
| // override the cursor settings inside the SVG image. Any |
| // other cursor property will take precedence. |
| // |
| // - <use> Same behavior as for <image> except that the |
| // behavior is controlled from the <use> element bridge |
| // (SVGUseElementBridge). |
| // |
| // - <text>, <tref> and <tspan> : a cursor value of auto |
| // will cause the cursor to be set to a text cursor. Note |
| // that text content with an 'auto' cursor and descendant |
| // of an anchor will have its cursor set to the anchor |
| // cursor through the SVGAElementBridge. |
| // |
| String nameSpaceURI = e.getNamespaceURI(); |
| if (SVGConstants.SVG_NAMESPACE_URI.equals(nameSpaceURI)) { |
| String tag = e.getLocalName(); |
| if (SVGConstants.SVG_A_TAG.equals(tag)) { |
| cursor = CursorManager.ANCHOR_CURSOR; |
| } else if (SVGConstants.SVG_TEXT_TAG.equals(tag) || |
| SVGConstants.SVG_TSPAN_TAG.equals(tag) || |
| SVGConstants.SVG_TREF_TAG.equals(tag) ) { |
| cursor = CursorManager.TEXT_CURSOR; |
| } else if (SVGConstants.SVG_IMAGE_TAG.equals(tag)) { |
| // Do not change the cursor |
| return null; |
| } else { |
| cursor = CursorManager.DEFAULT_CURSOR; |
| } |
| } else { |
| cursor = CursorManager.DEFAULT_CURSOR; |
| } |
| } else { |
| // Specific, logical cursor |
| cursor = CursorManager.getPredefinedCursor(cursorStr); |
| } |
| |
| return cursor; |
| } |
| |
| |
| /** |
| * Returns a cursor for the given value list. Note that the |
| * code assumes that the input value has at least two entries. |
| * So the caller should check that before calling the method. |
| * For example, CSSUtilities.convertCursor performs that check. |
| */ |
| public Cursor convertSVGCursor(Element e, Value l) { |
| int nValues = l.getLength(); |
| Element cursorElement = null; |
| for (int i=0; i<nValues-1; i++) { |
| Value cursorValue = l.item(i); |
| if (cursorValue.getPrimitiveType() == CSSPrimitiveValue.CSS_URI) { |
| String uri = cursorValue.getStringValue(); |
| |
| // If the uri does not resolve to a cursor element, |
| // then, this is not a type of cursor uri we can handle: |
| // go to the next or default to logical cursor |
| try { |
| cursorElement = ctx.getReferencedElement(e, uri); |
| } catch (BridgeException be) { |
| // Be only silent if this is a case where the target |
| // could not be found. Do not catch other errors (e.g, |
| // malformed URIs) |
| if (!ERR_URI_BAD_TARGET.equals(be.getCode())) { |
| throw be; |
| } |
| } |
| |
| if (cursorElement != null) { |
| // We go an element, check it is of type cursor |
| String cursorNS = cursorElement.getNamespaceURI(); |
| if (SVGConstants.SVG_NAMESPACE_URI.equals(cursorNS) && |
| SVGConstants.SVG_CURSOR_TAG.equals |
| (cursorElement.getLocalName())) { |
| Cursor c = convertSVGCursorElement(cursorElement); |
| if (c != null) { |
| return c; |
| } |
| } |
| } |
| } |
| } |
| |
| // If we got to that point, it means that no cursorElement |
| // produced a valid cursor, i.e., either a format we support |
| // or a valid referenced image (no broken image). |
| // Fallback on the built in cursor property. |
| Value cursorValue = l.item(nValues-1); |
| String cursorStr = SVGConstants.SVG_AUTO_VALUE; |
| if (cursorValue.getPrimitiveType() == CSSPrimitiveValue.CSS_IDENT) { |
| cursorStr = cursorValue.getStringValue(); |
| } |
| |
| return convertBuiltInCursor(e, cursorStr); |
| } |
| |
| /** |
| * Returns a cursor for a given element |
| */ |
| public Cursor convertSVGCursorElement(Element cursorElement) { |
| // One of the cursor url resolved to a <cursor> element |
| // Try to handle its image. |
| String uriStr = XLinkSupport.getXLinkHref(cursorElement); |
| if (uriStr.length() == 0) { |
| throw new BridgeException(ctx, cursorElement, ERR_ATTRIBUTE_MISSING, |
| new Object[] {"xlink:href"}); |
| } |
| |
| String baseURI = AbstractNode.getBaseURI(cursorElement); |
| ParsedURL purl; |
| if (baseURI == null) { |
| purl = new ParsedURL(uriStr); |
| } else { |
| purl = new ParsedURL(baseURI, uriStr); |
| } |
| |
| // |
| // Convert the cursor's hot spot |
| // |
| UnitProcessor.Context uctx |
| = UnitProcessor.createContext(ctx, cursorElement); |
| |
| String s = cursorElement.getAttributeNS(null, SVG_X_ATTRIBUTE); |
| float x = 0; |
| if (s.length() != 0) { |
| x = UnitProcessor.svgHorizontalCoordinateToUserSpace |
| (s, SVG_X_ATTRIBUTE, uctx); |
| } |
| |
| s = cursorElement.getAttributeNS(null, SVG_Y_ATTRIBUTE); |
| float y = 0; |
| if (s.length() != 0) { |
| y = UnitProcessor.svgVerticalCoordinateToUserSpace |
| (s, SVG_Y_ATTRIBUTE, uctx); |
| } |
| |
| CursorDescriptor desc = new CursorDescriptor(purl, x, y); |
| |
| // |
| // Check if there is a cursor in the cache for this url |
| // |
| Cursor cachedCursor = cursorCache.getCursor(desc); |
| |
| if (cachedCursor != null) { |
| return cachedCursor; |
| } |
| |
| // |
| // Load image into Filter f and transform hotSpot to |
| // cursor space. |
| // |
| Point2D.Float hotSpot = new Point2D.Float(x, y); |
| Filter f = cursorHrefToFilter(cursorElement, |
| purl, |
| hotSpot); |
| if (f == null) { |
| cursorCache.clearCursor(desc); |
| return null; |
| } |
| |
| // The returned Filter is guaranteed to create a |
| // default rendering of the desired size |
| Rectangle cursorSize = f.getBounds2D().getBounds(); |
| RenderedImage ri = f.createScaledRendering(cursorSize.width, |
| cursorSize.height, |
| null); |
| Image img = null; |
| |
| if (ri instanceof Image) { |
| img = (Image)ri; |
| } else { |
| img = renderedImageToImage(ri); |
| } |
| |
| // Make sure the not spot does not fall out of the cursor area. If it |
| // does, then clamp the coordinates to the image space. |
| hotSpot.x = hotSpot.x < 0 ? 0 : hotSpot.x; |
| hotSpot.y = hotSpot.y < 0 ? 0 : hotSpot.y; |
| hotSpot.x = hotSpot.x > (cursorSize.width-1) ? cursorSize.width - 1 : hotSpot.x; |
| hotSpot.y = hotSpot.y > (cursorSize.height-1) ? cursorSize.height - 1: hotSpot.y; |
| |
| // |
| // The cursor image is now into 'img' |
| // |
| Cursor c = Toolkit.getDefaultToolkit() |
| .createCustomCursor(img, |
| new Point(Math.round(hotSpot.x), |
| Math.round(hotSpot.y)), |
| purl.toString()); |
| |
| cursorCache.putCursor(desc, c); |
| return c; |
| } |
| |
| /** |
| * Converts the input ParsedURL into a Filter and transforms the |
| * input hotSpot point (in image space) to cursor space |
| */ |
| protected Filter cursorHrefToFilter(Element cursorElement, |
| ParsedURL purl, |
| Point2D hotSpot) { |
| |
| AffineRable8Bit f = null; |
| String uriStr = purl.toString(); |
| Dimension cursorSize = null; |
| |
| // Try to load as an SVG Document |
| DocumentLoader loader = ctx.getDocumentLoader(); |
| SVGDocument svgDoc = (SVGDocument)cursorElement.getOwnerDocument(); |
| URIResolver resolver = ctx.createURIResolver(svgDoc, loader); |
| try { |
| Element rootElement = null; |
| Node n = resolver.getNode(uriStr, cursorElement); |
| if (n.getNodeType() == Node.DOCUMENT_NODE) { |
| SVGDocument doc = (SVGDocument)n; |
| // FIXX: really should be subCtx here. |
| ctx.initializeDocument(doc); |
| rootElement = doc.getRootElement(); |
| } else { |
| throw new BridgeException |
| (ctx, cursorElement, ERR_URI_IMAGE_INVALID, |
| new Object[] {uriStr}); |
| } |
| GraphicsNode node = ctx.getGVTBuilder().build(ctx, rootElement); |
| |
| // |
| // The cursorSize define the viewport into which the |
| // cursor is displayed. That viewport is platform |
| // dependant and is not defined by the SVG content. |
| // |
| float width = DEFAULT_PREFERRED_WIDTH; |
| float height = DEFAULT_PREFERRED_HEIGHT; |
| UnitProcessor.Context uctx |
| = UnitProcessor.createContext(ctx, rootElement); |
| |
| String s = rootElement.getAttribute(SVG_WIDTH_ATTRIBUTE); |
| if (s.length() != 0) { |
| width = UnitProcessor.svgHorizontalLengthToUserSpace |
| (s, SVG_WIDTH_ATTRIBUTE, uctx); |
| } |
| |
| s = rootElement.getAttribute(SVG_HEIGHT_ATTRIBUTE); |
| if (s.length() != 0) { |
| height = UnitProcessor.svgVerticalLengthToUserSpace |
| (s, SVG_HEIGHT_ATTRIBUTE, uctx); |
| } |
| |
| cursorSize |
| = Toolkit.getDefaultToolkit().getBestCursorSize |
| (Math.round(width), Math.round(height)); |
| |
| // Handle the viewBox transform |
| AffineTransform at = ViewBox.getPreserveAspectRatioTransform |
| (rootElement, cursorSize.width, cursorSize.height, ctx); |
| Filter filter = node.getGraphicsNodeRable(true); |
| f = new AffineRable8Bit(filter, at); |
| } catch (BridgeException ex) { |
| throw ex; |
| } catch (SecurityException ex) { |
| throw new BridgeException(ctx, cursorElement, ex, ERR_URI_UNSECURE, |
| new Object[] {uriStr}); |
| } catch (Exception ex) { |
| /* Nothing to do */ |
| } |
| |
| |
| // If f is null, it means that we are not dealing with |
| // an SVG image. Try as a raster image. |
| if (f == null) { |
| ImageTagRegistry reg = ImageTagRegistry.getRegistry(); |
| Filter filter = reg.readURL(purl); |
| if (filter == null) { |
| return null; |
| } |
| |
| // Check if we got a broken image |
| if (BrokenLinkProvider.hasBrokenLinkProperty(filter)) { |
| return null; |
| } |
| |
| Rectangle preferredSize = filter.getBounds2D().getBounds(); |
| cursorSize = Toolkit.getDefaultToolkit().getBestCursorSize |
| (preferredSize.width, preferredSize.height); |
| |
| if (preferredSize != null && preferredSize.width >0 |
| && preferredSize.height > 0 ) { |
| AffineTransform at = new AffineTransform(); |
| if (preferredSize.width > cursorSize.width |
| || |
| preferredSize.height > cursorSize.height) { |
| at = ViewBox.getPreserveAspectRatioTransform |
| (new float[] {0, 0, preferredSize.width, preferredSize.height}, |
| SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_XMINYMIN, |
| true, |
| cursorSize.width, |
| cursorSize.height); |
| } |
| f = new AffineRable8Bit(filter, at); |
| } else { |
| // Invalid Size |
| return null; |
| } |
| } |
| |
| |
| // |
| // Transform the hot spot from image space to cursor space |
| // |
| AffineTransform at = f.getAffine(); |
| at.transform(hotSpot, hotSpot); |
| |
| // |
| // In all cases, clip to the cursor boundaries |
| // |
| Rectangle cursorViewport |
| = new Rectangle(0, 0, cursorSize.width, cursorSize.height); |
| |
| PadRable8Bit cursorImage |
| = new PadRable8Bit(f, cursorViewport, |
| PadMode.ZERO_PAD); |
| |
| return cursorImage; |
| |
| } |
| |
| |
| /** |
| * Implementation helper: converts a RenderedImage to an Image |
| */ |
| protected Image renderedImageToImage(RenderedImage ri) { |
| int x = ri.getMinX(); |
| int y = ri.getMinY(); |
| SampleModel sm = ri.getSampleModel(); |
| ColorModel cm = ri.getColorModel(); |
| WritableRaster wr = Raster.createWritableRaster(sm, new Point(x,y)); |
| ri.copyData(wr); |
| |
| return new BufferedImage(cm, wr, cm.isAlphaPremultiplied(), null); |
| } |
| |
| /** |
| * Simple inner class which holds the information describing |
| * a cursor, i.e., the image it points to and the hot spot point |
| * coordinates. |
| */ |
| static class CursorDescriptor { |
| ParsedURL purl; |
| float x; |
| float y; |
| String desc; |
| |
| public CursorDescriptor(ParsedURL purl, |
| float x, float y) { |
| if (purl == null) { |
| throw new IllegalArgumentException(); |
| } |
| |
| this.purl = purl; |
| this.x = x; |
| this.y = y; |
| |
| // Desc is used for hascode as well as for toString() |
| this.desc = this.getClass().getName() + |
| "\n\t:[" + this.purl + "]\n\t:[" + x + "]:[" + y + "]"; |
| } |
| |
| public boolean equals(Object obj) { |
| if (obj == null |
| || |
| !(obj instanceof CursorDescriptor)) { |
| return false; |
| } |
| |
| CursorDescriptor desc = (CursorDescriptor)obj; |
| boolean isEqual = |
| this.purl.equals(desc.purl) |
| && |
| this.x == desc.x |
| && |
| this.y == desc.y; |
| |
| return isEqual; |
| } |
| |
| public String toString() { |
| return this.desc; |
| } |
| |
| public int hashCode() { |
| return desc.hashCode(); |
| } |
| } |
| |
| /** |
| * Simple extension of the SoftReferenceCache that |
| * offers typed interface (Kind of needed as SoftReferenceCache |
| * mostly has protected methods). |
| */ |
| static class CursorCache extends SoftReferenceCache { |
| public CursorCache() { |
| } |
| |
| public Cursor getCursor(CursorDescriptor desc) { |
| return (Cursor)requestImpl(desc); |
| } |
| |
| public void putCursor(CursorDescriptor desc, |
| Cursor cursor) { |
| putImpl(desc, cursor); |
| } |
| |
| public void clearCursor(CursorDescriptor desc) { |
| clearImpl(desc); |
| } |
| } |
| } |