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

Last change on this file since 17318 was 16069, checked in by simon04, 4 years ago

see #18752 - Harmonize stopwatch logging

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