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

Last change on this file since 1183 was 1183, checked in by bln4, 6 years ago

org.expeditee.gui.MessageBay ->

Added a synchronized keyword to the function updateBuffer(Colour, Clip, Dimension). This should hopefully fix the ConcurrentModificationException that I have been unable to track down the cause of.

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