jdk/src/java.desktop/unix/classes/sun/awt/X11/InfoWindow.java
changeset 25859 3317bb8137f4
parent 24538 25bf8153fbfe
child 28231 b608ffcaed74
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/unix/classes/sun/awt/X11/InfoWindow.java	Sun Aug 17 15:54:13 2014 +0100
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.awt.X11;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.awt.peer.TrayIconPeer;
+import sun.awt.*;
+import java.awt.image.*;
+import java.text.BreakIterator;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * An utility window class. This is a base class for Tooltip and Balloon.
+ */
+@SuppressWarnings("serial") // JDK-implementation class
+public abstract class InfoWindow extends Window {
+    private Container container;
+    private Closer closer;
+
+    protected InfoWindow(Frame parent, Color borderColor) {
+        super(parent);
+        setType(Window.Type.POPUP);
+        container = new Container() {
+            @Override
+            public Insets getInsets() {
+                return new Insets(1, 1, 1, 1);
+            }
+        };
+        setLayout(new BorderLayout());
+        setBackground(borderColor);
+        add(container, BorderLayout.CENTER);
+        container.setLayout(new BorderLayout());
+
+        closer = new Closer();
+    }
+
+    public Component add(Component c) {
+        container.add(c, BorderLayout.CENTER);
+        return c;
+    }
+
+    protected void setCloser(Runnable action, int time) {
+        closer.set(action, time);
+    }
+
+    // Must be executed on EDT.
+    protected void show(Point corner, int indent) {
+        assert SunToolkit.isDispatchThreadForAppContext(this);
+
+        pack();
+
+        Dimension size = getSize();
+        // TODO: When 6356322 is fixed we should get screen bounds in
+        // this way: eframe.getGraphicsConfiguration().getBounds().
+        Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize();
+
+        if (corner.x < scrSize.width/2 && corner.y < scrSize.height/2) { // 1st square
+            setLocation(corner.x + indent, corner.y + indent);
+
+        } else if (corner.x >= scrSize.width/2 && corner.y < scrSize.height/2) { // 2nd square
+            setLocation(corner.x - indent - size.width, corner.y + indent);
+
+        } else if (corner.x < scrSize.width/2 && corner.y >= scrSize.height/2) { // 3rd square
+            setLocation(corner.x + indent, corner.y - indent - size.height);
+
+        } else if (corner.x >= scrSize.width/2 && corner.y >= scrSize.height/2) { // 4th square
+            setLocation(corner.x - indent - size.width, corner.y - indent - size.height);
+        }
+
+        super.show();
+        closer.schedule();
+    }
+
+    public void hide() {
+        closer.close();
+    }
+
+    private class Closer implements Runnable {
+        Runnable action;
+        int time;
+
+        public void run() {
+            doClose();
+        }
+
+        void set(Runnable action, int time) {
+            this.action = action;
+            this.time = time;
+        }
+
+        void schedule() {
+            XToolkit.schedule(this, time);
+        }
+
+        void close() {
+            XToolkit.remove(this);
+            doClose();
+        }
+
+        // WARNING: this method may be executed on Toolkit thread.
+        private void doClose() {
+            SunToolkit.executeOnEventHandlerThread(InfoWindow.this, new Runnable() {
+                public void run() {
+                    InfoWindow.super.hide();
+                    invalidate();
+                    if (action != null) {
+                        action.run();
+                    }
+                }
+            });
+        }
+    }
+
+
+    private interface LiveArguments {
+        /** Whether the target of the InfoWindow is disposed. */
+        boolean isDisposed();
+
+        /** The bounds of the target of the InfoWindow. */
+        Rectangle getBounds();
+    }
+
+    @SuppressWarnings("serial") // JDK-implementation class
+    public static class Tooltip extends InfoWindow {
+
+        public interface LiveArguments extends InfoWindow.LiveArguments {
+            /** The tooltip to be displayed. */
+            String getTooltipString();
+        }
+
+        private final Object target;
+        private final LiveArguments liveArguments;
+
+        private final Label textLabel = new Label("");
+        private final Runnable starter = new Runnable() {
+                public void run() {
+                    display();
+                }};
+
+        private final static int TOOLTIP_SHOW_TIME = 10000;
+        private final static int TOOLTIP_START_DELAY_TIME = 1000;
+        private final static int TOOLTIP_MAX_LENGTH = 64;
+        private final static int TOOLTIP_MOUSE_CURSOR_INDENT = 5;
+        private final static Color TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 220);
+        private final static Font TOOLTIP_TEXT_FONT = XWindow.getDefaultFont();
+
+        public Tooltip(Frame parent, Object target,
+                LiveArguments liveArguments)
+        {
+            super(parent, Color.black);
+
+            this.target = target;
+            this.liveArguments = liveArguments;
+
+            XTrayIconPeer.suppressWarningString(this);
+
+            setCloser(null, TOOLTIP_SHOW_TIME);
+            textLabel.setBackground(TOOLTIP_BACKGROUND_COLOR);
+            textLabel.setFont(TOOLTIP_TEXT_FONT);
+            add(textLabel);
+        }
+
+        /*
+         * WARNING: this method is executed on Toolkit thread!
+         */
+        private void display() {
+            // Execute on EDT to avoid deadlock (see 6280857).
+            SunToolkit.executeOnEventHandlerThread(target, new Runnable() {
+                    public void run() {
+                        if (liveArguments.isDisposed()) {
+                            return;
+                        }
+
+                        String tooltipString = liveArguments.getTooltipString();
+                        if (tooltipString == null) {
+                            return;
+                        } else if (tooltipString.length() >  TOOLTIP_MAX_LENGTH) {
+                            textLabel.setText(tooltipString.substring(0, TOOLTIP_MAX_LENGTH));
+                        } else {
+                            textLabel.setText(tooltipString);
+                        }
+
+                        Point pointer = AccessController.doPrivileged(
+                            new PrivilegedAction<Point>() {
+                                public Point run() {
+                                    if (!isPointerOverTrayIcon(liveArguments.getBounds())) {
+                                        return null;
+                                    }
+                                    return MouseInfo.getPointerInfo().getLocation();
+                                }
+                            });
+                        if (pointer == null) {
+                            return;
+                        }
+                        show(new Point(pointer.x, pointer.y), TOOLTIP_MOUSE_CURSOR_INDENT);
+                    }
+                });
+        }
+
+        public void enter() {
+            XToolkit.schedule(starter, TOOLTIP_START_DELAY_TIME);
+        }
+
+        public void exit() {
+            XToolkit.remove(starter);
+            if (isVisible()) {
+                hide();
+            }
+        }
+
+        private boolean isPointerOverTrayIcon(Rectangle trayRect) {
+            Point p = MouseInfo.getPointerInfo().getLocation();
+            return !(p.x < trayRect.x || p.x > (trayRect.x + trayRect.width) ||
+                     p.y < trayRect.y || p.y > (trayRect.y + trayRect.height));
+        }
+    }
+
+    @SuppressWarnings("serial") // JDK-implementation class
+    public static class Balloon extends InfoWindow {
+
+        public interface LiveArguments extends InfoWindow.LiveArguments {
+            /** The action to be performed upon clicking the baloon. */
+            String getActionCommand();
+        }
+
+        private final LiveArguments liveArguments;
+        private final Object target;
+
+        private final static int BALLOON_SHOW_TIME = 10000;
+        private final static int BALLOON_TEXT_MAX_LENGTH = 256;
+        private final static int BALLOON_WORD_LINE_MAX_LENGTH = 16;
+        private final static int BALLOON_WORD_LINE_MAX_COUNT = 4;
+        private final static int BALLOON_ICON_WIDTH = 32;
+        private final static int BALLOON_ICON_HEIGHT = 32;
+        private final static int BALLOON_TRAY_ICON_INDENT = 0;
+        private final static Color BALLOON_CAPTION_BACKGROUND_COLOR = new Color(200, 200 ,255);
+        private final static Font BALLOON_CAPTION_FONT = new Font(Font.DIALOG, Font.BOLD, 12);
+
+        private Panel mainPanel = new Panel();
+        private Panel captionPanel = new Panel();
+        private Label captionLabel = new Label("");
+        private Button closeButton = new Button("X");
+        private Panel textPanel = new Panel();
+        private XTrayIconPeer.IconCanvas iconCanvas = new XTrayIconPeer.IconCanvas(BALLOON_ICON_WIDTH, BALLOON_ICON_HEIGHT);
+        private Label[] lineLabels = new Label[BALLOON_WORD_LINE_MAX_COUNT];
+        private ActionPerformer ap = new ActionPerformer();
+
+        private Image iconImage;
+        private Image errorImage;
+        private Image warnImage;
+        private Image infoImage;
+        private boolean gtkImagesLoaded;
+
+        private Displayer displayer = new Displayer();
+
+        public Balloon(Frame parent, Object target, LiveArguments liveArguments) {
+            super(parent, new Color(90, 80 ,190));
+            this.liveArguments = liveArguments;
+            this.target = target;
+
+            XTrayIconPeer.suppressWarningString(this);
+
+            setCloser(new Runnable() {
+                    public void run() {
+                        if (textPanel != null) {
+                            textPanel.removeAll();
+                            textPanel.setSize(0, 0);
+                            iconCanvas.setSize(0, 0);
+                            XToolkit.awtLock();
+                            try {
+                                displayer.isDisplayed = false;
+                                XToolkit.awtLockNotifyAll();
+                            } finally {
+                                XToolkit.awtUnlock();
+                            }
+                        }
+                    }
+                }, BALLOON_SHOW_TIME);
+
+            add(mainPanel);
+
+            captionLabel.setFont(BALLOON_CAPTION_FONT);
+            captionLabel.addMouseListener(ap);
+
+            captionPanel.setLayout(new BorderLayout());
+            captionPanel.add(captionLabel, BorderLayout.WEST);
+            captionPanel.add(closeButton, BorderLayout.EAST);
+            captionPanel.setBackground(BALLOON_CAPTION_BACKGROUND_COLOR);
+            captionPanel.addMouseListener(ap);
+
+            closeButton.addActionListener(new ActionListener() {
+                    public void actionPerformed(ActionEvent e) {
+                        hide();
+                    }
+                });
+
+            mainPanel.setLayout(new BorderLayout());
+            mainPanel.setBackground(Color.white);
+            mainPanel.add(captionPanel, BorderLayout.NORTH);
+            mainPanel.add(iconCanvas, BorderLayout.WEST);
+            mainPanel.add(textPanel, BorderLayout.CENTER);
+
+            iconCanvas.addMouseListener(ap);
+
+            for (int i = 0; i < BALLOON_WORD_LINE_MAX_COUNT; i++) {
+                lineLabels[i] = new Label();
+                lineLabels[i].addMouseListener(ap);
+                lineLabels[i].setBackground(Color.white);
+            }
+
+            displayer.start();
+        }
+
+        public void display(String caption, String text, String messageType) {
+            if (!gtkImagesLoaded) {
+                loadGtkImages();
+            }
+            displayer.display(caption, text, messageType);
+        }
+
+        private void _display(String caption, String text, String messageType) {
+            captionLabel.setText(caption);
+
+            BreakIterator iter = BreakIterator.getWordInstance();
+            if (text != null) {
+                iter.setText(text);
+                int start = iter.first(), end;
+                int nLines = 0;
+
+                do {
+                    end = iter.next();
+
+                    if (end == BreakIterator.DONE ||
+                        text.substring(start, end).length() >= 50)
+                    {
+                        lineLabels[nLines].setText(text.substring(start, end == BreakIterator.DONE ?
+                                                                  iter.last() : end));
+                        textPanel.add(lineLabels[nLines++]);
+                        start = end;
+                    }
+                    if (nLines == BALLOON_WORD_LINE_MAX_COUNT) {
+                        if (end != BreakIterator.DONE) {
+                            lineLabels[nLines - 1].setText(
+                                new String(lineLabels[nLines - 1].getText() + " ..."));
+                        }
+                        break;
+                    }
+                } while (end != BreakIterator.DONE);
+
+
+                textPanel.setLayout(new GridLayout(nLines, 1));
+            }
+
+            if ("ERROR".equals(messageType)) {
+                iconImage = errorImage;
+            } else if ("WARNING".equals(messageType)) {
+                iconImage = warnImage;
+            } else if ("INFO".equals(messageType)) {
+                iconImage = infoImage;
+            } else {
+                iconImage = null;
+            }
+
+            if (iconImage != null) {
+                Dimension tpSize = textPanel.getSize();
+                iconCanvas.setSize(BALLOON_ICON_WIDTH, (BALLOON_ICON_HEIGHT > tpSize.height ?
+                                                        BALLOON_ICON_HEIGHT : tpSize.height));
+                iconCanvas.validate();
+            }
+
+            SunToolkit.executeOnEventHandlerThread(target, new Runnable() {
+                    public void run() {
+                        if (liveArguments.isDisposed()) {
+                            return;
+                        }
+                        Point parLoc = getParent().getLocationOnScreen();
+                        Dimension parSize = getParent().getSize();
+                        show(new Point(parLoc.x + parSize.width/2, parLoc.y + parSize.height/2),
+                             BALLOON_TRAY_ICON_INDENT);
+                        if (iconImage != null) {
+                            iconCanvas.updateImage(iconImage); // call it after the show(..) above
+                        }
+                    }
+                });
+        }
+
+        public void dispose() {
+            displayer.interrupt();
+            super.dispose();
+        }
+
+        private void loadGtkImages() {
+            if (!gtkImagesLoaded) {
+                errorImage = (Image)Toolkit.getDefaultToolkit().getDesktopProperty(
+                    "gtk.icon.gtk-dialog-error.6.rtl");
+                warnImage = (Image)Toolkit.getDefaultToolkit().getDesktopProperty(
+                    "gtk.icon.gtk-dialog-warning.6.rtl");
+                infoImage = (Image)Toolkit.getDefaultToolkit().getDesktopProperty(
+                    "gtk.icon.gtk-dialog-info.6.rtl");
+                gtkImagesLoaded = true;
+            }
+        }
+
+        private class ActionPerformer extends MouseAdapter {
+            public void mouseClicked(MouseEvent e) {
+                // hide the balloon by any click
+                hide();
+                if (e.getButton() == MouseEvent.BUTTON1) {
+                    ActionEvent aev = new ActionEvent(target, ActionEvent.ACTION_PERFORMED,
+                                                      liveArguments.getActionCommand(),
+                                                      e.getWhen(), e.getModifiers());
+                    XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev);
+                }
+            }
+        }
+
+        private class Displayer extends Thread {
+            final int MAX_CONCURRENT_MSGS = 10;
+
+            ArrayBlockingQueue<Message> messageQueue = new ArrayBlockingQueue<Message>(MAX_CONCURRENT_MSGS);
+            boolean isDisplayed;
+
+            Displayer() {
+                setDaemon(true);
+            }
+
+            public void run() {
+                while (true) {
+                    Message msg = null;
+                    try {
+                        msg = messageQueue.take();
+                    } catch (InterruptedException e) {
+                        return;
+                    }
+
+                    /*
+                     * Wait till the previous message is displayed if any
+                     */
+                    XToolkit.awtLock();
+                    try {
+                        while (isDisplayed) {
+                            try {
+                                XToolkit.awtLockWait();
+                            } catch (InterruptedException e) {
+                                return;
+                            }
+                        }
+                        isDisplayed = true;
+                    } finally {
+                        XToolkit.awtUnlock();
+                    }
+                    _display(msg.caption, msg.text, msg.messageType);
+                }
+            }
+
+            void display(String caption, String text, String messageType) {
+                messageQueue.offer(new Message(caption, text, messageType));
+            }
+        }
+
+        private static class Message {
+            String caption, text, messageType;
+
+            Message(String caption, String text, String messageType) {
+                this.caption = caption;
+                this.text = text;
+                this.messageType = messageType;
+            }
+        }
+    }
+}
+