The long story is, I'm trying to implement the Mac version of the
TrayIcon API of the JDIC project :
https://jdic.dev.java.net/
It's basically an API to the System Tray on windows, and on the Mac
it's an API to the Status Bar. Some of the notification actions in
Windows OS(the balloon dialog that pops up over a tray icon) map to
the Dock notification action, but most of the TrayIcon API is
appropriate to the Mac Status Bar. I have a Cocoa test app that does
everything I need, so the native part is working.
The Status Bar seems like an odd place to put the System Tray
interface. In particular, it raises the question of which status bar
-- there could be as many as one on every Finder window. Or are we
talking about different things? See:
One problem I am having is that the API I have to implement only
provides access to an Icon (javax.swing.Icon). That's just an
interface, so it is a pretty opaque object; all you can really do is
get the size of the Icon, and call paintIcon on it.
If I had access to the image URL, I could of course pass that from
Java to the JNI method and create an NSImage (actually an
NSBitmapImageRep), and I can pass that to my NSStatusItem as the
image. Or if I see the image is multi-framed, I can use a Timer to
periodically update that image and thus provide support for an
animated GIF. Or I could also create a custom NSView with an
NSImageViewer, and if my image is multi-framed, it would
automatically animate it for me.
But, I do NOT have access to the image URL. From an Icon, I would
like to at least get an Image, and maybe from that a BufferedImage,
and have some hope of getting access to the image data. But, there
are at least 8 implementations of Icon, and no guarantee what you get
passed will be an ImageIcon. And even if it IS an ImageIcon, the Mac
implementation (on my machine) shows that the actual class is an
apple.awt.OSXImage, of which I have no official documentation. On
Windows, it would either be a BufferedImage or a VolitleImage. From
the BufferedImage I could do something with the ImageIO classes,
however, there is no GIF writer in the JDK, so even if I can figure
out the image is really a GIF, there's no way to write it to a stream
or a ByteBuffer in order to pass the bytes to JNI.
Of course, it may not be an ImageIcon, and it may not have an URL or
other kind of encoded image data. All you can really count on is that
there is code behind the paintIcon method that will render the icon.
I have to use the old-school ImageObserver method, and continually
monitor the state of the Image, and when I see the FRAMEBITS flag, I
have to create an offscreen Graphics in Java, paint that single new
frame, then convert that to something I can pass to a JNI method(like
a PNG), that will then install it as the image on the NSStatusItem.
So this is the only thing I can think of that will work, and be safe,
only using public, documented APIs.
Since you've only got an Icon instance to work with, you can't even
count on using ImageObserver. If I implemented a VectorIcon class
whose paintIcon method only used Graphics.drawLine calls to render a
line-art icon, it would not be usable with such an implementation.
I think that the only safe, pure-java way of doing this is to render
the Icon to an off-screen BufferedImage and then pass the pixel data
from it to the native drawing code. Note that this approach will not
automatically animate an animated icon -- you will only get a single
frame (although I can think of hacks to detect and handle animation,
see below).
Some of the not-so-safe hacks I am also contemplating are :
the ImageIcon constructor takes a String description argument, that I
do not think is used much. If you created the ImageIcon via a URL or
file name, and do not specify the description field, it gets set to
the URL or filename you used to construct the object. So I could get
this value, see if the file/url exists, and pass that to JNI, and I'm
in business.
Or, I could get a BufferedImage version of the ImageIcon by noting
that OSXImage extends sun.awt.image.ToolkitImage, and that class has
a public getBufferedImage() method. I think there may be a way of
getting an IIOImage from a BufferedImage or one of it's subclasses,
and then I could at least access the metadata that tells me how many
frames the image contains. If it's just one, I can just draw it
offscreen, and write it to a PNG (I can write to many different
formats, just not GIF). If it's multiple frames, I'm in the same boat
as above; I either update the image every time my ImageObserver tells
me a new frame is available, or I can extract each frame and pass
them to JNI as an array of PNGs, and then implement my own animation
loops.
Or, finally, I could avoid this by just drawing in Swing/AWT, to a
native NSView, and let Java handle a lot of this image processing for
me. It would require however a means of drawing into an NSView from
Java.
As I'm sure you are aware, all Java drawing is done through a
Graphics or Graphics2D instance. Swing/AWT creates these objects as
part of its normal rendering process. If you want to render to a non-
Swing/AWT graphics context using normal Swing rendering code you will
need a Graphics or Graphics2D instance that will draw to the
appropriate NSView. Writing one yourself would probably be relatively
straightforward JNI, but would be an awful lot of work (especially
getting drawImage to work properly for animated Images, so ImageIcon
will work right, applying AffineTransforms, etc.). This probably
isn't a viable solution for you.
Ultimately, all Swing on-screen rendering boils down to calls to a
Graphics2D instance of the AWT Window ancestor in the Swing
containment hierarchy. If you could somehow create a Window instance
(and perhaps more importantly, its peer) for this NSView, then you
could leverage all the existing Swing rendering infrastructure.
However, the code that does all that is deep in the bowels of Apple's
proprietary JRE implementation, so you can't see how it works.
Getting this hack to work would require assistance from Apple. I do
not know if DTS would be able to help you out on it, if you had an
incident available. You could try asking (see http://
developer.apple.com/faq/techsupport.html, email is email@hidden, but
read the FAQ first).
I think you are stuck rendering your icon to an off-screen image, and
passing the pixels to native code to be drawn to the NSView. To make
animated icons work, you could consider creating a proxy Graphics2D
class. Its constructor takes the Graphics2D instance of a
BufferedImage (or the BufferedImage itself), and implements every
method of the Graphics2D interface and simply calls the same method
on the off-screen rendering context, except for the drawImage
methods. They all need to provide an ImageObserver that will provide
notification of the animation events so that your class can re-render
the pixmap and pass the new frame on to the native code. Don't forget
to relay any events received by your ImageObserver to any
ImageObserver passed in by the caller. You would pass the proxy
Graphics2D instance in to the paintIcon method whenever you determine
that the NSView needs to be repainted (read the docs for
CocoaComponent carefully -- the rules it outlines for mixing AppKit
thread code and Swing code must be followed), and then pass the
generated pixels down to the native code when it is done, and be
aware that there may well be subsequent native repaints required
(e.g. due to ImageObserver notifications to the proxy).
As for passing the pixels down to the native code for rendering in
the NSView, I wouldn't worry about encoding them as PNG or GIF or
anything else -- given how small most "System Tray" icons are, the
memory savings would be negligible (even a 128x128 icon is only 16 K-
pixels). Just pass in raw RGB-32 data and either blit the pixels
directly into the NSView (could get ugly if the pixel formats do not
match), or build an NSImage from them and paint it.
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Java-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/java-dev/email@hidden