/**
* 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;
}
}