/* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration) * and Cosylab 2002, All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ package com.cosylab.logging; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; /** * The widget reporting messages. * <P> * This widget is invisible and appears only when a message must be notified * to the user. * When the user presses the Ok button, the panel hides itself. * The method <code>removeError()</code> allows to hide the panel programmatically. * <P> * The look of the panel changes if a detailed description of the error is present. * If it is the case, the panel shows a title label on top. * If instead the detailed description is not defined, then the label and the detailed panel are hidden. * This allows to use the component as "light" or "heavy" by setting a detailed description. * Which of the two modality is in use is returned by the <code>boolean</code> of the two <code>showMessage(...)</code> methods. * * @author acaproni * */ public class MessageWidget extends JPanel { /** * The type of the message shown by the glass pane * * @author acaproni * */ public enum MessageType { Error("dialog-error.png"), Warning("dialog-warning.png"), Info("dialog-information.png"); /** * The icon shown for each type of dialog * */ public final ImageIcon icon; /** * Constructor * * @param iconPath */ private MessageType(String iconPath) { icon=new ImageIcon(MessageWidget.class.getResource("/"+iconPath)); } }; /** * The listener to be notified when the user acknowledge * the message by pressing the button * * @author acaproni * */ public interface MessageWidgetListener { public void errorAcknowledged(); } /** * The text describing the problem */ private final JLabel shortDescriptionLbl=new JLabel(); /** * The text to give a detailed description of the problem. */ private final JTextArea detailedDescritpionTA= new JTextArea(); /** * The panel with the detailed description of the problem. * <P> * This component appears only if it is not empty. */ private JScrollPane detailedDescPnl; /** * The icon */ private final JLabel iconLbl = new JLabel(); /** * The title of the error. * <P> * The title is displayed on top but only if the detailed description is present. */ private final JLabel titleLbl = new JLabel(); /** * The button to acknowledge the message */ private final JButton ackBtn = new JButton("Ok"); /** * The listeners to be notified when the user acknowledges the message */ private Collection<MessageWidgetListener> ackListeners=null; /** * Constructor * * @param client The {@link LoggingClient} that owns this glass pane */ public MessageWidget() { initialize(); setVisible(false); } /** * Init te GUI. */ private void initialize() { BorderLayout layout = new BorderLayout(); layout.setHgap(10); layout.setVgap(10); setLayout(layout); // The upper panel contains the icon, the message and the ack button JPanel upperPnl=new JPanel(new BorderLayout()); upperPnl.add(iconLbl,BorderLayout.WEST); upperPnl.add(shortDescriptionLbl,BorderLayout.CENTER); upperPnl.add(ackBtn,BorderLayout.EAST); // Add the title JPanel titlePnl = new JPanel(new FlowLayout(FlowLayout.CENTER)); titlePnl.add(titleLbl); upperPnl.add(titlePnl,BorderLayout.NORTH); ackBtn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { removeMessage(); notifyListeners(); } }); add(upperPnl,BorderLayout.NORTH); detailedDescPnl = new JScrollPane( detailedDescritpionTA, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); detailedDescPnl.setBorder(BorderFactory.createTitledBorder("Further details")); detailedDescritpionTA.setEditable(false); add(detailedDescPnl,BorderLayout.CENTER); } /** * Show the glass pane and catches all the events until the user acknowledges the message * by pressing the button. * <P> * The message type and the short description are mandatory to present to the user the * message. * A detailed description can also be given to better explain what's going on. It might be * a stack trace, for example. * * @param messageType The type of the message * @param shortDescription A short description of the message * @param description The detailed description; it can be <code>null</code> or empty. * @return <code>true</code> if the detailed description is not empty i.e. the description * scroll panel is visible */ public boolean showMessage(final MessageType messageType, final String shortDescription, final String description) { if (messageType==null) { throw new IllegalArgumentException("The type can't be null"); } if (shortDescription==null || shortDescription.isEmpty()) { throw new IllegalArgumentException("Invalid message descritpion"); } SwingUtilities.invokeLater(new Runnable() { public void run() { shortDescriptionLbl.setText(shortDescription); iconLbl.setIcon(messageType.icon); setVisible(true); if (description!=null && !description.isEmpty()) { detailedDescritpionTA.setText(description); detailedDescPnl.setVisible(true); titleLbl.setText("<HTML><FONT size=\"+1\">"+messageType+"</FONT></HTML>"); titleLbl.setVisible(true); } else { detailedDescritpionTA.setText(""); detailedDescPnl.setVisible(false); titleLbl.setVisible(false); } } }); return (description!=null && !description.isEmpty()); } /** * Show the glass pane and catches all the events until the user acknowledges the message * by pressing the button. * <P> * This method shows an error message, by setting the stack trace of the passed {@link Throwable} * in the detailed description of the error. * The throwable and the short description are mandatory to present to the user the * message. * * @param shortDescription A short description of the message * @param t The not <code>null</code> throwable to be displayed in the detailed text area. * @return <code>true</code> if the detailed description is not empty i.e. the description * scroll panel is visible */ public boolean showMessage(String shortDescription, Throwable t) { if (t==null) { throw new IllegalArgumentException("The throwable can't be null"); } StringWriter writer = new StringWriter(); PrintWriter printW = new PrintWriter(writer); printW.flush(); t.printStackTrace(printW); String desc=writer.getBuffer().toString(); printW.close(); return showMessage(MessageType.Error,shortDescription,desc); } /** * Hide the error panel. * <P> * This happens when the user presses the OK button but it might happen * when the abnormal situation has been fixed and therefore jlog has * no reason to ask for the attention of the user. * One example could be that of an automatic reconnection. */ public void removeMessage() { SwingUtilities.invokeLater(new Runnable() { public void run() { setVisible(false); } }); } public JButton getAckButton() { return ackBtn; } /** * Add a listener to be notified when the user presses the ack button. * * @param listener The listener to add to the list of listeners; * the listener is not added if it is already in the list */ public void addAckListener(MessageWidgetListener listener) { if (listener==null) { throw new IllegalArgumentException("The listener can't be null"); } if (ackListeners==null) { Vector<MessageWidgetListener> v = new Vector<MessageWidgetListener>(); ackListeners=Collections.synchronizedCollection(v); } if (!ackListeners.contains(listener)) { ackListeners.add(listener); } } /** * Remove a listener from the listeners to be notified when the user * acknowledges the message. * If the listener is not in the list, does nothing. * * @param listener The listener to remove from the list of listeners; * @return <code>true</code> if an element was removed as a result of this call */ public boolean removeAckListener(MessageWidgetListener listener) { if (listener==null) { throw new IllegalArgumentException("The listener can't be null"); } if (ackListeners==null) { return false; } return ackListeners.remove(listener); } /** * Notify all the listeners that the user acknowledged the message */ private void notifyListeners() { if (ackListeners==null) { return; } Iterator<MessageWidgetListener> iter = ackListeners.iterator(); while (iter.hasNext()) { iter.next().errorAcknowledged(); } } }