Path: blob/master/src/java.desktop/unix/classes/sun/awt/X11/InfoWindow.java
41159 views
/*1* Copyright (c) 2009, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package sun.awt.X11;2627import java.awt.BorderLayout;28import java.awt.Button;29import java.awt.Color;30import java.awt.Component;31import java.awt.Container;32import java.awt.Dimension;33import java.awt.Font;34import java.awt.Frame;35import java.awt.GridLayout;36import java.awt.Image;37import java.awt.Insets;38import java.awt.Label;39import java.awt.MouseInfo;40import java.awt.Panel;41import java.awt.Point;42import java.awt.Rectangle;43import java.awt.Toolkit;44import java.awt.Window;45import java.awt.event.ActionEvent;46import java.awt.event.ActionListener;47import java.awt.event.MouseAdapter;48import java.awt.event.MouseEvent;49import java.security.AccessController;50import java.security.PrivilegedAction;51import java.text.BreakIterator;52import java.util.concurrent.ArrayBlockingQueue;5354import sun.awt.SunToolkit;55import sun.awt.UNIXToolkit;5657/**58* An utility window class. This is a base class for Tooltip and Balloon.59*/60@SuppressWarnings("serial") // JDK-implementation class61public abstract class InfoWindow extends Window {62private Container container;63private Closer closer;6465protected InfoWindow(Frame parent, Color borderColor) {66super(parent);67setType(Window.Type.POPUP);68container = new Container() {69@Override70public Insets getInsets() {71return new Insets(1, 1, 1, 1);72}73};74setLayout(new BorderLayout());75setBackground(borderColor);76add(container, BorderLayout.CENTER);77container.setLayout(new BorderLayout());7879closer = new Closer();80}8182public Component add(Component c) {83container.add(c, BorderLayout.CENTER);84return c;85}8687protected void setCloser(Runnable action, int time) {88closer.set(action, time);89}9091// Must be executed on EDT.92@SuppressWarnings("deprecation")93protected void show(Point corner, int indent) {94assert SunToolkit.isDispatchThreadForAppContext(this);9596pack();9798Dimension size = getSize();99Rectangle scrSize = getGraphicsConfiguration().getBounds();100101if (corner.x < scrSize.x + scrSize.width/2 && corner.y < scrSize.y + scrSize.height/2) { // 1st square102setLocation(corner.x + indent, corner.y + indent);103104} else if (corner.x >= scrSize.x + scrSize.width/2 && corner.y < scrSize.y + scrSize.height/2) { // 2nd square105setLocation(corner.x - indent - size.width, corner.y + indent);106107} else if (corner.x < scrSize.x + scrSize.width/2 && corner.y >= scrSize.y + scrSize.height/2) { // 3rd square108setLocation(corner.x + indent, corner.y - indent - size.height);109110} else if (corner.x >= scrSize.x +scrSize.width/2 && corner.y >= scrSize.y +scrSize.height/2) { // 4th square111setLocation(corner.x - indent - size.width, corner.y - indent - size.height);112}113114super.show();115closer.schedule();116}117118@SuppressWarnings("deprecation")119public void hide() {120closer.close();121}122123private class Closer implements Runnable {124Runnable action;125int time;126127public void run() {128doClose();129}130131void set(Runnable action, int time) {132this.action = action;133this.time = time;134}135136void schedule() {137XToolkit.schedule(this, time);138}139140void close() {141XToolkit.remove(this);142doClose();143}144145// WARNING: this method may be executed on Toolkit thread.146@SuppressWarnings("deprecation")147private void doClose() {148SunToolkit.executeOnEventHandlerThread(InfoWindow.this, new Runnable() {149public void run() {150InfoWindow.super.hide();151invalidate();152if (action != null) {153action.run();154}155}156});157}158}159160161private interface LiveArguments {162/** Whether the target of the InfoWindow is disposed. */163boolean isDisposed();164165/** The bounds of the target of the InfoWindow. */166Rectangle getBounds();167}168169@SuppressWarnings("serial") // JDK-implementation class170public static class Tooltip extends InfoWindow {171172public interface LiveArguments extends InfoWindow.LiveArguments {173/** The tooltip to be displayed. */174String getTooltipString();175}176177private final Object target;178private final LiveArguments liveArguments;179180private final Label textLabel = new Label("");181private final Runnable starter = new Runnable() {182public void run() {183display();184}};185186private static final int TOOLTIP_SHOW_TIME = 10000;187private static final int TOOLTIP_START_DELAY_TIME = 1000;188private static final int TOOLTIP_MAX_LENGTH = 64;189private static final int TOOLTIP_MOUSE_CURSOR_INDENT = 5;190private static final Color TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 220);191private static final Font TOOLTIP_TEXT_FONT = XWindow.getDefaultFont();192193public Tooltip(Frame parent, Object target,194LiveArguments liveArguments)195{196super(parent, Color.black);197198this.target = target;199this.liveArguments = liveArguments;200201XTrayIconPeer.suppressWarningString(this);202203setCloser(null, TOOLTIP_SHOW_TIME);204textLabel.setBackground(TOOLTIP_BACKGROUND_COLOR);205textLabel.setFont(TOOLTIP_TEXT_FONT);206add(textLabel);207}208209/*210* WARNING: this method is executed on Toolkit thread!211*/212private void display() {213// Execute on EDT to avoid deadlock (see 6280857).214SunToolkit.executeOnEventHandlerThread(target, new Runnable() {215public void run() {216if (liveArguments.isDisposed()) {217return;218}219220String tooltipString = liveArguments.getTooltipString();221if (tooltipString == null) {222return;223} else if (tooltipString.length() > TOOLTIP_MAX_LENGTH) {224textLabel.setText(tooltipString.substring(0, TOOLTIP_MAX_LENGTH));225} else {226textLabel.setText(tooltipString);227}228229@SuppressWarnings("removal")230Point pointer = AccessController.doPrivileged(231new PrivilegedAction<Point>() {232public Point run() {233if (!isPointerOverTrayIcon(liveArguments.getBounds())) {234return null;235}236return MouseInfo.getPointerInfo().getLocation();237}238});239if (pointer == null) {240return;241}242show(new Point(pointer.x, pointer.y), TOOLTIP_MOUSE_CURSOR_INDENT);243}244});245}246247public void enter() {248XToolkit.schedule(starter, TOOLTIP_START_DELAY_TIME);249}250251public void exit() {252XToolkit.remove(starter);253if (isVisible()) {254hide();255}256}257258private boolean isPointerOverTrayIcon(Rectangle trayRect) {259Point p = MouseInfo.getPointerInfo().getLocation();260return !(p.x < trayRect.x || p.x > (trayRect.x + trayRect.width) ||261p.y < trayRect.y || p.y > (trayRect.y + trayRect.height));262}263}264265@SuppressWarnings("serial") // JDK-implementation class266public static class Balloon extends InfoWindow {267268public interface LiveArguments extends InfoWindow.LiveArguments {269/** The action to be performed upon clicking the baloon. */270String getActionCommand();271}272273private final LiveArguments liveArguments;274private final Object target;275276private static final int BALLOON_SHOW_TIME = 10000;277private static final int BALLOON_TEXT_MAX_LENGTH = 256;278private static final int BALLOON_WORD_LINE_MAX_LENGTH = 16;279private static final int BALLOON_WORD_LINE_MAX_COUNT = 4;280private static final int BALLOON_ICON_WIDTH = 32;281private static final int BALLOON_ICON_HEIGHT = 32;282private static final int BALLOON_TRAY_ICON_INDENT = 0;283private static final Color BALLOON_CAPTION_BACKGROUND_COLOR = new Color(200, 200 ,255);284private static final Font BALLOON_CAPTION_FONT = new Font(Font.DIALOG, Font.BOLD, 12);285286private Panel mainPanel = new Panel();287private Panel captionPanel = new Panel();288private Label captionLabel = new Label("");289private Button closeButton = new Button("X");290private Panel textPanel = new Panel();291private XTrayIconPeer.IconCanvas iconCanvas = new XTrayIconPeer.IconCanvas(BALLOON_ICON_WIDTH, BALLOON_ICON_HEIGHT);292private Label[] lineLabels = new Label[BALLOON_WORD_LINE_MAX_COUNT];293private ActionPerformer ap = new ActionPerformer();294295private Image iconImage;296private Image errorImage;297private Image warnImage;298private Image infoImage;299private boolean gtkImagesLoaded;300301private Displayer displayer = new Displayer();302303public Balloon(Frame parent, Object target, LiveArguments liveArguments) {304super(parent, new Color(90, 80 ,190));305this.liveArguments = liveArguments;306this.target = target;307308XTrayIconPeer.suppressWarningString(this);309310setCloser(new Runnable() {311public void run() {312if (textPanel != null) {313textPanel.removeAll();314textPanel.setSize(0, 0);315iconCanvas.setSize(0, 0);316XToolkit.awtLock();317try {318displayer.isDisplayed = false;319XToolkit.awtLockNotifyAll();320} finally {321XToolkit.awtUnlock();322}323}324}325}, BALLOON_SHOW_TIME);326327add(mainPanel);328329captionLabel.setFont(BALLOON_CAPTION_FONT);330captionLabel.addMouseListener(ap);331332captionPanel.setLayout(new BorderLayout());333captionPanel.add(captionLabel, BorderLayout.WEST);334captionPanel.add(closeButton, BorderLayout.EAST);335captionPanel.setBackground(BALLOON_CAPTION_BACKGROUND_COLOR);336captionPanel.addMouseListener(ap);337338closeButton.addActionListener(new ActionListener() {339public void actionPerformed(ActionEvent e) {340hide();341}342});343344mainPanel.setLayout(new BorderLayout());345mainPanel.setBackground(Color.white);346mainPanel.add(captionPanel, BorderLayout.NORTH);347mainPanel.add(iconCanvas, BorderLayout.WEST);348mainPanel.add(textPanel, BorderLayout.CENTER);349350iconCanvas.addMouseListener(ap);351352for (int i = 0; i < BALLOON_WORD_LINE_MAX_COUNT; i++) {353lineLabels[i] = new Label();354lineLabels[i].addMouseListener(ap);355lineLabels[i].setBackground(Color.white);356}357358displayer.thread.start();359}360361public void display(String caption, String text, String messageType) {362if (!gtkImagesLoaded) {363loadGtkImages();364}365displayer.display(caption, text, messageType);366}367368private void _display(String caption, String text, String messageType) {369captionLabel.setText(caption);370371BreakIterator iter = BreakIterator.getWordInstance();372if (text != null) {373iter.setText(text);374int start = iter.first(), end;375int nLines = 0;376377do {378end = iter.next();379380if (end == BreakIterator.DONE ||381text.substring(start, end).length() >= 50)382{383lineLabels[nLines].setText(text.substring(start, end == BreakIterator.DONE ?384iter.last() : end));385textPanel.add(lineLabels[nLines++]);386start = end;387}388if (nLines == BALLOON_WORD_LINE_MAX_COUNT) {389if (end != BreakIterator.DONE) {390lineLabels[nLines - 1].setText(391new String(lineLabels[nLines - 1].getText() + " ..."));392}393break;394}395} while (end != BreakIterator.DONE);396397398textPanel.setLayout(new GridLayout(nLines, 1));399}400401if ("ERROR".equals(messageType)) {402iconImage = errorImage;403} else if ("WARNING".equals(messageType)) {404iconImage = warnImage;405} else if ("INFO".equals(messageType)) {406iconImage = infoImage;407} else {408iconImage = null;409}410411if (iconImage != null) {412Dimension tpSize = textPanel.getSize();413iconCanvas.setSize(BALLOON_ICON_WIDTH, (BALLOON_ICON_HEIGHT > tpSize.height ?414BALLOON_ICON_HEIGHT : tpSize.height));415iconCanvas.validate();416}417418SunToolkit.executeOnEventHandlerThread(target, new Runnable() {419public void run() {420if (liveArguments.isDisposed()) {421return;422}423Point parLoc = getParent().getLocationOnScreen();424Dimension parSize = getParent().getSize();425show(new Point(parLoc.x + parSize.width/2, parLoc.y + parSize.height/2),426BALLOON_TRAY_ICON_INDENT);427if (iconImage != null) {428iconCanvas.updateImage(iconImage); // call it after the show(..) above429}430}431});432}433434public void dispose() {435displayer.thread.interrupt();436super.dispose();437}438439private void loadGtkImages() {440if (!gtkImagesLoaded) {441//check whether the gtk version is >= 3.10 as the Icon names were442//changed from this release443UNIXToolkit tk = (UNIXToolkit) Toolkit.getDefaultToolkit();444if (tk.checkGtkVersion(3, 10, 0)) {445errorImage = (Image) tk.getDesktopProperty(446"gtk.icon.dialog-error.6.rtl");447warnImage = (Image) tk.getDesktopProperty(448"gtk.icon.dialog-warning.6.rtl");449infoImage = (Image) tk.getDesktopProperty(450"gtk.icon.dialog-information.6.rtl");451} else {452errorImage = (Image) tk.getDesktopProperty(453"gtk.icon.gtk-dialog-error.6.rtl");454warnImage = (Image) tk.getDesktopProperty(455"gtk.icon.gtk-dialog-warning.6.rtl");456infoImage = (Image) tk.getDesktopProperty(457"gtk.icon.gtk-dialog-info.6.rtl");458}459gtkImagesLoaded = true;460}461}462@SuppressWarnings("deprecation")463private class ActionPerformer extends MouseAdapter {464public void mouseClicked(MouseEvent e) {465// hide the balloon by any click466hide();467if (e.getButton() == MouseEvent.BUTTON1) {468ActionEvent aev = new ActionEvent(target, ActionEvent.ACTION_PERFORMED,469liveArguments.getActionCommand(),470e.getWhen(), e.getModifiers());471XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev);472}473}474}475476private class Displayer implements Runnable {477final int MAX_CONCURRENT_MSGS = 10;478479ArrayBlockingQueue<Message> messageQueue = new ArrayBlockingQueue<Message>(MAX_CONCURRENT_MSGS);480boolean isDisplayed;481final Thread thread;482483Displayer() {484this.thread = new Thread(null, this, "Displayer", 0, false);485this.thread.setDaemon(true);486}487488@Override489public void run() {490while (true) {491Message msg = null;492try {493msg = messageQueue.take();494} catch (InterruptedException e) {495return;496}497498/*499* Wait till the previous message is displayed if any500*/501XToolkit.awtLock();502try {503while (isDisplayed) {504try {505XToolkit.awtLockWait();506} catch (InterruptedException e) {507return;508}509}510isDisplayed = true;511} finally {512XToolkit.awtUnlock();513}514_display(msg.caption, msg.text, msg.messageType);515}516}517518void display(String caption, String text, String messageType) {519messageQueue.offer(new Message(caption, text, messageType));520}521}522523private static class Message {524String caption, text, messageType;525526Message(String caption, String text, String messageType) {527this.caption = caption;528this.text = text;529this.messageType = messageType;530}531}532}533}534535536537