Fix UTF8 bug, create first executable JAR file
[jvcard.git] / src / com / googlecode / lanterna / gui2 / AnimatedLabel.java
... / ...
CommitLineData
1/*
2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
3 *
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Copyright (C) 2010-2015 Martin
18 */
19package com.googlecode.lanterna.gui2;
20
21import com.googlecode.lanterna.TerminalSize;
22
23import java.lang.ref.WeakReference;
24import java.util.*;
25
26/**
27 * This is a special label that contains not just a single text to display but a number of frames that are cycled
28 * through. The class will manage a timer on its own and ensure the label is updated and redrawn. There is a static
29 * helper method available to create the classic "spinning bar": {@code createClassicSpinningLine()}
30 */
31public class AnimatedLabel extends Label {
32 private static Timer TIMER = null;
33 private static final WeakHashMap<AnimatedLabel, TimerTask> SCHEDULED_TASKS = new WeakHashMap<AnimatedLabel, TimerTask>();
34
35 /**
36 * Creates a classic spinning bar which can be used to signal to the user that an operation in is process.
37 * @return {@code AnimatedLabel} instance which is setup to show a spinning bar
38 */
39 public static AnimatedLabel createClassicSpinningLine() {
40 return createClassicSpinningLine(150);
41 }
42
43 /**
44 * Creates a classic spinning bar which can be used to signal to the user that an operation in is process.
45 * @param speed Delay in between each frame
46 * @return {@code AnimatedLabel} instance which is setup to show a spinning bar
47 */
48 public static AnimatedLabel createClassicSpinningLine(int speed) {
49 AnimatedLabel animatedLabel = new AnimatedLabel("-");
50 animatedLabel.addFrame("\\");
51 animatedLabel.addFrame("|");
52 animatedLabel.addFrame("/");
53 animatedLabel.startAnimation(speed);
54 return animatedLabel;
55 }
56
57 private final List<String[]> frames;
58 private TerminalSize combinedMaximumPreferredSize;
59 private int currentFrame;
60
61 /**
62 * Creates a new animated label, initially set to one frame. You will need to add more frames and call
63 * {@code startAnimation()} for this to start moving.
64 *
65 * @param firstFrameText The content of the label at the first frame
66 */
67 public AnimatedLabel(String firstFrameText) {
68 super(firstFrameText);
69 frames = new ArrayList<String[]>();
70 currentFrame = 0;
71 combinedMaximumPreferredSize = TerminalSize.ZERO;
72
73 String[] lines = splitIntoMultipleLines(firstFrameText);
74 frames.add(lines);
75 ensurePreferredSize(lines);
76 }
77
78 /**
79 * Adds one more frame at the end of the list of frames
80 * @param text Text to use for the label at this frame
81 */
82 public synchronized void addFrame(String text) {
83 String[] lines = splitIntoMultipleLines(text);
84 frames.add(lines);
85 ensurePreferredSize(lines);
86 }
87
88 private void ensurePreferredSize(String[] lines) {
89 combinedMaximumPreferredSize = combinedMaximumPreferredSize.max(getBounds(lines, combinedMaximumPreferredSize));
90 }
91
92 /**
93 * Advances the animated label to the next frame. You normally don't need to call this manually as it will be done
94 * by the animation thread.
95 */
96 public synchronized void nextFrame() {
97 currentFrame++;
98 if(currentFrame >= frames.size()) {
99 currentFrame = 0;
100 }
101 super.setLines(frames.get(currentFrame));
102 invalidate();
103 }
104
105 @Override
106 public void onRemoved(Container container) {
107 stopAnimation();
108 }
109
110 /**
111 * Starts the animation thread which will periodically call {@code nextFrame()} at the interval specified by the
112 * {@code millisecondsPerFrame} parameter. After all frames have been cycled through, it will start over from the
113 * first frame again.
114 * @param millisecondsPerFrame The interval in between every frame
115 */
116 public synchronized void startAnimation(long millisecondsPerFrame) {
117 if(TIMER == null) {
118 TIMER = new Timer("AnimatedLabel");
119 }
120 AnimationTimerTask animationTimerTask = new AnimationTimerTask(this);
121 SCHEDULED_TASKS.put(this, animationTimerTask);
122 TIMER.scheduleAtFixedRate(animationTimerTask, millisecondsPerFrame, millisecondsPerFrame);
123 }
124
125 /**
126 * Halts the animation thread and the label will stop at whatever was the current frame at the time when this was
127 * called
128 */
129 public synchronized void stopAnimation() {
130 removeTaskFromTimer(this);
131 }
132
133 private static synchronized void removeTaskFromTimer(AnimatedLabel animatedLabel) {
134 SCHEDULED_TASKS.get(animatedLabel).cancel();
135 SCHEDULED_TASKS.remove(animatedLabel);
136 canCloseTimer();
137 }
138
139 private static synchronized void canCloseTimer() {
140 if(SCHEDULED_TASKS.isEmpty()) {
141 TIMER.cancel();
142 TIMER = null;
143 }
144 }
145
146 private static class AnimationTimerTask extends TimerTask {
147 private final WeakReference<AnimatedLabel> labelRef;
148
149 private AnimationTimerTask(AnimatedLabel label) {
150 this.labelRef = new WeakReference<AnimatedLabel>(label);
151 }
152
153 @Override
154 public void run() {
155 AnimatedLabel animatedLabel = labelRef.get();
156 if(animatedLabel == null) {
157 cancel();
158 canCloseTimer();
159 }
160 else {
161 if(animatedLabel.getBasePane() == null) {
162 animatedLabel.stopAnimation();
163 }
164 else {
165 animatedLabel.nextFrame();
166 }
167 }
168 }
169 }
170}