/** * MessageBay.java * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.expeditee.gui; import java.util.LinkedList; import java.util.List; import org.expeditee.Util; import org.expeditee.actions.Misc; import org.expeditee.auth.AuthenticatorBrowser; import org.expeditee.core.Clip; import org.expeditee.core.Colour; import org.expeditee.core.Dimension; import org.expeditee.core.Font; import org.expeditee.core.Image; import org.expeditee.encryption.items.surrogates.Label; import org.expeditee.gio.EcosystemManager; import org.expeditee.gio.GraphicsManager; import org.expeditee.items.Item; import org.expeditee.items.Text; import org.expeditee.settings.UserSettings; /** * The bay at the bottom of the expeditee browser which displays messages. TODO: * Make it thread safe! */ public final class MessageBay { /** The distance from the top of the message bay to the Message frame link. */ private static final int MESSAGE_LINK_Y_OFFSET = 100; /** TODO: Comment. cts16 */ private static final int MESSAGE_LINK_X = 50; /** TODO: Comment. cts16 */ public static final Colour ERROR_COLOR = Colour.RED; /** TODO: Comment. cts16 */ public static final String MESSAGES_FRAMESET_NAME = "Messages"; /** The list of messages shown in the message bay. */ private static List _messages = new LinkedList(); /** * Messages which were delayed because they couldn't be shown at time of * creation. */ private static List _delayedMessages = new LinkedList(); /** TODO: Comment. cts16 */ private static Text _status = null; private static Text _authorisedUser = null; private static Text _surrogateMode = null; /** Buffer image of the message window. */ private static Image _messageBuffer = null; /** Creator for creating the message frames. */ private static FrameCreator _creator = null; /** The user we are providing messages for **/ private static String _forUser = null; /** Font used for the messages. */ private static Font _messageFont = new Font("Serif-Plain-16"); /** The number of messages currently shown (used for scrolling up). */ private static int _messageCount = 0; /** If true, error messages are not shown to the user. */ private static boolean _suppressMessages = false; /** The link to the message frameset. */ private static Item _messageLink = new Text(-2, "@" + MESSAGES_FRAMESET_NAME, Colour.BLACK, Colour.WHITE); /** TODO: Comment. cts16 */ private static String _lastMessage = null; /** TODO: Comment. cts16 */ private static boolean isLinkInitialized = false; /** Static-only class. */ private MessageBay() { } /** Whether the message bay is ready to display messages. */ public static boolean isReady() { return Browser.isInitComplete(); } /** Syncs message bay size according to FrameGraphics max size. */ private static void updateSize() { for (Item i : _messages) { if (i != null) { i.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); } } _messageLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); updateLink(); } /** Whether the given item is an item in the message bay. */ public static boolean isMessageItem(Item i) { return _messages.contains(i) || i == _messageLink || i == getStatus(); } /** TODO: Comment. cts16 */ public synchronized static Item getMessageLink() { return _messageLink; } /** TODO: Comment. cts16 */ public synchronized static List getMessages() { return _messages; } /** Causes the entire message bay area to be invalidated. */ public synchronized static void invalidateFullBay() { DisplayController.invalidateArea(DisplayController.getMessageBayPaintArea()); } /** TODO: Comment. cts16 */ private static void updateLink() { if (!isLinkInitialized && DisplayController.getFramePaintArea() != null && DisplayController.getFramePaintAreaWidth() > 0) { // set up 'Messages' link on the right hand side _messageLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MESSAGE_LINK_Y_OFFSET, MESSAGE_LINK_X); _messageLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); isLinkInitialized = true; } else { _messageLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MESSAGE_LINK_Y_OFFSET, MESSAGE_LINK_X); } } /** TODO: Comment. cts16 */ public static Image getImage(Clip clip, Dimension size) { // Can't get an image with an invalid size if (size == null || size.width <= 0 || size.height <= 0) { return null; } // Update the buffer updateBuffer(Item.DEFAULT_BACKGROUND, clip, size); // Return the image buffer return _messageBuffer; } public static void clear() { MessageBay._messageCount = 0; MessageBay._messages.clear(); } /** Updates the image buffer to reflect the current state of the message bay. */ private synchronized static void updateBuffer(Colour background, Clip clip, Dimension size) { // If the buffer doesn't exist or is the wrong size, recreate it if (_messageBuffer == null || !_messageBuffer.getSize().equals(size)) { _messageBuffer = Image.createImage(size, true); clip = null; // Need to recreate the entire image; updateSize(); } GraphicsManager g = EcosystemManager.getGraphicsManager(); g.pushDrawingSurface(_messageBuffer); if (clip != null) { g.pushClip(clip); } g.setAntialiasing(true); g.clear(background); g.setFont(_messageFont); for (Item message : _messages) { if (message != null) { if (clip == null || clip.isNotClipped() || message.isInDrawingArea(clip.getBounds())) { FrameGraphics.PaintItem(message); } } } if (getStatus() != null) { FrameGraphics.PaintItem(getStatus()); } if (AuthenticatorBrowser.isAuthenticated() && _authorisedUser != null) { FrameGraphics.PaintItem(_authorisedUser); } if (Label.isInSurrogateMode() && _surrogateMode != null) { FrameGraphics.PaintItem(_surrogateMode); } if (clip == null || clip.isNotClipped() || _messageLink.isInDrawingArea(clip.getBounds())) { FrameGraphics.PaintItem(_messageLink); } g.popDrawingSurface(); } /** TODO: Comment. cts16 */ private static Text displayMessage(String message, String link, List actions, Colour color) { return displayMessage(message, link, actions, color, true); } /** TODO: Comment. cts16 */ public synchronized static Text displayMessage(String message, String link, Colour color, boolean displayAlways, String action) { List actions = new LinkedList(); if (action != null) { actions.add(action); } return displayMessage(message, link, actions, color, displayAlways); } public static void updateFramesetLocation() { if (_forUser != UserSettings.UserName.get()) { if (AuthenticatorBrowser.isAuthenticated()) { _creator = new FrameCreator(MESSAGES_FRAMESET_NAME, FrameIO.MESSAGES_PATH, MESSAGES_FRAMESET_NAME, FrameCreator.ExistingFramesetOptions.OverrideExistingFrames, false, AuthenticatorBrowser.PROFILEENCRYPTIONLABEL); } else { _creator = new FrameCreator(MESSAGES_FRAMESET_NAME, FrameIO.MESSAGES_PATH, MESSAGES_FRAMESET_NAME, FrameCreator.ExistingFramesetOptions.OverrideExistingFrames, false, null); } _forUser = UserSettings.UserName.get(); } } /** TODO: Comment. cts16 */ private static Text newMessage(String message, String link, List actions, Colour color) { Text t = new Text(getMessagePrefix(true) + message); t.setPosition(20, 15 + _messages.size() * 25); t.setOffset(0, -DisplayController.getFramePaintAreaHeight()); t.setColor(color); t.setLink(link); t.setActions(actions); t.setFont(_messageFont.clone()); _creator.addItem(t.copy(), true); if (link == null) { t.setLink(_creator.getCurrent()); } return t; } /** TODO: Comment. cts16 */ private synchronized static Text displayMessage(String message, String link, List actions, Colour color, boolean displayAlways, boolean redraw) { assert (message != null); if (!isReady()) { delayMessage(message, link, actions, color, displayAlways, redraw); return null; } System.out.println(message); // Invalidate whole area invalidateFullBay(); if (_suppressMessages) { return null; } if (!displayAlways && message.equals(_lastMessage)) { Item lastMessage = _messages.get(_messages.size() - 1); String text = lastMessage.getText(); if (text.endsWith("}")) { int startOfRepeat = text.lastIndexOf("{"); String repeatString = text.substring(startOfRepeat); repeatString = repeatString.substring(1, repeatString.length() - 1); try { int repeatCount = Integer.parseInt(repeatString) + 1; text = text.substring(0, startOfRepeat).trim() + " {" + repeatCount + "}"; } catch (NumberFormatException e) { e.printStackTrace(); } } else { text = text.trim() + " {2}"; } lastMessage.setText(text); DisplayController.DisableMailMode(); return null; } _lastMessage = message; if (_creator == null) { if (AuthenticatorBrowser.isAuthenticated()) { _creator = new FrameCreator(MESSAGES_FRAMESET_NAME, FrameIO.MESSAGES_PATH, MESSAGES_FRAMESET_NAME, FrameCreator.ExistingFramesetOptions.OverrideExistingFrames, false, AuthenticatorBrowser.PROFILEENCRYPTIONLABEL); } else { _creator = new FrameCreator(MESSAGES_FRAMESET_NAME, FrameIO.MESSAGES_PATH, MESSAGES_FRAMESET_NAME, FrameCreator.ExistingFramesetOptions.OverrideExistingFrames, false, null); } _forUser = UserSettings.UserName.get(); } // set up 'Messages' link on the right hand side updateLink(); if (_messages.size() >= 3) { _messages.remove(0); for (Item i : _messages) { i.setY(i.getY() - 25); } } Text t = newMessage(message, link, actions, color); _messages.add(t); // update the link to the latest message frame _messageLink.setLink(_creator.getCurrent()); if (redraw) { DisplayController.DisableMailMode(); DisplayController.requestRefresh(true); } return t; } /** TODO: Comment. cts16 */ public synchronized static Text displayMessage(String message, String link, List actions, Colour color, boolean displayAlways) { return displayMessage(message, link, actions, color, displayAlways, true); } /** TODO: Comment. cts16 */ public synchronized static void overwriteMessage(String message) { overwriteMessage(message, null); } /** TODO: Comment. cts16 */ public synchronized static void overwriteMessage(String message, Colour color) { _messages.remove(_messages.size() - 1); Text t = newMessage(message, null, null, color); _messages.add(t); DisplayController.requestRefresh(true); } /** TODO: Comment. cts16 */ private static String getMessagePrefix(int counter) { return "@" + counter + ": "; } /** TODO: Comment. cts16 */ private static String getMessagePrefix(boolean incrementCounter) { if (incrementCounter) { _messageCount++; } return getMessagePrefix(_messageCount); } /** * Checks if the error message ends with a frame name after the * frameNameSeparator symbol * * @param message * the message to be displayed */ public synchronized static Text linkedErrorMessage(String message) { if (_suppressMessages) { return null; } Misc.beep(); String[] tokens = message.split(Text.FRAME_NAME_SEPARATOR); String link = null; if (tokens.length > 1) { link = tokens[tokens.length - 1]; } return displayMessage(message, link, null, ERROR_COLOR); } /** TODO: Comment. cts16 */ public synchronized static Text errorMessage(String message) { if (_suppressMessages) { return null; } Misc.beep(); return displayMessage(message, null, null, ERROR_COLOR, false); } /** * Displays the given message in the message area of the Frame, any previous * message is cleared from the screen. * * @param message * The message to display to the user in the message area */ public synchronized static Text displayMessage(String message) { return displayMessageAlways(message); } /** TODO: Comment. cts16 */ public synchronized static Text displayMessageOnce(String message) { return displayMessage(message, null, null, Colour.BLACK, false); } /** TODO: Comment. cts16 */ public synchronized static Text displayMessage(String message, Colour textColor) { return displayMessage(message, null, null, textColor); } /** TODO: Comment. cts16 */ public synchronized static Text displayMessage(Text message) { Text t = null; String link = message.getLink(); List action = message.getAction(); Colour color = message.getColor(); for (String s : message.getTextList()) { t = displayMessage(s, link, action, color); } return t; } /** TODO: Comment. cts16 */ public synchronized static Text displayMessageAlways(String message) { return displayMessage(message, null, null, Colour.BLACK); // Misc.Beep(); } /** TODO: Comment. cts16 */ public synchronized static Text warningMessage(String message) { return displayMessage(message, null, null, Colour.MAGENTA); } /** TODO: Comment. cts16 */ public synchronized static List warningMessages(List messages) { if (messages == null) { return null; } List ret = new LinkedList(); for (String message : messages) { ret.add(warningMessage(message)); } return ret; } /** TODO: Comment. cts16 */ public synchronized static void suppressMessages(boolean val) { _suppressMessages = val; } /** TODO: Comment. cts16 */ public synchronized static void setStatus(String status) { if (_status == null) { _status = new Text(status); _status.setPosition(0, 85); _status.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY()); _status.setLink(null); // maybe link to a help frame? _status.setFont(new Font(Text.MONOSPACED_FONT)); } else { _status.setText(status); } _authorisedUser = new Text("Username: " + System.getProperty("user.name")); _authorisedUser.setFont(new Font(Text.MONOSPACED_FONT)); _authorisedUser.setY(95); _authorisedUser.setAnchorRight(1); if (Label.isInSurrogateMode()) { String surrogateModeString = Label.surrogateModeString(); _surrogateMode = new Text(surrogateModeString); _surrogateMode.setFont(new Font(Text.MONOSPACED_FONT)); _surrogateMode.setY(75); _surrogateMode.setAnchorRight(1); } else { if (_surrogateMode != null) { _surrogateMode.setVisible(false); } } // invalidateFullBay(); DisplayController.requestRefresh(true); } /** TODO: Comment. cts16 */ public static final class Progress { /** The colour progress bars should be displayed in. */ private static final Colour BAR_COLOUR = Colour.GREEN.darker(); /** * The character used to assemble the uncompleted portions of the progress bar. */ private static final char UNCOMPLETED_CHARACTER = '\u2591'; // ░ /** * The character used to assemble the completed portions of the progress bar. */ private static final char COMPLETED_CHARACTER = '\u2592'; // ▒ /** What the progress bar should look like when at 100% completion. */ private static final String COMPLETED_BAR = Util.nCopiesOf(20, COMPLETED_CHARACTER); /** What the progress bar should look like when at 0% completion. */ private static final String UNCOMPLETED_BAR = Util.nCopiesOf(20, UNCOMPLETED_CHARACTER); private String _message; private Text _text; protected Progress(String text) { this._text = displayMessage(text, null, null, BAR_COLOUR, true, false); this._message = this._text.getText(); this._text.setText(this._message + " [" + UNCOMPLETED_BAR + "] 0%"); DisplayController.requestRefresh(true); } public void UpdateMessage(final String text, final int newProgress) throws Exception { this._message = text; set(newProgress); } public String GetMessage() { return _message; } /** * * @param progress * progress value from 0 to 100 * @return true if the progress was updated, false if the progress was off the * screen * @throws Exception * if progress out of bounds */ public boolean set(int progress) throws Exception { if (progress < 0 || progress > 100) { throw new Exception("Progress value out of bounds"); } int p = progress / 5; if (isMessageItem(this._text)) { this._text.setText(this._message + " [" + COMPLETED_BAR.substring(0, p) + UNCOMPLETED_BAR.substring(p) + "] " + progress + "%"); DisplayController.requestRefresh(true); return true; } return false; } } /** TODO: Comment. cts16 */ public synchronized static Progress displayProgress(String message) { return new Progress(message); } /** Remembers the arguments to a displayMessage call for later use. */ private static class DelayedMessage { private String _message; private String _link; private List _actions; private Colour _colour; private boolean _displayAlways; private boolean _redraw; public DelayedMessage(String message, String link, List actions, Colour color, boolean displayAlways, boolean redraw) { _message = message; _link = link; if (actions == null) { _actions = null; } else { _actions = new LinkedList(); _actions.addAll(actions); } _colour = color == null ? null : color.clone(); _displayAlways = displayAlways; _redraw = redraw; } public void display() { displayMessage(_message, _link, _actions, _colour, _displayAlways, _redraw); } } private static void delayMessage(String message, String link, List actions, Colour color, boolean displayAlways, boolean redraw) { _delayedMessages.add(new DelayedMessage(message, link, actions, color, displayAlways, redraw)); } public static void showDelayedMessages(final boolean requestRefresh) { if (isReady()) { for (DelayedMessage message : _delayedMessages) { message.display(); } _delayedMessages.clear(); invalidateFullBay(); if(requestRefresh) { DisplayController.requestRefresh(true); } } } public static Text getStatus() { return _status; } }