source: josm/trunk/src/org/openstreetmap/josm/gui/SplashScreen.java@ 12371

Last change on this file since 12371 was 12272, checked in by Don-vip, 7 years ago

fix #14849 - fix crash at startup when two initialization tasks have been translated with the same text

  • Property svn:eol-style set to native
File size: 12.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Color;
7import java.awt.Component;
8import java.awt.Container;
9import java.awt.Dimension;
10import java.awt.GridBagConstraints;
11import java.awt.GridBagLayout;
12import java.awt.Insets;
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.util.List;
16import java.util.Objects;
17import java.util.concurrent.CopyOnWriteArrayList;
18
19import javax.swing.BorderFactory;
20import javax.swing.JComponent;
21import javax.swing.JFrame;
22import javax.swing.JLabel;
23import javax.swing.JPanel;
24import javax.swing.JProgressBar;
25import javax.swing.JScrollPane;
26import javax.swing.JSeparator;
27import javax.swing.ScrollPaneConstants;
28import javax.swing.border.Border;
29import javax.swing.border.EmptyBorder;
30import javax.swing.border.EtchedBorder;
31import javax.swing.event.ChangeEvent;
32import javax.swing.event.ChangeListener;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.data.Version;
36import org.openstreetmap.josm.gui.progress.ProgressMonitor;
37import org.openstreetmap.josm.gui.progress.ProgressTaskId;
38import org.openstreetmap.josm.gui.util.GuiHelper;
39import org.openstreetmap.josm.gui.widgets.JosmEditorPane;
40import org.openstreetmap.josm.tools.GBC;
41import org.openstreetmap.josm.tools.ImageProvider;
42import org.openstreetmap.josm.tools.Utils;
43import org.openstreetmap.josm.tools.WindowGeometry;
44
45/**
46 * Show a splash screen so the user knows what is happening during startup.
47 * @since 976
48 */
49public class SplashScreen extends JFrame implements ChangeListener {
50
51 private final transient SplashProgressMonitor progressMonitor;
52 private final SplashScreenProgressRenderer progressRenderer;
53
54 /**
55 * Constructs a new {@code SplashScreen}.
56 */
57 public SplashScreen() {
58 setUndecorated(true);
59
60 // Add a nice border to the main splash screen
61 Container contentPane = this.getContentPane();
62 Border margin = new EtchedBorder(1, Color.white, Color.gray);
63 if (contentPane instanceof JComponent) {
64 ((JComponent) contentPane).setBorder(margin);
65 }
66
67 // Add a margin from the border to the content
68 JPanel innerContentPane = new JPanel(new GridBagLayout());
69 innerContentPane.setBorder(new EmptyBorder(10, 10, 2, 10));
70 contentPane.add(innerContentPane);
71
72 // Add the logo
73 JLabel logo = new JLabel(ImageProvider.get("logo.svg", ImageProvider.ImageSizes.SPLASH_LOGO));
74 GridBagConstraints gbc = new GridBagConstraints();
75 gbc.gridheight = 2;
76 gbc.insets = new Insets(0, 0, 0, 70);
77 innerContentPane.add(logo, gbc);
78
79 // Add the name of this application
80 JLabel caption = new JLabel("JOSM – " + tr("Java OpenStreetMap Editor"));
81 caption.setFont(GuiHelper.getTitleFont());
82 gbc.gridheight = 1;
83 gbc.gridx = 1;
84 gbc.insets = new Insets(30, 0, 0, 0);
85 innerContentPane.add(caption, gbc);
86
87 // Add the version number
88 JLabel version = new JLabel(tr("Version {0}", Version.getInstance().getVersionString()));
89 gbc.gridy = 1;
90 gbc.insets = new Insets(0, 0, 0, 0);
91 innerContentPane.add(version, gbc);
92
93 // Add a separator to the status text
94 JSeparator separator = new JSeparator(JSeparator.HORIZONTAL);
95 gbc.gridx = 0;
96 gbc.gridy = 2;
97 gbc.gridwidth = 2;
98 gbc.fill = GridBagConstraints.HORIZONTAL;
99 gbc.insets = new Insets(15, 0, 5, 0);
100 innerContentPane.add(separator, gbc);
101
102 // Add a status message
103 progressRenderer = new SplashScreenProgressRenderer();
104 gbc.gridy = 3;
105 gbc.insets = new Insets(0, 0, 10, 0);
106 innerContentPane.add(progressRenderer, gbc);
107 progressMonitor = new SplashProgressMonitor(null, this);
108
109 pack();
110
111 WindowGeometry.centerOnScreen(this.getSize(), "gui.geometry").applySafe(this);
112
113 // Add ability to hide splash screen by clicking it
114 addMouseListener(new MouseAdapter() {
115 @Override
116 public void mousePressed(MouseEvent event) {
117 setVisible(false);
118 }
119 });
120 }
121
122 @Override
123 public void stateChanged(ChangeEvent ignore) {
124 GuiHelper.runInEDT(() -> progressRenderer.setTasks(progressMonitor.toString()));
125 }
126
127 /**
128 * A task (of a {@link ProgressMonitor}).
129 */
130 private abstract static class Task {
131
132 /**
133 * Returns a HTML representation for this task.
134 * @param sb a {@code StringBuilder} used to build the HTML code
135 * @return {@code sb}
136 */
137 public abstract StringBuilder toHtml(StringBuilder sb);
138
139 @Override
140 public final String toString() {
141 return toHtml(new StringBuilder(1024)).toString();
142 }
143 }
144
145 /**
146 * A single task (of a {@link ProgressMonitor}) which keeps track of its execution duration
147 * (requires a call to {@link #finish()}).
148 */
149 private static class MeasurableTask extends Task {
150 private final String name;
151 private final long start;
152 private String duration = "";
153
154 MeasurableTask(String name) {
155 this.name = name;
156 this.start = System.currentTimeMillis();
157 }
158
159 public void finish() {
160 if (isFinished()) {
161 throw new IllegalStateException("This task has already been finished: " + name);
162 }
163 duration = tr(" ({0})", Utils.getDurationString(System.currentTimeMillis() - start));
164 }
165
166 /**
167 * Determines if this task has been finished.
168 * @return {@code true} if this task has been finished
169 */
170 public boolean isFinished() {
171 return !duration.isEmpty();
172 }
173
174 @Override
175 public StringBuilder toHtml(StringBuilder sb) {
176 return sb.append(name).append("<i style='color: #666666;'>").append(duration).append("</i>");
177 }
178
179 @Override
180 public boolean equals(Object o) {
181 if (this == o) return true;
182 if (o == null || getClass() != o.getClass()) return false;
183 MeasurableTask that = (MeasurableTask) o;
184 return Objects.equals(name, that.name)
185 && isFinished() == that.isFinished();
186 }
187
188 @Override
189 public int hashCode() {
190 return Objects.hashCode(name);
191 }
192 }
193
194 /**
195 * A {@link ProgressMonitor} which stores the (sub)tasks in a tree.
196 */
197 public static class SplashProgressMonitor extends Task implements ProgressMonitor {
198
199 private final String name;
200 private final ChangeListener listener;
201 private final List<Task> tasks = new CopyOnWriteArrayList<>();
202 private SplashProgressMonitor latestSubtask;
203
204 /**
205 * Constructs a new {@code SplashProgressMonitor}.
206 * @param name name
207 * @param listener change listener
208 */
209 public SplashProgressMonitor(String name, ChangeListener listener) {
210 this.name = name;
211 this.listener = listener;
212 }
213
214 @Override
215 public StringBuilder toHtml(StringBuilder sb) {
216 sb.append(Utils.firstNonNull(name, ""));
217 if (!tasks.isEmpty()) {
218 sb.append("<ul>");
219 for (Task i : tasks) {
220 sb.append("<li>");
221 i.toHtml(sb);
222 sb.append("</li>");
223 }
224 sb.append("</ul>");
225 }
226 return sb;
227 }
228
229 @Override
230 public void beginTask(String title) {
231 if (title != null && !title.isEmpty()) {
232 if (Main.isDebugEnabled()) {
233 Main.debug(title);
234 }
235 final MeasurableTask task = new MeasurableTask(title);
236 tasks.add(task);
237 listener.stateChanged(null);
238 }
239 }
240
241 @Override
242 public void beginTask(String title, int ticks) {
243 this.beginTask(title);
244 }
245
246 @Override
247 public void setCustomText(String text) {
248 this.beginTask(text);
249 }
250
251 @Override
252 public void setExtraText(String text) {
253 this.beginTask(text);
254 }
255
256 @Override
257 public void indeterminateSubTask(String title) {
258 this.subTask(title);
259 }
260
261 @Override
262 public void subTask(String title) {
263 if (Main.isDebugEnabled()) {
264 Main.debug(title);
265 }
266 latestSubtask = new SplashProgressMonitor(title, listener);
267 tasks.add(latestSubtask);
268 listener.stateChanged(null);
269 }
270
271 @Override
272 public ProgressMonitor createSubTaskMonitor(int ticks, boolean internal) {
273 if (latestSubtask != null) {
274 return latestSubtask;
275 } else {
276 // subTask has not been called before, such as for plugin update, #11874
277 return this;
278 }
279 }
280
281 /**
282 * @deprecated Use {@link #finishTask(String)} instead.
283 */
284 @Override
285 @Deprecated
286 public void finishTask() {
287 // Not used
288 }
289
290 /**
291 * Displays the given task as finished.
292 * @param title the task title
293 */
294 public void finishTask(String title) {
295 final Task task = Utils.find(tasks, new MeasurableTask(title)::equals);
296 if (task instanceof MeasurableTask) {
297 ((MeasurableTask) task).finish();
298 if (Main.isDebugEnabled()) {
299 Main.debug(tr("{0} completed in {1}", title, ((MeasurableTask) task).duration));
300 }
301 listener.stateChanged(null);
302 }
303 }
304
305 @Override
306 public void invalidate() {
307 // Not used
308 }
309
310 @Override
311 public void setTicksCount(int ticks) {
312 // Not used
313 }
314
315 @Override
316 public int getTicksCount() {
317 return 0;
318 }
319
320 @Override
321 public void setTicks(int ticks) {
322 // Not used
323 }
324
325 @Override
326 public int getTicks() {
327 return 0;
328 }
329
330 @Override
331 public void worked(int ticks) {
332 // Not used
333 }
334
335 @Override
336 public boolean isCanceled() {
337 return false;
338 }
339
340 @Override
341 public void cancel() {
342 // Not used
343 }
344
345 @Override
346 public void addCancelListener(CancelListener listener) {
347 // Not used
348 }
349
350 @Override
351 public void removeCancelListener(CancelListener listener) {
352 // Not used
353 }
354
355 @Override
356 public void appendLogMessage(String message) {
357 // Not used
358 }
359
360 @Override
361 public void setProgressTaskId(ProgressTaskId taskId) {
362 // Not used
363 }
364
365 @Override
366 public ProgressTaskId getProgressTaskId() {
367 return null;
368 }
369
370 @Override
371 public Component getWindowParent() {
372 return Main.parent;
373 }
374 }
375
376 /**
377 * Returns the progress monitor.
378 * @return The progress monitor
379 */
380 public SplashProgressMonitor getProgressMonitor() {
381 return progressMonitor;
382 }
383
384 private static class SplashScreenProgressRenderer extends JPanel {
385 private final JosmEditorPane lblTaskTitle = new JosmEditorPane();
386 private final JProgressBar progressBar = new JProgressBar(JProgressBar.HORIZONTAL);
387 private static final String LABEL_HTML = "<html>"
388 + "<style>ul {margin-top: 0; margin-bottom: 0; padding: 0;} li {margin: 0; padding: 0;}</style>";
389
390 protected void build() {
391 setLayout(new GridBagLayout());
392
393 JosmEditorPane.makeJLabelLike(lblTaskTitle, false);
394 lblTaskTitle.setText(LABEL_HTML);
395 final JScrollPane scrollPane = new JScrollPane(lblTaskTitle,
396 ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
397 scrollPane.setPreferredSize(new Dimension(0, 320));
398 scrollPane.setBorder(BorderFactory.createEmptyBorder());
399 add(scrollPane, GBC.eol().insets(5, 5, 0, 0).fill(GridBagConstraints.HORIZONTAL));
400
401 progressBar.setIndeterminate(true);
402 add(progressBar, GBC.eol().insets(5, 15, 0, 0).fill(GridBagConstraints.HORIZONTAL));
403 }
404
405 /**
406 * Constructs a new {@code SplashScreenProgressRenderer}.
407 */
408 SplashScreenProgressRenderer() {
409 build();
410 }
411
412 /**
413 * Sets the tasks to displayed. A HTML formatted list is expected.
414 * @param tasks HTML formatted list of tasks
415 */
416 public void setTasks(String tasks) {
417 lblTaskTitle.setText(LABEL_HTML + tasks);
418 lblTaskTitle.setCaretPosition(lblTaskTitle.getDocument().getLength());
419 }
420 }
421}
Note: See TracBrowser for help on using the repository browser.