Revision: 4040
          http://vexi.svn.sourceforge.net/vexi/?rev=4040&view=rev
Author:   clrg
Date:     2011-02-22 01:01:50 +0000 (Tue, 22 Feb 2011)

Log Message:
-----------
Add basic Swing platform implementation
- set it as the default implementation for Java 1.4+
- perhaps 'Java4' would be more suitable; revert Java 1.4 code from AWT?
- currently it is a heavily modified version of AWT.java
- currently triple(!) buffered as single-buffered JFrame did not work well
- uses an ImageIcon in a JPanel because painting direct to JFrame did not work 
well
- TODO: test on older JREs; should it be Java5+ or Java6+?
- TODO: window implementation
- TODO: investigate using JFrame double buffer i.e. drop backbuffer

Modified Paths:
--------------
    trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Platform.java

Added Paths:
-----------
    trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Swing.java

Modified: trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Platform.java
===================================================================
--- trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Platform.java  
2011-02-22 00:46:39 UTC (rev 4039)
+++ trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Platform.java  
2011-02-22 01:01:50 UTC (rev 4040)
@@ -85,8 +85,12 @@
                 } else {
                     platform_class = "X11";
                 }
-            } else if (!version.startsWith("1.0") && 
!version.startsWith("1.1")) {
+            } else if (version.startsWith("1.0") || version.startsWith("1.1")) 
{
+                platform_class = "AWT";
+            } else if (version.startsWith("1.2") || version.startsWith("1.3")) 
{
                 platform_class = "Java2";
+            } else if (version.startsWith("1")) {
+                platform_class = "Swing";
             }
 
             if (platform_class == null) {

Added: trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Swing.java
===================================================================
--- trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Swing.java             
                (rev 0)
+++ trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Swing.java     
2011-02-22 01:01:50 UTC (rev 4040)
@@ -0,0 +1,1067 @@
+// Copyright 2000-2008 the Contributors, as shown in the revision logs.
+// Licensed under the GNU General Public License version 2 ("the License").
+// You may not use this file except in compliance with the License.
+
+package org.vexi.plat;
+
+import java.awt.AlphaComposite;
+import java.awt.BorderLayout;
+import java.awt.Button;
+import java.awt.Canvas;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.FileDialog;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.Insets;
+import java.awt.MediaTracker;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.TextArea;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.awt.im.InputMethodRequests;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.DirectColorModel;
+import java.awt.image.ImageObserver;
+import java.awt.image.MemoryImageSource;
+import java.awt.image.PixelGrabber;
+import java.io.File;
+import java.io.InputStream;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+
+import org.ibex.js.JS;
+import org.ibex.util.Callable;
+import org.ibex.util.IOUtil;
+import org.ibex.util.Semaphore;
+import org.vexi.core.Box;
+import org.vexi.core.Main;
+import org.vexi.core.Surface;
+import org.vexi.graphics.Font;
+import org.vexi.graphics.Picture;
+import org.vexi.graphics.PixelBuffer;
+import org.vexi.util.Log;
+
+/** Platform subclass for all VM's providing AWT+Swing 1.4 functionality */
+public class Swing extends JVM {
+
+    protected String getDescriptiveName() {
+        return "Generic JDK 1.4+ with AWT+Swing";
+    }
+
+    protected void postInit() {
+        Log.system.diag(Platform.class, "               color depth = " +
+                Toolkit.getDefaultToolkit().getColorModel().getPixelSize() + 
"bpp");
+    }
+    
+    public Swing() {
+        // disable the focus manager so we can intercept the tab key
+        javax.swing.FocusManager.setCurrentManager(new 
javax.swing.FocusManager() {
+            public void processKeyEvent(Component focusedComponent, KeyEvent 
anEvent) { }
+            public void focusPreviousComponent(Component aComponent) { }
+            public void focusNextComponent(Component aComponent) { }
+        });
+    }
+    
+    
+    //////// Platform Implementation //////////////////////////////////
+    
+    protected PixelBuffer _createPixelBuffer(int w, int h, Surface owner) {
+        AWTPixelBuffer buf = new AWTPixelBuffer(w, h);
+        MediaTracker mediatracker = new MediaTracker(new Canvas());
+        mediatracker.addImage(buf.i, 1);
+        try {
+            mediatracker.waitForAll();
+        } catch (InterruptedException e) { }
+        mediatracker.removeImage(buf.i);
+        return buf;
+    }
+    protected Picture _createPicture(JS r) { return new AWTPicture(r); }
+    protected Surface _createSurface(Box b, boolean framed) { return new 
AWTSurface(b, framed); }
+
+    protected Rectangle _getScreenBounds() { return 
getPrimaryDisplayConfiguration().getBounds(); }
+    private GraphicsConfiguration getPrimaryDisplayConfiguration() {
+        return 
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
+    }
+
+    protected void _criticalAbort(String message) {
+        Log.system.error(this, message);
+        final Dialog d = new Dialog(new Frame(), "Vexi Cannot Continue");
+        d.setLayout(new BorderLayout());
+        TextArea ta = new TextArea("Vexi cannot continue because:\n\n" + 
message, 10, 80);
+        ta.setEditable(false);
+        d.add(ta, "Center");
+        Button b = new Button("OK");
+        b.addActionListener(new ActionListener() {
+                public void actionPerformed(ActionEvent e) {
+                    d.dispose();
+                }
+            });
+        d.add(b, "South");
+        d.setModal(true);
+        d.pack();
+        d.setVisible(true);
+        new Semaphore().block();
+    }
+
+    protected String _getClipBoard() {
+        Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
+        if (cb == null) {
+            return null;
+        }
+        Transferable clipdata = cb.getContents(null);
+        try {
+            return (String)clipdata.getTransferData(DataFlavor.stringFlavor);
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    protected void _setClipBoard(String s) {
+        Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+        if (clipboard == null) {
+            return;
+        }
+        StringSelection clipString = new StringSelection(s);
+        clipboard.setContents(clipString, clipString);
+    }
+
+    /** some platforms (cough, cough, NetscapeVM) have totally broken modifier 
masks; they will need to override this */
+    protected static int modifiersToButtonNumber(int modifiers) {
+        if ((modifiers & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) {
+            return 1;
+        }
+        if ((modifiers & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK) {
+            // ugh, MacOSX reports the right mouse button as BUTTON2_MASK...
+            if (System.getProperty("os.name", "").startsWith("Mac OS X")) {
+                return 2;
+            }
+            return 3;
+        }
+        if ((modifiers & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK) {
+            // ugh, MacOSX reports the right mouse button as BUTTON2_MASK...
+            if (System.getProperty("os.name", "").startsWith("Mac OS X")) {
+                return 3;
+            }
+            return 2;
+        }
+        return 0;
+    }
+
+    static class FileDialogHelper extends FileDialog implements 
WindowListener, ComponentListener {
+        Semaphore s;
+        public FileDialogHelper(String suggestedFileName, Semaphore s, boolean 
write) {
+            super(new Frame(), write ? "Save" : "Open", write ? 
FileDialog.SAVE : FileDialog.LOAD);
+            this.s = s;
+            addWindowListener(this);
+            addComponentListener(this);
+            if (suggestedFileName.indexOf(File.separatorChar) == -1) {
+                setFile(suggestedFileName);
+            } else {
+                setDirectory(suggestedFileName.substring(0, 
suggestedFileName.lastIndexOf(File.separatorChar)));
+                
setFile(suggestedFileName.substring(suggestedFileName.lastIndexOf(File.separatorChar)
 + 1));
+            }
+            setVisible(true);
+        }
+        public void windowActivated(WindowEvent e) { }
+        public void windowClosed(WindowEvent e) { s.release(); }
+        public void windowClosing(WindowEvent e) { }
+        public void windowDeactivated(WindowEvent e) { }
+        public void windowDeiconified(WindowEvent e) { }
+        public void windowIconified(WindowEvent e) { }
+        public void windowOpened(WindowEvent e) { }
+        public void componentHidden(ComponentEvent e) { s.release(); }
+        public void componentMoved(ComponentEvent e) { }
+        public void componentResized(ComponentEvent e) { }
+        public void componentShown(ComponentEvent e) { }
+    };
+
+    protected String _fileDialog(String suggestedFileName, boolean write) {
+        final Semaphore s = new Semaphore();
+        FileDialogHelper fd = new FileDialogHelper(suggestedFileName, s, 
write);
+        s.block();
+        return fd.getDirectory() == null ? null : (fd.getDirectory() + 
File.separatorChar + fd.getFile());
+    }
+
+
+    // Inner Classes 
/////////////////////////////////////////////////////////////////////////////////////
+
+    protected Font.Glyph _createGlyph(Font f, char c) { return new AWTGlyph(f, 
c); }
+    protected static class AWTGlyph extends Font.Glyph {
+        private Image i = null;
+        private static ColorModel cmodel = new DirectColorModel(32, 
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
+        private BufferedImage i2 = null;
+        int lastcolor = 0;
+        BufferedImage getBufferedImage() {
+            if (i2 == null) {
+                i2 = new BufferedImage(width, height, 
BufferedImage.TYPE_INT_ARGB);
+            }
+            return i2;
+        }
+
+        // this doesn't work on Win32 because the JVM is broken
+        /*
+        static final ColorModel cmodel = new ColorModel(8) {
+            public int getRed(int p) { return 0; }
+            public int getGreen(int p) { return 0; }
+            public int getBlue(int p) { return 0; }
+            public int getAlpha(int p) { return p & 0xFF; }
+        };
+        */
+
+        public AWTGlyph(Font f, char c) { super(f, c); }
+        Image getImage() {
+            if (i == null && isLoaded) {
+                int[] data2 = new int[data.length];
+                for (int i=0; i<data2.length; i++) {
+                    data2[i] = ((data[i]) & 0xff) << 24;
+                }
+
+                MemoryImageSource mis = new MemoryImageSource(width, height, 
cmodel, data2, 0, width);
+                mis.setAnimated(true);
+                i = Toolkit.getDefaultToolkit().createImage(mis);
+                MediaTracker mediatracker = new MediaTracker(new Canvas());
+                mediatracker.addImage(i, 1);
+                try {
+                    mediatracker.waitForAll();
+                } catch (InterruptedException e) { }
+                mediatracker.removeImage(i);
+                synchronized(AWTPixelBuffer.class) { 
+                    if (AWTPixelBuffer.component == null) {
+                        AWTPixelBuffer.component = new Frame();
+                        AWTPixelBuffer.component.setVisible(false);
+                        AWTPixelBuffer.component.addNotify();
+                    }
+                }
+                data = null;
+            }
+            return i;
+        }
+    }
+
+    protected static class AWTPicture extends Picture {
+        public Image i = null;
+        private static ColorModel cmodel = new DirectColorModel(32, 
0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
+        
+        public AWTPicture(Image i) {
+            this.i = i;
+            height = i.getHeight(null);
+            width = i.getWidth(null);
+            isLoaded = true;
+            initialized = true;
+        }
+        
+        boolean initialized = false;
+        public AWTPicture(JS r) { super(r); }
+        public void init() {
+            if (initialized) {
+                return;
+            }
+            initialized = true;
+            MemoryImageSource mis = new MemoryImageSource(width, height, 
cmodel, data, 0, width);
+            // FIXME: support animated images
+            // REMARK: default to false otherwise the resultant Image will
+            // cause Java to hang (!) if the image is used as a frame icon
+            mis.setAnimated(false);
+            i = Toolkit.getDefaultToolkit().createImage(mis);
+            MediaTracker mediatracker = new MediaTracker(new Canvas());
+            mediatracker.addImage(i, 1);
+            try {
+                mediatracker.waitForAll();
+            } catch (InterruptedException e) { }
+            mediatracker.removeImage(i);
+            // what the f... does this do?
+            synchronized(AWTPixelBuffer.class) {
+                if (AWTPixelBuffer.component == null) {
+                    AWTPixelBuffer.component = new Frame();
+                    AWTPixelBuffer.component.setVisible(false);
+                    AWTPixelBuffer.component.addNotify();
+                }
+            }
+        }
+    }
+    
+    protected static class AWTPixelBuffer implements PixelBuffer {
+        
+        protected Image i = null;
+        protected Graphics g = null;
+        protected AWTSurface surface = null;
+        
+        /** JDK1.1 platforms require that a component be associated with each 
off-screen buffer */
+        static Component component = null;
+
+        public AWTPixelBuffer() { }
+        public AWTPixelBuffer(Image i) {
+            this.i = i;
+            g = i.getGraphics();
+        }
+        public AWTPixelBuffer(AWTSurface s) {
+            this(Platform.getScreenBounds().width, 
Platform.getScreenBounds().height);
+//            this.surface = s;
+        }
+//        public AWTPixelBuffer(int w, int h, AWTSurface s) {
+//            this(w, h);
+//            this.surface = s;
+//        }
+        public AWTPixelBuffer(int w, int h) {
+            synchronized(AWTPixelBuffer.class) {
+                if (component == null) {
+                    component = new Frame();
+                    component.setVisible(false);
+                    component.addNotify();
+                }
+            }
+            i = component.createImage(w, h);
+            MediaTracker mediatracker = new MediaTracker(new Canvas());
+            mediatracker.addImage(i, 1);
+            try {
+                mediatracker.waitForAll();
+            } catch (InterruptedException e) { }
+            mediatracker.removeImage(i);
+            // assigned and disposed of as-required by render
+            //g = i.getGraphics();
+        }
+        
+        public int getHeight() { return i == null ? 0 : i.getHeight(null); }
+        public int getWidth() { return i == null ? 0 : i.getWidth(null); }
+        public Picture toPicture() { return new AWTPicture(i); }
+        
+        static interface DrawItem {
+            public abstract void draw();
+        }
+
+        /** draw an unscaled image */
+        public void drawPicture(Picture source, int dx, int dy, int cx1, int 
cy1, int cx2, int cy2) {
+            ((AWTPicture)source).init();
+            g.setClip(cx1, cy1, cx2 - cx1, cy2 - cy1);
+            g.drawImage(((AWTPicture)source).i, dx, dy, null);
+            g.setClip(0, 0, i.getWidth(null), i.getHeight(null));
+        }
+
+        /** draw an scaled image */
+        public void drawPicture(Picture source, int dx1, int dy1, int dx2, int 
dy2, int sx1, int sy1, int sx2, int sy2) {
+            ((AWTPicture)source).init();
+            g.drawImage(((AWTPicture)source).i, dx1, dy1, dx2, dy2, sx1, sy1, 
sx2, sy2, null);
+        }
+
+        /** implemented with java.awt 1.1's setXORMode() */
+        public void drawGlyph(Font.Glyph glyph, int dx, int dy, int cx1, int 
cy1, int cx2, int cy2, int argb) {
+            if (glyph.width == 0 || glyph.height == 0) {
+                return;
+            }
+            AWTGlyph awtglyph = ((AWTGlyph)glyph);
+            int w = cx2 - cx1;
+            int h = cy2 - cy1;
+            BufferedImage bufi = awtglyph.getBufferedImage();
+            if (awtglyph.lastcolor != argb) {
+                awtglyph.lastcolor = argb;
+                Graphics2D src = (Graphics2D)bufi.getGraphics();
+                src.setClip(0, 0, awtglyph.width, glyph.height);
+                src.setComposite(AlphaComposite.Src);
+                src.setColor(new Color((argb & 0x00ff0000) >> 16, (argb & 
0x0000ff00) >> 8, argb & 0x000000ff, (argb >> 24) & 0x000000ff));
+                src.drawImage(awtglyph.getImage(), 0, 0, null);
+                src.setComposite(AlphaComposite.SrcIn);
+                src.fillRect(0, 0, glyph.width, glyph.height);
+            }
+            Graphics2D dst = (Graphics2D)g;
+            dst.setClip(cx1, cy1, w, h);
+            dst.setComposite(AlphaComposite.SrcOver);
+            dst.drawImage(bufi, dx, dy, null);
+            dst.setClip(0, 0, i.getWidth(null), i.getHeight(null));
+        }
+
+        // FIXME: try to use OS acceleration
+        public void fillTrapezoid(int x1, int x2, int y1, int x3, int x4, int 
y2, int argb) {
+            g.setColor(new java.awt.Color((argb & 0x00FF0000) >> 16, (argb & 
0x0000FF00) >> 8, (argb & 0x000000FF), (argb & 0xFF000000) >>> 24));
+            if (x1 == x3 && x2 == x4) {
+                g.fillRect(x1, y1, x4 - x1, y2 - y1);
+            } else for(int y=y1; y<y2; y++) {
+                int _x1 = (int)Math.floor((y - y1) * (x3 - x1) / (y2 - y1) + 
x1);
+                int _y1 = (int)Math.floor(y);
+                int _x2 = (int)Math.ceil((y - y1) * (x4 - x2) / (y2 - y1) + 
x2);
+                int _y2 = (int)Math.floor(y) + 1;
+                if (_x1 > _x2) {
+                    // swap _x1/_x2
+                    int tmp = _x1;
+                    _x1 = _x2;
+                    _x2 = tmp;
+                }
+                g.fillRect(_x1, _y1, _x2 - _x1, _y2 - _y1);
+            }
+        }
+        public void drawLine(int x1, int y1, int x2, int y2, int color) {
+            // TODO Auto-generated method stub
+            throw new Error("FIXME: not yet implemented");
+        }
+        public void fillTriangle(int x1, int y1, int x2, int y2, int x3, int 
y3, int color) {
+            // TODO Auto-generated method stub
+            throw new Error("FIXME: not yet implemented");
+        }
+    }
+    
+    
+    protected static class AWTSurface extends 
Surface.DoubleBufferedSurfaceNoBlit
+        implements MouseListener, MouseMotionListener, MouseWheelListener, 
KeyListener, ComponentListener, WindowListener {
+        
+        public Picture toPicture() { return new 
AWTPicture(((AWTPixelBuffer)backbuffer).i); }
+        private Object buffersync = new Object();
+
+        /** synchronized as otherwise it is possible to blit before images 
have been rendered to the backbuffer */
+        public void blit(PixelBuffer s, int sx, int sy, int dx, int dy, int 
dx2, int dy2) {
+            throw new Error("Swing implementation does not support direct 
blitting");
+        }
+        
+        /** if (component instanceof Frame) then frame == window else frame == 
null */
+        Window window = null;
+        JFrame frame = null;
+        JLabel panel = null;
+        
+        /** our component's insets */
+        //protected Insets insets = new Insets(0, 0, 0, 0);
+        
+        /** some JDKs let us recycle a single Dimension object when calling 
getSize() */
+        Dimension singleSize = new Dimension();
+        
+        public void toBack() { if (window != null) window.toBack(); }
+        public void toFront() {
+            // window.toFront() does not work well
+            //if (window != null) window.toFront();
+            if (window != null) { window.setVisible(false); 
window.setVisible(true); }
+        }
+        public void requestFocus() { if (window != null) 
window.requestFocus(); }
+        public void setLocation() { if (window != null) window.setLocation(x, 
y); }
+        public void setTitleBarText(String s) { if (frame != null) 
frame.setTitle(s); }
+        public void setInvisible(boolean b) { window.setVisible(!b); }
+        public void setIcon(final Picture i) { 
+            if (frame == null) {
+                return;
+            }
+            if (i==null) {
+                frame.setIconImage(new ImageIcon(Surface.vexiIcon).getImage());
+            } else {
+                if (i.isLoaded) {
+                    // must init() otherwise ((AWTPicture)i).i is null
+                    ((AWTPicture)i).init();
+                    frame.setIconImage(((AWTPicture)i).i);
+                } else {
+                    Main.SCHEDULER.add(new Callable() {
+                        public Object run(Object o) throws Exception {
+                            if (i.isLoaded) {
+                                // must init() otherwise ((AWTPicture)i).i is 
null
+                                ((AWTPicture)i).init();
+                                frame.setIconImage(((AWTPicture)i).i);
+                            } else {
+                                Main.SCHEDULER.add(this);
+                            }
+                            return null;
+                        }
+                    });
+                }
+            }
+        }
+        public void _setSize(int width, int height) {
+            discoverInsets();
+            window.setSize(width + (leftInset + rightInset), height + 
(topInset + bottomInset));
+        }
+        protected void _setMinimized(boolean b) {
+            if (frame!=null) {
+                frame.setExtendedState(b ? Frame.ICONIFIED : Frame.NORMAL);
+            } else {
+                Log.warn(this, "JDK 1.1 platforms cannot minimize or 
unminimize windows");
+            }
+        }
+        protected void _setMaximized(boolean b) {
+            if (frame!=null) {
+                frame.setExtendedState(b ? Frame.MAXIMIZED_BOTH : 
Frame.NORMAL);
+            } else {
+                window.setLocation(new Point(0, 0));
+                window.setSize(Toolkit.getDefaultToolkit().getScreenSize());
+            }
+        }
+        
+//        private boolean invalidated = false;
+        
+        class InnerIcon extends ImageIcon {
+            public InnerIcon(Image buffer) { super(buffer); }
+            public void paintIcon(Component c, Graphics g, int x, int y) {
+                // need to synchronize as painting may occur during
+                // render() which will result in ugly flicker
+                synchronized (buffersync) {
+                    super.paintIcon(c, g, x, y);
+                }
+            }
+        }
+        
+        class InnerPanel extends JLabel {
+            private boolean reassignBuffer;
+            public InnerPanel(Image buffer) {
+                super(new InnerIcon(buffer));
+                setHorizontalAlignment(JLabel.LEFT);
+                setVerticalAlignment(JLabel.TOP);
+                setPreferredSize(getMaximumSize());
+                validate();
+            }
+            public void resetBuffer() { reassignBuffer = true; }
+            public boolean isDoubleBuffered() { return true; }
+            public void paintComponent(Graphics g) {
+                if (reassignBuffer) {
+                    ((ImageIcon)getIcon()).getImage().flush();
+                    setIcon(new InnerIcon(((AWTPixelBuffer)backbuffer).i));
+                    panel.setPreferredSize(panel.getMaximumSize());
+                    panel.validate();
+                    reassignBuffer = false;
+                }
+                super.paintComponent(g);
+//                ImageIcon icon = (ImageIcon)getIcon();
+//                System.out.println(AWTSurface.this.toString()+" panel paint 
"+getWidth()+", "+getHeight()+", "+icon.getIconWidth()+", 
"+icon.getIconHeight());
+//                if (icon.getIconWidth() < getWidth() || icon.getIconHeight() 
< getHeight()) {
+//                    g.setColor(rootcolor);
+//                    if (icon.getIconWidth() < getWidth()) {
+//                        g.fillRect(icon.getIconWidth(), 0, getWidth(), 
Math.max(icon.getIconHeight(), getHeight()));
+//                    }
+//                    if (icon.getIconHeight() < getHeight()) {
+//                        g.fillRect(0, icon.getIconHeight(), 
Math.max(icon.getIconWidth(), getWidth()), getHeight());
+//                    }
+//                }
+                // make sure that there's no nasty edges from a previous 
larger render
+//                if (root.width < getWidth() || root.height < getHeight()) {
+//                    System.out.println(root.width+", "+getWidth());
+//                    g.setColor(rootcolor);
+//                    if (root.width < getWidth()) {
+//                        g.fillRect(root.width, 0, getWidth(), 
Math.max(root.height, getHeight()));
+//                    }
+//                    if (root.height < getHeight()) {
+//                        g.fillRect(0, root.height, Math.max(root.width, 
getWidth()), getHeight());
+//                    }
+//                }
+            }
+//            public void paintComponent() {
+//            }
+//            public void invaidate() { super.invalidate(); invalidated = 
true; }
+//            public void update(Graphics gr) {
+//                System.out.println(AWTSurface.this.toString()+" panel update 
"+gr.getClipBounds());
+//                Rectangle r = gr.getClipBounds();
+//                if (r==null) r = new Rectangle(0, 0, getWidth(), 
getHeight());
+//                else {
+//                    gr.setClip(r.x + leftInset, r.y + topInset, r.width, 
r.height);
+//                }
+//                Image img = imageicon.getImage();
+//                if (img!=null) {
+//                    blit(img.getGraphics());
+//                }
+//            }
+//            public void paintComponent(Graphics gr) {
+//                System.out.println(AWTSurface.this.toString()+" panel paint 
"+gr.getClipBounds());
+//                Image img = imageicon.getImage();
+//                if (img!=null) {
+//                    blit(img.getGraphics());
+//                }
+//             }
+//                blit(gr);
+//            }
+//            Rectangle r = gr.getClipBounds();
+//            if (r==null) r = new Rectangle(0, 0, getWidth(), getHeight());
+//            if (r.x==bx && r.y==by && r.width==bwidth && r.height==bheight) {
+//                return;
+//            }
+//            panel.paint(gr); }
+        }
+
+        class InnerFrame extends JFrame {
+            public InnerFrame() throws java.lang.UnsupportedOperationException 
{ }
+            /** overrides the native Frame method to enforce a minimum size */
+            private Dimension minimumSize = new Dimension(0, 0);
+            public void setMinimumDimensions(int w, int h) { minimumSize = new 
Dimension(w, h); }
+            public Dimension getMinimumSize() { return minimumSize; }
+            public boolean isDoubleBuffered() { return true; }
+//            public ComponentAdapter enforceMinSize = new ComponentAdapter() {
+//                public void componentResized(ComponentEvent event) {
+//                    Dimension newDim = frame.getSize();
+//                    int minWidth = minimumSize.width + leftInset + 
rightInset;
+//                    int minHeight = minimumSize.height + topInset + 
bottomInset;
+//                    if (newDim.width < minWidth || newDim.height < 
minHeight) {
+//                        if (newDim.width < minWidth) {
+//                            newDim.width = minWidth;
+//                        }
+//                        if (newDim.height < minHeight) {
+//                            newDim.height = minHeight;
+//                        }
+//                        frame.setSize(newDim);
+//                    }
+//                }
+//            };
+            public InputMethodRequests getInputMethodRequests() {
+                // REMARK - makes us a passive client FOOTNOTE1
+                return null;
+            }
+        }
+
+        class InnerWindow extends Window {
+            public InnerWindow() throws 
java.lang.UnsupportedOperationException {
+                super(new Frame());
+            }
+            public boolean isDoubleBuffered() { return true; }
+            /** overrides the native Window method to enforce a minimum size */
+            private Dimension minimumSize = new Dimension(0, 0);
+            public void setMinimumDimensions(int w, int h) { minimumSize = new 
Dimension(w, h); }
+            public Dimension getMinimumSize() { return minimumSize; }
+//            public void update(Graphics gr) { paint(gr); }
+//            public void paint(Graphics gr) { blit(backbuffer, gr); }
+            public InputMethodRequests getInputMethodRequests() {
+                // REMARK - makes us a passive client FOOTNOTE1
+                return null;
+            }
+        }
+
+        /** establish the size of the frame decorations */
+        private void discoverInsets() {
+            Insets i = window.getInsets();
+            if (leftInset == i.left && topInset == i.top && bottomInset == 
i.bottom && rightInset == i.right) {
+                return;
+            }
+            leftInset = i.left;
+            topInset = i.top;
+            bottomInset = i.bottom;
+            rightInset = i.right;
+            if (window.getWidth() != pendingWidth + (leftInset + rightInset)
+                    || window.getHeight() != pendingHeight + (topInset + 
bottomInset)) {
+                // respect the size of the root box
+                window.setSize(pendingWidth + (leftInset + rightInset), 
pendingHeight + (topInset + bottomInset));
+            }
+        }
+        
+        /** sets the maximum size of the frame */
+        public void _setMaximumSize(int maxw, int maxh) {
+            
+        }
+
+        /** note that the minimum size is handled by getMinimumSize in 
InnerFrame/InnerWindow */
+        public void _setMinimumSize(int minw, int minh) {
+            if (frame != null) {
+                ((InnerFrame)frame).setMinimumDimensions(minw, minh);
+            } else if (window != null) {
+                ((InnerWindow)window).setMinimumDimensions(minw, minh);
+            }
+            // FEATURE: Java 1.5
+            //window.setMinimumSize(new 
Dimension(root.minwidth+leftInset+rightInset, 
root.minheight+topInset+bottomInset));
+        }
+        
+        /** set whether a frame can be resized by the user */
+        public void _setResizable(boolean resizable) {
+            if (frame != null) {
+                frame.setResizable(resizable);
+                if (!resizable) {
+                    Dimension min = frame.getMinimumSize();
+                    frame.setSize(min.width, min.height);
+                }
+            }
+        }
+
+        private int rootfill = 0x0;
+        private Color rootcolor = null;
+        private final void setBackgroundColor() {
+            if (rootfill == root.getIntFillcolor()) {
+                return;
+            }
+            rootfill = root.getIntFillcolor();
+            rootcolor = (rootfill & 0xFF000000) == 0 ? Color.white :
+                new Color((rootfill >> 16) & 0xff, (rootfill >> 8) & 0xff, 
(rootfill) & 0xff);
+            window.setBackground(rootcolor);
+            panel.setBackground(rootcolor);
+        }
+
+        public RenderedRegions render() {
+            RenderedRegions r;
+            synchronized (buffersync) {
+                // REMARK: it is entirely possible that our initial estimation
+                // for the maximum window size is exceeded, so here we check 
for
+                // this condition and resize the backbuffer as necessary - and
+                // only do so during render to avoid rapid repeats during 
resize
+                AWTPixelBuffer buf = (AWTPixelBuffer)backbuffer;
+                if (buf.getWidth() < pendingWidth || buf.getHeight() < 
pendingHeight) {
+                    int h = Math.max(pendingWidth, buf.getWidth());
+                    int w = Math.max(pendingHeight, buf.getHeight());
+                    AWTPixelBuffer new_backbuffer = 
(AWTPixelBuffer)Platform.createPixelBuffer(w, h, null);
+                    // actually an increase in height/width will cause 
super.render()
+                    // to re-render the entire backbuffer, so we can skip this 
part
+                    /*
+                    Graphics g = new_backbuffer.i.getGraphics();
+                    g.drawImage(buf.i, 0, 0, null);
+                    g.dispose();
+                    */
+                    buf = new_backbuffer;
+                    backbuffer = new_backbuffer;
+                    ((InnerPanel)panel).resetBuffer();
+                }
+
+                buf.g = buf.i.getGraphics();
+                if (pendingWidth < root.width || pendingHeight < root.height) {
+                    buf.g.setColor(rootcolor);
+                    if (pendingWidth < root.width) {
+                        buf.g.fillRect(pendingWidth, 0, root.width, 
Math.max(pendingHeight, root.height));
+                    }
+                    if (pendingHeight < root.height) {
+                        buf.g.fillRect(0, pendingHeight, 
Math.max(pendingWidth, root.width), root.height);
+                    }
+                }
+                r = super.render();
+                buf.g.dispose();
+                buf.g = null;
+
+                if (r==null) {
+                    return null;
+                }
+
+                // useful optimisation;
+                setBackgroundColor();
+
+                int x = root.width;
+                int y = root.height;
+                int w = 0;
+                int h = 0;
+                
+                final int numregions = r.numregions;
+                final int[] regions = r.regions;
+                final int rwidth = root.width;
+                final int rheight = root.height;
+                // REMARK: whilst in theory only blitting the dirty areas
+                // would seem more efficient, in practise it results in a
+                // large slowdown due to a delay in processing each blit,
+                // so instead we just blit a single all-encompassing area
+                // NB: original implementation in r4011
+                for (int i = 0; i < numregions; i++) {
+                    // consumed dirty areas are marked with a -1
+                    if (regions[4*i]<0) {
+                        continue;
+                    }
+                    //System.out.println(toString()+" region: 
"+regions[4*i]+", "+regions[4*i+1]+", "+regions[4*i+2]+", "+regions[4*i+3]);
+                    // handle confirmed dirty areas
+                    x = Math.min(x, regions[4*i]);
+                    y = Math.min(y, regions[4*i+1]);
+                    w = Math.max(w, regions[4*i+2]);
+                    h = Math.max(h, regions[4*i+3]);
+                }
+                if (x < 0) x = 0;
+                if (y < 0) y = 0;
+                if (w > rwidth) w = rwidth;
+                if (h > rheight) h = rheight;
+                if (w > x && h > y) {
+                    //System.out.println(toString()+" render: "+x+", "+y+", 
"+w+", "+h);
+                    // push repaint in to the EDT
+                    panel.repaint(x, y, w-x, h-y);
+                }
+            }
+            return r;
+        }
+
+        AWTSurface(Box root, boolean framed) {
+            super(root);
+            try {
+                if (framed) {
+                    window = frame = new InnerFrame();
+                    // ensures user cannot resize frame to be smaller than 
contents
+                    // FIXME: this does not work on Linux
+//                    
frame.addComponentListener(((InnerFrame)frame).enforceMinSize);
+                } else {
+                    window = new InnerWindow();
+                }
+                panel = new InnerPanel(((AWTPixelBuffer)backbuffer).i);
+                window.add(panel);
+                window.validate();
+            } catch (java.lang.UnsupportedOperationException e) {
+                // this is here to catch HeadlessException on jdk1.4
+                Log.system.error(this, "Exception thrown creating Surface 
"+(framed?"frame":"window"));
+                Log.system.error(this, e);
+            }
+            
+            // Theoretically pack causes the insets to be calculated.
+            //
+            // AWT Bug - (Linux X11 at least)
+            // The insets calculated are not always exact. It seems 
+            // that only a guess is returned.
+            window.pack();
+            
+            // initialise the backbuffer and window with the root colour
+            // REMARK - this is for nicer initial rendering
+            setBackgroundColor();
+
+            panel.addComponentListener(this);
+            panel.addMouseListener(this);
+            panel.addMouseMotionListener(this);
+            panel.addMouseWheelListener(this);
+            window.addKeyListener(this);
+            window.addWindowListener(this);
+            // workaround for an OS X limitation
+            try {
+                window.getInputContext().setCompositionEnabled(false);
+            } catch (UnsupportedOperationException e) {
+                Log.system.warn(this, "setCompositionEnabled() failed. Are you 
running OS X?");
+            }
+            System.out.println(toString()+" constructed");
+        }
+
+        // IMPORTANT: makeVisible must be called before render()
+        // to ensure that our peer has been created
+        public void makeVisible(boolean b) {
+            window.setVisible(b);
+            // initialize Swing's double buffering otherwise certain actions
+            // (notably resizing frames) will not be done smoothly
+            // NB: this must be called after setVisible()
+            if (frame!=null) frame.createBufferStrategy(2);
+        }
+        
+        public void _dispose() {
+            panel.removeMouseListener(this);
+            panel.removeMouseWheelListener(this);
+            panel.removeKeyListener(this);
+            panel.removeComponentListener(this);
+            panel.removeMouseMotionListener(this);
+            window.removeWindowListener(this);
+            window.dispose();
+        }
+
+        public void syncCursor() {
+            if (cursor.equals("crosshair")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
+            else if (cursor.equals("east")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
+            else if (cursor.equals("move")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+            else if (cursor.equals("north")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
+            else if (cursor.equals("northeast")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
+            else if (cursor.equals("northwest")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
+            else if (cursor.equals("south")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
+            else if (cursor.equals("southeast")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
+            else if (cursor.equals("southwest")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
+            else if (cursor.equals("text")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
+            else if (cursor.equals("west")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
+            else if (cursor.equals("wait")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+            else if (cursor.equals("hand")) 
window.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+            else 
window.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+        }
+        
+        // AWT Message translation 
////////////////////////////////////////////////////////////////
+        
+        // These functions are all executed in the AWT thread, not the
+        // MessageQueue thread. As a result, they must be *extremely*
+        // careful about invoking methods on instances of Box. Currently,
+        // they should only enqueue messages, use Box.whoIs()
+        // (unsynchronised but thought to be safe), and modify members of
+        // Surface.
+        
+        public void componentHidden(ComponentEvent e) { }
+        public void componentShown(ComponentEvent e) { }
+        public void windowOpened(WindowEvent e) { }
+        public void windowClosed(WindowEvent e) { }
+        public void windowClosing(WindowEvent e) { Close(); }
+        public void windowIconified(WindowEvent e) { Minimized(true); }
+        public void windowDeiconified(WindowEvent e) { window.repaint(0, 0, 
root.width, root.height); Minimized(false); }
+        public void windowActivated(WindowEvent e) { Focused(true); }
+        public void windowDeactivated(WindowEvent e) { Focused(false); }
+        public void componentMoved(ComponentEvent e) { 
PosChange(window.getLocation().x, window.getLocation().y); }
+
+        public void componentResized(ComponentEvent e) {
+            // Insets first known exactly on first componentResized event
+            // Before being set visible (AWT on top of XWindows at least) 
+            // after pack it seems AWT guesses what the insets will be
+            // so check every time in case they change(?!)
+            discoverInsets();
+            componentResized(window.getWidth() - leftInset - rightInset, 
window.getHeight() - topInset - bottomInset);
+            window.repaint();
+        }
+
+        /** frame/window resize notification with newwidth and newheight
+         *  in relation to the backbuffer i.e. insets already subtracted
+         **/
+        public void componentResized(int newwidth, int newheight) { 
SizeChange(newwidth, newheight); }
+        
+        private void checkEvent(InputEvent e) {
+            alt = e.isAltDown();
+            control = e.isControlDown();
+            shift = e.isShiftDown();
+        }
+
+        public void keyTyped(KeyEvent k) {
+            checkEvent(k);
+            // REMARK - we are a so called 'passive Input Method' client.
+            // We receive composed text (e.g. Asian characters) as key events
+            // where we want to convert them into a KeyPressed/KeyReleased
+            // Vexi event. Unfortunately we need a way to distinguish between 
+            // events due to inputMethods (which we want) and those from the
+            // keyboard which are already handled via keyPressed/keyReleased.
+            // HACK - simplest is to just except non-ascii chars.
+            int unicode = (int)k.getKeyChar();
+            if (255 >= unicode) {
+                return;
+            }
+            KeyPressed(""+k.getKeyChar());
+            KeyReleased(""+k.getKeyChar());
+        }
+
+
+        //////// OVERRIDES FOR EVENT LISTENERS 
////////////////////////////////////////////////
+
+        public void keyPressed(KeyEvent k) { checkEvent(k); 
KeyPressed(translateKey(k)); }
+        public void keyReleased(KeyEvent k) { checkEvent(k); 
KeyReleased(translateKey(k)); }
+        public void mouseExited(MouseEvent m) { mouseMoved(m); }
+        public void mouseEntered(MouseEvent m) { mouseMoved(m); }
+        public void mouseDragged(MouseEvent m) { mouseMoved(m); }
+        public void mouseMoved(MouseEvent m) { checkEvent(m); Move(m.getX(), 
m.getY()); } //window.repaint(); }
+        public void mousePressed(MouseEvent m) { checkEvent(m); 
Press(modifiersToButtonNumber(m.getModifiers())); }
+        public void mouseReleased(MouseEvent m) { checkEvent(m); 
Release(modifiersToButtonNumber(m.getModifiers())); }
+        public void mouseClicked(MouseEvent m) {
+            checkEvent(m);
+            if (m.getClickCount() == 2) {
+                DoubleClick(modifiersToButtonNumber(m.getModifiers()));
+            } else {
+                Click(modifiersToButtonNumber(m.getModifiers()));
+            }
+        }
+        public void mouseWheelMoved(MouseWheelEvent e) {
+            checkEvent(e);
+            int notches = e.getWheelRotation();
+            // FIXME: take into account WHEEL_BLOCK_SCROLL / WHEEL_UNIT_SCROLL
+            VScroll(notches);
+        }
+        protected boolean _isLockingKeyOn(int key) {
+            // AWT's support for getLockingKeyState is minimal at best and
+            // more often than not throws an UnsupportedOperationException
+            int eventkeyid = -1;
+            switch (key) {
+            case Surface.CAPS_LOCK: eventkeyid = KeyEvent.VK_CAPS_LOCK; break;
+            case Surface.KANA_LOCK: eventkeyid = KeyEvent.VK_KANA_LOCK; break;
+            case Surface.NUM_LOCK: eventkeyid = KeyEvent.VK_NUM_LOCK; break;
+            case Surface.SCROLL_LOCK: eventkeyid = KeyEvent.VK_SCROLL_LOCK; 
break;
+            }
+            if (eventkeyid == -1) {
+                return false;
+            }
+            return window.getToolkit().getLockingKeyState(eventkeyid);
+        }
+        
+        String translateKey(KeyEvent k) {
+            switch (k.getKeyCode()) {
+            case KeyEvent.VK_ALT: return "alt";
+            case KeyEvent.VK_BACK_SPACE: return "back_space";
+            case KeyEvent.VK_CONTROL: return "control";
+            case KeyEvent.VK_DELETE: return "delete";
+            case KeyEvent.VK_DOWN: return "down";
+            case KeyEvent.VK_END: return "end";
+            case KeyEvent.VK_ENTER: return "enter";
+            case KeyEvent.VK_ESCAPE: return "escape";
+            case KeyEvent.VK_F1: return "f1";
+            case KeyEvent.VK_F10: return "f10";
+            case KeyEvent.VK_F11: return "f11";
+            case KeyEvent.VK_F12: return "f12";
+            case KeyEvent.VK_F2: return "f2";
+            case KeyEvent.VK_F3: return "f3";
+            case KeyEvent.VK_F4: return "f4";
+            case KeyEvent.VK_F5: return "f5";
+            case KeyEvent.VK_F6: return "f6"; 
+            case KeyEvent.VK_F7: return "f7";
+            case KeyEvent.VK_F8: return "f8";
+            case KeyEvent.VK_F9: return "f9";
+            case KeyEvent.VK_HOME: return "home";
+            case KeyEvent.VK_INSERT: return "insert";
+            case KeyEvent.VK_LEFT: return "left";
+            case KeyEvent.VK_META: return "alt";
+            case KeyEvent.VK_PAGE_DOWN: return "page_down";
+            case KeyEvent.VK_PAGE_UP: return "page_up";
+            case KeyEvent.VK_PAUSE: return "pause";
+            case KeyEvent.VK_PRINTSCREEN: return "printscreen";
+            case KeyEvent.VK_RIGHT: return "right";
+            case KeyEvent.VK_SHIFT: return "shift";
+            case KeyEvent.VK_TAB: return "tab";
+            case KeyEvent.VK_UP: return "up";
+            case KeyEvent.VK_CAPS_LOCK: return "caps_lock";
+            case KeyEvent.VK_KANA_LOCK: return "kana_lock";
+            case KeyEvent.VK_NUM_LOCK: return "num_lock";
+            case KeyEvent.VK_SCROLL_LOCK: return "scroll_lock";
+            case KeyEvent.VK_UNDEFINED: return "unknown";
+            // VK_ entries not defined for Java 1.4
+            case 524: return "windows_key";
+            case 525: return "context_menu";
+            // usually only single / known characters
+            default:
+                char c = k.getKeyChar();
+                if (c >= 1 && c <= 26) {
+                    c = (char)('a' + c - 1);
+                }
+                return String.valueOf(c);
+            }
+        }
+        public void drawLine(int x1, int y1, int x2, int y2, int color) {
+            // TODO Auto-generated method stub
+            throw new Error("FIXME: not yet implemented");
+        }
+        public void fillTriangle(int x1, int y1, int x2, int y2, int x3, int 
y3, int color) {
+            // TODO Auto-generated method stub
+            throw new Error("FIXME: not yet implemented");
+        }
+    }
+
+    /** Processes the supplied InputStream and converts it to a format
+     *  that can be assigned as the contents of the given Picture
+     */
+    protected void _decodeJPEG(InputStream is, Picture p) {
+        try {
+            Image i = 
Toolkit.getDefaultToolkit().createImage(IOUtil.toByteArray(is));
+            MediaTracker mediatracker = new MediaTracker(new Canvas());
+            mediatracker.addImage(i, 1);
+            try {
+                mediatracker.waitForAll();
+            } catch (InterruptedException e) { }
+            mediatracker.removeImage(i);
+            final int width = i.getWidth(null);
+            final int height = i.getHeight(null);
+            final int[] data = new int[width * height];
+            PixelGrabber pg = new PixelGrabber(i, 0, 0, width, height, data, 
0, width);
+            pg.grabPixels();
+            if ((pg.getStatus() & ImageObserver.ABORT) != 0) {
+                Log.system.info(this, "PixelGrabber reported an error while 
decoding JPEG image");
+            }
+            p.width = width;
+            p.height = height;
+            p.data = data;
+        } catch (Exception e) {
+            Log.system.warn(this, "Exception caught while decoding JPEG 
image");
+            Log.system.warn(this, e);
+        }
+    }
+}
+/* FOOTNOTES
+ * 1. Passive InputMethod Client - means: we only handle committed text. 
+ * 
+ * The alternative to be an 'Active InputMethod Client' would mean we have 
+ * in place composition, this presents quite a lot of complexity. We would 
need to 
+ * interface with the text widgets and get it to render uncommitted text, move 
the
+ * caret ... etc.
+ * 
+ */


Property changes on: 
trunk/org.vexi-core.main/src/main/java/org/vexi/plat/Swing.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
Index, Search & Analyze Logs and other IT data in Real-Time with Splunk 
Collect, index and harness all the fast moving IT data generated by your 
applications, servers and devices whether physical, virtual or in the cloud.
Deliver compliance at lower cost and gain new business insights. 
Free Software Download: http://p.sf.net/sfu/splunk-dev2dev
_______________________________________________
Vexi-svn mailing list
Vexi-svn@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/vexi-svn

Reply via email to