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
Line 
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
19package org.expeditee.gui;
20
21import java.util.LinkedList;
22import java.util.List;
23
24import org.expeditee.Util;
25import org.expeditee.actions.Misc;
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;
33import org.expeditee.items.Item;
34import org.expeditee.items.Text;
35
36/**
37 * The bay at the bottom of the expeditee browser which displays messages.
38 * TODO: Make it thread safe!
39 */
40public final class MessageBay {
41
42 /** The distance from the top of the message bay to the Message frame link. */
43 private static final int MESSAGE_LINK_Y_OFFSET = 100;
44
45 /** TODO: Comment. cts16 */
46 private static final int MESSAGE_LINK_X = 50;
47
48 /** TODO: Comment. cts16 */
49 public static final Colour ERROR_COLOR = Colour.RED;
50
51 /** TODO: Comment. cts16 */
52 public static final String MESSAGES_FRAMESET_NAME = "Messages";
53
54 /** The list of messages shown in the message bay. */
55 private static List<Item> _messages = new LinkedList<Item>();
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 */
61 private static Text _status = null;
62
63 /** Buffer image of the message window. */
64 private static Image _messageBuffer = null;
65
66 /** Creator for creating the message frames. */
67 private static FrameCreator _creator = null;
68
69 /** Font used for the messages. */
70 private static Font _messageFont = new Font("Serif-Plain-16");
71
72 /** The number of messages currently shown (used for scrolling up). */
73 private static int _messageCount = 0;
74
75 /** If true, error messages are not shown to the user. */
76 private static boolean _suppressMessages = false;
77
78 /** The link to the message frameset. */
79 private static Item _messageLink = new Text(-2, "@" + MESSAGES_FRAMESET_NAME, Colour.BLACK, Colour.WHITE);
80
81 /** TODO: Comment. cts16 */
82 private static String _lastMessage = null;
83
84 /** TODO: Comment. cts16 */
85 private static boolean isLinkInitialized = false;
86
87 /** Static-only class. */
88 private MessageBay()
89 {
90 }
91
92 /** Whether the message bay is ready to display messages. */
93 public static boolean isReady()
94 {
95 return Browser.isInitComplete();
96 }
97
98 /** Syncs message bay size according to FrameGraphics max size. */
99 static void updateSize()
100 {
101 for(Item i : _messages) {
102 if(i != null) {
103 i.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
104 }
105 }
106
107 _messageLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
108
109 updateLink();
110 }
111
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;
116 }
117
118 /** TODO: Comment. cts16 */
119 public synchronized static Item getMessageLink()
120 {
121 return _messageLink;
122 }
123
124 /** TODO: Comment. cts16 */
125 public synchronized static List<Item> getMessages()
126 {
127 return _messages;
128 }
129
130 /** Causes the entire message bay area to be invalidated. */
131 public synchronized static void invalidateFullBay()
132 {
133 DisplayController.invalidateArea(DisplayController.getMessageBayPaintArea());
134 }
135
136 /** TODO: Comment. cts16 */
137 private static void updateLink()
138 {
139 if (!isLinkInitialized && DisplayController.getFramePaintArea().getWidth() > 0) {
140 // set up 'Messages' link on the right hand side
141 _messageLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MESSAGE_LINK_Y_OFFSET, MESSAGE_LINK_X);
142 _messageLink.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
143 isLinkInitialized = true;
144 } else {
145 _messageLink.setPosition(DisplayController.getMessageBayPaintArea().getWidth() - MESSAGE_LINK_Y_OFFSET, MESSAGE_LINK_X);
146 }
147 }
148
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;
154
155 // Update the buffer
156 updateBuffer(Item.DEFAULT_BACKGROUND, clip, size);
157
158 // Return the image buffer
159 return _messageBuffer;
160 }
161
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();
170 }
171
172 GraphicsManager g = EcosystemManager.getGraphicsManager();
173 g.pushDrawingSurface(_messageBuffer);
174
175 if (clip != null) g.pushClip(clip);
176 g.setAntialiasing(true);
177
178 g.clear(background);
179
180 g.setFont(_messageFont);
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 }
188 }
189
190 if (_status != null) FrameGraphics.PaintItem(_status);
191
192 if (clip == null || clip.isNotClipped() || _messageLink.isInDrawingArea(clip.getBounds())) {
193 FrameGraphics.PaintItem(_messageLink);
194 }
195
196 g.popDrawingSurface();
197 }
198
199 /** TODO: Comment. cts16 */
200 private static Text displayMessage(String message, String link, List<String> actions, Colour color)
201 {
202 return displayMessage(message, link, actions, color, true);
203 }
204
205 /** TODO: Comment. cts16 */
206 public synchronized static Text displayMessage(String message, String link, Colour color, boolean displayAlways, String action)
207 {
208 List<String> actions = new LinkedList<String>();
209 if (action != null) actions.add(action);
210 return displayMessage(message, link, actions, color, displayAlways);
211 }
212
213 /** TODO: Comment. cts16 */
214 private static Text newMessage(String message, String link, List<String> actions, Colour color)
215 {
216 Text t = new Text(getMessagePrefix(true) + message);
217 t.setPosition(20, 15 + _messages.size() * 25);
218 t.setOffset(0, -DisplayController.getFramePaintArea().getHeight());
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 }
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
238 System.out.println(message);
239
240 // Invalidate whole area
241 invalidateFullBay();
242
243 if (_suppressMessages) return null;
244
245 if (!displayAlways && message.equals(_lastMessage)) {
246 Misc.beep();
247 return null;
248 }
249
250 _lastMessage = message;
251
252 if (_creator == null) {
253 _creator = new FrameCreator(MESSAGES_FRAMESET_NAME, FrameIO.MESSAGES_PATH, MESSAGES_FRAMESET_NAME, true, false);
254 }
255
256 // set up 'Messages' link on the right hand side
257 updateLink();
258
259 if(_messages.size() >= 3) {
260 _messages.remove(0);
261 for(Item i : _messages) {
262 i.setY(i.getY() - 25);
263 }
264 }
265
266 Text t = newMessage(message, link, actions, color);
267
268 _messages.add(t);
269
270 // update the link to the latest message frame
271 _messageLink.setLink(_creator.getCurrent());
272
273 // TODO: Can we just make this DisplayController.requestRefresh()? cts16
274 if(redraw) {
275 DisplayController.requestRefresh(true);
276 }
277
278 return t;
279 }
280
281 /** TODO: Comment. cts16 */
282 public synchronized static Text displayMessage(String message, String link, List<String> actions, Colour color, boolean displayAlways)
283 {
284 return displayMessage(message, link, actions, color, displayAlways, true);
285 }
286
287 /** TODO: Comment. cts16 */
288 public synchronized static void overwriteMessage(String message)
289 {
290 overwriteMessage(message, null);
291 }
292
293 /** TODO: Comment. cts16 */
294 public synchronized static void overwriteMessage(String message, Colour color)
295 {
296 _messages.remove(_messages.size() - 1);
297 Text t = newMessage(message, null, null, color);
298 _messages.add(t);
299 DisplayController.requestRefresh(true);
300 }
301
302 /** TODO: Comment. cts16 */
303 private static String getMessagePrefix(int counter)
304 {
305 return "@" + counter + ": ";
306 }
307
308 /** TODO: Comment. cts16 */
309 private static String getMessagePrefix(boolean incrementCounter)
310 {
311 if (incrementCounter) _messageCount++;
312
313 return getMessagePrefix(_messageCount);
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 */
323 public synchronized static Text linkedErrorMessage(String message)
324 {
325 if (_suppressMessages) return null;
326 Misc.beep();
327 String[] tokens = message.split(Text.FRAME_NAME_SEPARATOR);
328 String link = null;
329 if (tokens.length > 1) link = tokens[tokens.length - 1];
330 return displayMessage(message, link, null, ERROR_COLOR);
331 }
332
333 /** TODO: Comment. cts16 */
334 public synchronized static Text errorMessage(String message)
335 {
336 if (_suppressMessages) return null;
337 Misc.beep();
338 return displayMessage(message, null, null, ERROR_COLOR, false);
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 */
348 public synchronized static Text displayMessage(String message)
349 {
350 return displayMessageAlways(message);
351 }
352
353 /** TODO: Comment. cts16 */
354 public synchronized static Text displayMessageOnce(String message)
355 {
356 return displayMessage(message, null, null, Colour.BLACK, false);
357 }
358
359 /** TODO: Comment. cts16 */
360 public synchronized static Text displayMessage(String message, Colour textColor)
361 {
362 return displayMessage(message, null, null, textColor);
363 }
364
365 /** TODO: Comment. cts16 */
366 public synchronized static Text displayMessage(Text message)
367 {
368 Text t = null;
369 String link = message.getLink();
370 List<String> action = message.getAction();
371 Colour color = message.getColor();
372 for (String s : message.getTextList()) {
373 t = displayMessage(s, link, action, color);
374 }
375 return t;
376 }
377
378 /** TODO: Comment. cts16 */
379 public synchronized static Text displayMessageAlways(String message)
380 {
381 return displayMessage(message, null, null, Colour.BLACK);
382 // Misc.Beep();
383 }
384
385 /** TODO: Comment. cts16 */
386 public synchronized static Text warningMessage(String message)
387 {
388 return displayMessage(message, null, null, Colour.MAGENTA);
389 }
390
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 {
403 _suppressMessages = val;
404 }
405
406 /** TODO: Comment. cts16 */
407 public synchronized static void setStatus(String status)
408 {
409 if (_status == null) {
410 _status = new Text(status);
411 _status.setPosition(0, 85);
412 _status.setOffset(0, -DisplayController.getMessageBayPaintArea().getMinY());
413 _status.setLink(null); // maybe link to a help frame?
414 _status.setFont(new Font(Text.MONOSPACED_FONT));
415 } else {
416 _status.setText(status);
417 }
418
419 //invalidateFullBay();
420 DisplayController.requestRefresh(true);
421 }
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();
428
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'; // ▒
433
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);
438
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);
448 }
449
450 public void UpdateMessage(final String text, final int newProgress) throws Exception
451 {
452 this._message = text;
453 set(newProgress);
454 }
455
456 public String GetMessage()
457 {
458 return _message;
459 }
460
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 */
467 public boolean set(int progress) throws Exception
468 {
469 if(progress < 0 || progress > 100) throw new Exception("Progress value out of bounds");
470 int p = progress / 5;
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);
474 return true;
475 }
476 return false;
477 }
478 }
479
480 /** TODO: Comment. cts16 */
481 public synchronized static Progress displayProgress(String message)
482 {
483 return new Progress(message);
484 }
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 }
532
533}
Note: See TracBrowser for help on using the repository browser.