/*
 * Copyright (c) 2009, 2017, 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.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Label;
import java.awt.MouseInfo;
import java.awt.Panel;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.text.BreakIterator;
import java.util.concurrent.ArrayBlockingQueue;

import sun.awt.SunToolkit;

An utility window class. This is a base class for Tooltip and Balloon.
/** * 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. @SuppressWarnings("deprecation") protected void show(Point corner, int indent) { assert SunToolkit.isDispatchThreadForAppContext(this); pack(); Dimension size = getSize(); Rectangle scrSize = getGraphicsConfiguration().getBounds(); if (corner.x < scrSize.x + scrSize.width/2 && corner.y < scrSize.y + scrSize.height/2) { // 1st square setLocation(corner.x + indent, corner.y + indent); } else if (corner.x >= scrSize.x + scrSize.width/2 && corner.y < scrSize.y + scrSize.height/2) { // 2nd square setLocation(corner.x - indent - size.width, corner.y + indent); } else if (corner.x < scrSize.x + scrSize.width/2 && corner.y >= scrSize.y + scrSize.height/2) { // 3rd square setLocation(corner.x + indent, corner.y - indent - size.height); } else if (corner.x >= scrSize.x +scrSize.width/2 && corner.y >= scrSize.y +scrSize.height/2) { // 4th square setLocation(corner.x - indent - size.width, corner.y - indent - size.height); } super.show(); closer.schedule(); } @SuppressWarnings("deprecation") 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. @SuppressWarnings("deprecation") 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.
/** Whether the target of the InfoWindow is disposed. */
boolean isDisposed();
The bounds of the target of the InfoWindow.
/** 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.
/** 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 static final int TOOLTIP_SHOW_TIME = 10000; private static final int TOOLTIP_START_DELAY_TIME = 1000; private static final int TOOLTIP_MAX_LENGTH = 64; private static final int TOOLTIP_MOUSE_CURSOR_INDENT = 5; private static final Color TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 220); private static final 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.
/** The action to be performed upon clicking the baloon. */
String getActionCommand(); } private final LiveArguments liveArguments; private final Object target; private static final int BALLOON_SHOW_TIME = 10000; private static final int BALLOON_TEXT_MAX_LENGTH = 256; private static final int BALLOON_WORD_LINE_MAX_LENGTH = 16; private static final int BALLOON_WORD_LINE_MAX_COUNT = 4; private static final int BALLOON_ICON_WIDTH = 32; private static final int BALLOON_ICON_HEIGHT = 32; private static final int BALLOON_TRAY_ICON_INDENT = 0; private static final Color BALLOON_CAPTION_BACKGROUND_COLOR = new Color(200, 200 ,255); private static final 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.thread.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.thread.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; } } @SuppressWarnings("deprecation") 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 implements Runnable { final int MAX_CONCURRENT_MSGS = 10; ArrayBlockingQueue<Message> messageQueue = new ArrayBlockingQueue<Message>(MAX_CONCURRENT_MSGS); boolean isDisplayed; final Thread thread; Displayer() { this.thread = new Thread(null, this, "Displayer", 0, false); this.thread.setDaemon(true); } @Override 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; } } } }