source: trunk/src/org/expeditee/gui/MessageBay.java@ 1102

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

File size: 15.4 KB
RevLine 
[919]1/**
2 * MessageBay.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
[122]19package org.expeditee.gui;
20
21import java.util.LinkedList;
22import java.util.List;
23
[1102]24import org.expeditee.Util;
[122]25import org.expeditee.actions.Misc;
[1102]26import org.expeditee.core.Clip;
27import org.expeditee.core.Colour;
28import org.expeditee.core.Dimension;
29import org.expeditee.core.Font;
30import org.expeditee.core.Image;
31import org.expeditee.gio.EcosystemManager;
32import org.expeditee.gio.GraphicsManager;
[122]33import org.expeditee.items.Item;
34import org.expeditee.items.Text;
35
36/**
[1102]37 * The bay at the bottom of the expeditee browser which displays messages.
38 * TODO: Make it thread safe!
[122]39 */
40public final class MessageBay {
[130]41
[1102]42 /** The distance from the top of the message bay to the Message frame link. */
[122]43 private static final int MESSAGE_LINK_Y_OFFSET = 100;
44
[1102]45 /** TODO: Comment. cts16 */
[122]46 private static final int MESSAGE_LINK_X = 50;
[130]47
[1102]48 /** TODO: Comment. cts16 */
49 public static final Colour ERROR_COLOR = Colour.RED;
[130]50
[1102]51 /** TODO: Comment. cts16 */
[122]52 public static final String MESSAGES_FRAMESET_NAME = "Messages";
53
[1102]54 /** The list of messages shown in the message bay. */
[689]55 private static List<Item> _messages = new LinkedList<Item>();
[1102]56
57 /** Messages which were delayed because they couldn't be shown at time of creation. */
58 private static List<DelayedMessage> _delayedMessages = new LinkedList<DelayedMessage>();
59
60 /** TODO: Comment. cts16 */
[673]61 private static Text _status = null;
[122]62
[1102]63 /** Buffer image of the message window. */
64 private static Image _messageBuffer = null;
[130]65
[1102]66 /** Creator for creating the message frames. */
[122]67 private static FrameCreator _creator = null;
68
[1102]69 /** Font used for the messages. */
70 private static Font _messageFont = new Font("Serif-Plain-16");
[122]71
[1102]72 /** The number of messages currently shown (used for scrolling up). */
[122]73 private static int _messageCount = 0;
[130]74
[1102]75 /** If true, error messages are not shown to the user. */
[689]76 private static boolean _suppressMessages = false;
[122]77
[1102]78 /** The link to the message frameset. */
79 private static Item _messageLink = new Text(-2, "@" + MESSAGES_FRAMESET_NAME, Colour.BLACK, Colour.WHITE);
[122]80
[1102]81 /** TODO: Comment. cts16 */
[122]82 private static String _lastMessage = null;
83
[1102]84 /** TODO: Comment. cts16 */
85 private static boolean isLinkInitialized = false;
86
87 /** Static-only class. */
88 private MessageBay()
89 {
[130]90 }
[1102]91
92 /** Whether the message bay is ready to display messages. */
93 public static boolean isReady()
94 {
95 return Browser.isInitComplete();
96 }
[130]97
[1102]98 /** Syncs message bay size according to FrameGraphics max size. */
99 static void updateSize()
100 {
[689]101 for(Item i : _messages) {
102 if(i != null) {
[1102]103 i.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
[122]104 }
105 }
106
[1102]107 _messageLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
108
[125]109 updateLink();
[122]110 }
[130]111
[1102]112 /** Whether the given item is an item in the message bay. */
113 public static boolean isMessageItem(Item i)
114 {
115 return _messages.contains(i) || i == _messageLink || i == _status;
[122]116 }
[130]117
[1102]118 /** TODO: Comment. cts16 */
119 public synchronized static Item getMessageLink()
120 {
[122]121 return _messageLink;
122 }
[130]123
[1102]124 /** TODO: Comment. cts16 */
125 public synchronized static List<Item> getMessages()
126 {
[122]127 return _messages;
128 }
[130]129
[1102]130 /** Causes the entire message bay area to be invalidated. */
131 public synchronized static void invalidateFullBay()
132 {
133 DisplayController.invalidateArea(DisplayController.getMessageBayPaintArea());
[125]134 }
[130]135
[1102]136 /** TODO: Comment. cts16 */
137 private static void updateLink()
138 {
139 if (!isLinkInitialized && DisplayController.getFramePaintArea().getWidth() > 0) {
[125]140 // set up 'Messages' link on the right hand side
[1102]141 _messageLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MESSAGE_LINK_Y_OFFSET, MESSAGE_LINK_X);
142 _messageLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
[125]143 isLinkInitialized = true;
144 } else {
[1102]145 _messageLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MESSAGE_LINK_Y_OFFSET, MESSAGE_LINK_X);
[125]146 }
147 }
148
[1102]149 /** TODO: Comment. cts16 */
150 public static Image getImage(Clip clip, Dimension size)
151 {
152 // Can't get an image with an invalid size
153 if (size == null || size.width <= 0 || size.height <= 0) return null;
[130]154
[1102]155 // Update the buffer
156 updateBuffer(Item.DEFAULT_BACKGROUND, clip, size);
[689]157
[1102]158 // Return the image buffer
159 return _messageBuffer;
160 }
[130]161
[1102]162 /** Updates the image buffer to reflect the current state of the message bay. */
163 private static void updateBuffer(Colour background, Clip clip, Dimension size)
164 {
165 // If the buffer doesn't exist or is the wrong size, recreate it
166 if (_messageBuffer == null || !_messageBuffer.getSize().equals(size)) {
167 _messageBuffer = Image.createImage(size, true);
168 clip = null; // Need to recreate the entire image;
169 updateSize();
[122]170 }
171
[1102]172 GraphicsManager g = EcosystemManager.getGraphicsManager();
173 g.pushDrawingSurface(_messageBuffer);
[122]174
[1102]175 if (clip != null) g.pushClip(clip);
176 g.setAntialiasing(true);
177
178 g.clear(background);
179
[122]180 g.setFont(_messageFont);
[1102]181
182 for (Item message : _messages) {
183 if (message != null) {
184 if (clip == null || clip.isNotClipped() || message.isInDrawingArea(clip.getBounds())) {
185 FrameGraphics.PaintItem(message);
186 }
187 }
[122]188 }
[1102]189
190 if (_status != null) FrameGraphics.PaintItem(_status);
[130]191
[1102]192 if (clip == null || clip.isNotClipped() || _messageLink.isInDrawingArea(clip.getBounds())) {
193 FrameGraphics.PaintItem(_messageLink);
[125]194 }
[1102]195
196 g.popDrawingSurface();
[122]197 }
[130]198
[1102]199 /** TODO: Comment. cts16 */
200 private static Text displayMessage(String message, String link, List<String> actions, Colour color)
201 {
[284]202 return displayMessage(message, link, actions, color, true);
[122]203 }
[336]204
[1102]205 /** TODO: Comment. cts16 */
206 public synchronized static Text displayMessage(String message, String link, Colour color, boolean displayAlways, String action)
207 {
[284]208 List<String> actions = new LinkedList<String>();
[1102]209 if (action != null) actions.add(action);
[284]210 return displayMessage(message, link, actions, color, displayAlways);
211 }
[1102]212
213 /** TODO: Comment. cts16 */
214 private static Text newMessage(String message, String link, List<String> actions, Colour color)
215 {
[689]216 Text t = new Text(getMessagePrefix(true) + message);
217 t.setPosition(20, 15 + _messages.size() * 25);
[1102]218 t.setOffset(0, -DisplayController.getFramePaintArea().getHeight());
[689]219 t.setColor(color);
220 t.setLink(link);
221 t.setActions(actions);
222 t.setFont(_messageFont);
223 _creator.addItem(t.copy(), true);
224 if(link == null) t.setLink(_creator.getCurrent());
225 return t;
226 }
[1102]227
228 /** TODO: Comment. cts16 */
229 private synchronized static Text displayMessage(String message, String link, List<String> actions, Colour color, boolean displayAlways, boolean redraw)
230 {
231 assert (message != null);
232
233 if (!isReady()) {
234 delayMessage(message, link, actions, color, displayAlways, redraw);
235 return null;
236 }
237
[122]238 System.out.println(message);
[130]239
[125]240 // Invalidate whole area
241 invalidateFullBay();
[130]242
[1102]243 if (_suppressMessages) return null;
[122]244
245 if (!displayAlways && message.equals(_lastMessage)) {
[181]246 Misc.beep();
[284]247 return null;
[122]248 }
[1102]249
[122]250 _lastMessage = message;
251
252 if (_creator == null) {
[1102]253 _creator = new FrameCreator(MESSAGES_FRAMESET_NAME, FrameIO.MESSAGES_PATH, MESSAGES_FRAMESET_NAME, true, false);
[122]254 }
[130]255
[125]256 // set up 'Messages' link on the right hand side
257 updateLink();
[122]258
[689]259 if(_messages.size() >= 3) {
260 _messages.remove(0);
261 for(Item i : _messages) {
262 i.setY(i.getY() - 25);
[122]263 }
264 }
[689]265
266 Text t = newMessage(message, link, actions, color);
267
268 _messages.add(t);
269
[122]270 // update the link to the latest message frame
271 _messageLink.setLink(_creator.getCurrent());
[130]272
[1102]273 // TODO: Can we just make this DisplayController.requestRefresh()? cts16
[689]274 if(redraw) {
[1102]275 DisplayController.requestRefresh(true);
[649]276 }
[336]277
[689]278 return t;
[122]279 }
[130]280
[1102]281 /** TODO: Comment. cts16 */
282 public synchronized static Text displayMessage(String message, String link, List<String> actions, Colour color, boolean displayAlways)
283 {
[689]284 return displayMessage(message, link, actions, color, displayAlways, true);
285 }
286
[1102]287 /** TODO: Comment. cts16 */
288 public synchronized static void overwriteMessage(String message)
289 {
[247]290 overwriteMessage(message, null);
291 }
[336]292
[1102]293 /** TODO: Comment. cts16 */
294 public synchronized static void overwriteMessage(String message, Colour color)
295 {
[806]296 _messages.remove(_messages.size() - 1);
[689]297 Text t = newMessage(message, null, null, color);
298 _messages.add(t);
[1102]299 DisplayController.requestRefresh(true);
[122]300 }
[1102]301
302 /** TODO: Comment. cts16 */
303 private static String getMessagePrefix(int counter)
304 {
[689]305 return "@" + counter + ": ";
306 }
[122]307
[1102]308 /** TODO: Comment. cts16 */
309 private static String getMessagePrefix(boolean incrementCounter)
310 {
311 if (incrementCounter) _messageCount++;
312
[689]313 return getMessagePrefix(_messageCount);
[122]314 }
315
316 /**
317 * Checks if the error message ends with a frame name after the
318 * frameNameSeparator symbol
319 *
320 * @param message
321 * the message to be displayed
322 */
[1102]323 public synchronized static Text linkedErrorMessage(String message)
324 {
325 if (_suppressMessages) return null;
[181]326 Misc.beep();
[122]327 String[] tokens = message.split(Text.FRAME_NAME_SEPARATOR);
328 String link = null;
[1102]329 if (tokens.length > 1) link = tokens[tokens.length - 1];
[284]330 return displayMessage(message, link, null, ERROR_COLOR);
[122]331 }
332
[1102]333 /** TODO: Comment. cts16 */
334 public synchronized static Text errorMessage(String message)
335 {
336 if (_suppressMessages) return null;
[181]337 Misc.beep();
[284]338 return displayMessage(message, null, null, ERROR_COLOR, false);
[122]339 }
340
341 /**
342 * Displays the given message in the message area of the Frame, any previous
343 * message is cleared from the screen.
344 *
345 * @param message
346 * The message to display to the user in the message area
347 */
[1102]348 public synchronized static Text displayMessage(String message)
349 {
[284]350 return displayMessageAlways(message);
[122]351 }
352
[1102]353 /** TODO: Comment. cts16 */
354 public synchronized static Text displayMessageOnce(String message)
355 {
356 return displayMessage(message, null, null, Colour.BLACK, false);
[122]357 }
358
[1102]359 /** TODO: Comment. cts16 */
360 public synchronized static Text displayMessage(String message, Colour textColor)
361 {
[284]362 return displayMessage(message, null, null, textColor);
[122]363 }
364
[1102]365 /** TODO: Comment. cts16 */
366 public synchronized static Text displayMessage(Text message)
367 {
[336]368 Text t = null;
369 String link = message.getLink();
370 List<String> action = message.getAction();
[1102]371 Colour color = message.getColor();
[336]372 for (String s : message.getTextList()) {
373 t = displayMessage(s, link, action, color);
374 }
375 return t;
[122]376 }
377
[1102]378 /** TODO: Comment. cts16 */
379 public synchronized static Text displayMessageAlways(String message)
380 {
381 return displayMessage(message, null, null, Colour.BLACK);
[122]382 // Misc.Beep();
383 }
384
[1102]385 /** TODO: Comment. cts16 */
386 public synchronized static Text warningMessage(String message)
387 {
388 return displayMessage(message, null, null, Colour.MAGENTA);
[122]389 }
[130]390
[1102]391 /** TODO: Comment. cts16 */
392 public synchronized static List<Text> warningMessages(List<String> messages)
393 {
394 if (messages == null) return null;
395 List<Text> ret = new LinkedList<Text>();
396 for (String message : messages) ret.add(warningMessage(message));
397 return ret;
398 }
399
400 /** TODO: Comment. cts16 */
401 public synchronized static void suppressMessages(boolean val)
402 {
[689]403 _suppressMessages = val;
[122]404 }
[1102]405
406 /** TODO: Comment. cts16 */
407 public synchronized static void setStatus(String status)
408 {
[673]409 if (_status == null) {
410 _status = new Text(status);
[678]411 _status.setPosition(0, 85);
[1102]412 _status.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
[673]413 _status.setLink(null); // maybe link to a help frame?
[1102]414 _status.setFont(new Font(Text.MONOSPACED_FONT));
[673]415 } else {
416 _status.setText(status);
417 }
[1102]418
419 //invalidateFullBay();
420 DisplayController.requestRefresh(true);
[673]421 }
[1102]422
423 /** TODO: Comment. cts16 */
424 public static final class Progress
425 {
426 /** The colour progress bars should be displayed in. */
427 private static final Colour BAR_COLOUR = Colour.GREEN.darker();
[689]428
[1102]429 /** The character used to assemble the uncompleted portions of the progress bar. */
430 private static final char UNCOMPLETED_CHARACTER = '\u2591'; // ░
431 /** The character used to assemble the completed portions of the progress bar. */
432 private static final char COMPLETED_CHARACTER = '\u2592'; // ▒
[689]433
[1102]434 /** What the progress bar should look like when at 100% completion. */
435 private static final String COMPLETED_BAR = Util.nCopiesOf(20, COMPLETED_CHARACTER);
436 /** What the progress bar should look like when at 0% completion. */
437 private static final String UNCOMPLETED_BAR = Util.nCopiesOf(20, UNCOMPLETED_CHARACTER);
[689]438
[1102]439 private String _message;
440 private Text _text;
441
442 protected Progress(String text)
443 {
444 this._text = displayMessage(text, null, null, BAR_COLOUR, true, false);
445 this._message = this._text.getText();
446 this._text.setText(this._message + " [" + UNCOMPLETED_BAR + "] 0%");
447 DisplayController.requestRefresh(true);
[689]448 }
449
[1102]450 public void UpdateMessage(final String text, final int newProgress) throws Exception
451 {
452 this._message = text;
[929]453 set(newProgress);
454 }
455
[1102]456 public String GetMessage()
457 {
458 return _message;
[929]459 }
460
[689]461 /**
462 *
463 * @param progress progress value from 0 to 100
464 * @return true if the progress was updated, false if the progress was off the screen
465 * @throws Exception if progress out of bounds
466 */
[1102]467 public boolean set(int progress) throws Exception
468 {
[689]469 if(progress < 0 || progress > 100) throw new Exception("Progress value out of bounds");
470 int p = progress / 5;
[1102]471 if(isMessageItem(this._text)) {
472 this._text.setText(this._message + " [" + COMPLETED_BAR.substring(0, p) + UNCOMPLETED_BAR.substring(p) + "] " + progress + "%");
473 DisplayController.requestRefresh(true);
[689]474 return true;
475 }
476 return false;
477 }
478 }
[1102]479
480 /** TODO: Comment. cts16 */
481 public synchronized static Progress displayProgress(String message)
482 {
[689]483 return new Progress(message);
484 }
[1102]485
486 /** Remembers the arguments to a displayMessage call for later use. */
487 private static class DelayedMessage {
488
489 private String _message;
490 private String _link;
491 private List<String> _actions;
492 private Colour _colour;
493 private boolean _displayAlways;
494 private boolean _redraw;
495
496 public DelayedMessage(String message, String link, List<String> actions, Colour color, boolean displayAlways, boolean redraw)
497 {
498 _message = message;
499 _link = link;
500 if (actions == null) {
501 _actions = null;
502 } else {
503 _actions = new LinkedList<String>();
504 _actions.addAll(actions);
505 }
506 _colour = color == null ? null : color.clone();
507 _displayAlways = displayAlways;
508 _redraw = redraw;
509 }
510
511 public void display()
512 {
513 displayMessage(_message, _link, _actions, _colour, _displayAlways, _redraw);
514 }
515
516 }
517
518 private static void delayMessage(String message, String link, List<String> actions, Colour color, boolean displayAlways, boolean redraw)
519 {
520 _delayedMessages.add(new DelayedMessage(message, link, actions, color, displayAlways, redraw));
521 }
522
523 public static void showDelayedMessages()
524 {
525 if (isReady()) {
526 for (DelayedMessage message : _delayedMessages) message.display();
527 _delayedMessages.clear();
528 invalidateFullBay();
529 DisplayController.requestRefresh(true);
530 }
531 }
[130]532
[122]533}
Note: See TracBrowser for help on using the repository browser.