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

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

fix #14938 - robustness to system date changing during startup

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