001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.progress.swing; 003 004import java.awt.Component; 005import java.awt.GraphicsEnvironment; 006import java.awt.event.ActionListener; 007import java.awt.event.WindowAdapter; 008import java.awt.event.WindowEvent; 009import java.awt.event.WindowListener; 010 011import javax.swing.SwingUtilities; 012 013import org.openstreetmap.josm.Main; 014import org.openstreetmap.josm.gui.MainApplication; 015import org.openstreetmap.josm.gui.MapFrame; 016import org.openstreetmap.josm.gui.MapStatus.BackgroundProgressMonitor; 017import org.openstreetmap.josm.gui.PleaseWaitDialog; 018import org.openstreetmap.josm.gui.progress.AbstractProgressMonitor; 019import org.openstreetmap.josm.gui.progress.CancelHandler; 020import org.openstreetmap.josm.gui.progress.ProgressException; 021import org.openstreetmap.josm.gui.progress.ProgressTaskId; 022import org.openstreetmap.josm.gui.util.GuiHelper; 023import org.openstreetmap.josm.tools.bugreport.BugReport; 024 025/** 026 * A progress monitor used in {@link org.openstreetmap.josm.gui.PleaseWaitRunnable}. 027 * <p> 028 * Progress is displayed in a dialog window ({@link PleaseWaitDialog}). 029 * @since 12675 (moved from {@code gui.progress} package} 030 */ 031public class PleaseWaitProgressMonitor extends AbstractProgressMonitor { 032 033 /** 034 * Implemented by both foreground dialog and background progress dialog (in status bar) 035 */ 036 public interface ProgressMonitorDialog { 037 /** 038 * Sets the visibility of this dialog 039 * @param visible The visibility, <code>true</code> to show it, <code>false</code> to hide it 040 */ 041 void setVisible(boolean visible); 042 043 /** 044 * Updates the progress value to the specified progress. 045 * @param progress The progress as integer. Between 0 and {@link PleaseWaitProgressMonitor#PROGRESS_BAR_MAX} 046 */ 047 void updateProgress(int progress); 048 049 /** 050 * Sets the description of what is done 051 * @param text The description of the task 052 */ 053 void setCustomText(String text); 054 055 /** 056 * Sets the current action that is done 057 * @param text The current action 058 */ 059 void setCurrentAction(String text); 060 061 /** 062 * Display that the current progress cannot be determined 063 * @param newValue wether the progress cannot be determined 064 */ 065 void setIndeterminate(boolean newValue); 066 067 /** 068 * Append a message to the progress log 069 * <p> 070 * TODO Not implemented properly in background monitor, log message will get lost if progress runs in background 071 * @param message The message 072 */ 073 void appendLogMessage(String message); 074 } 075 076 /** 077 * The maximum value the progress bar that displays the current progress should have. 078 */ 079 public static final int PROGRESS_BAR_MAX = 10_000; 080 081 /** 082 * The progress monitor being currently displayed. 083 */ 084 static PleaseWaitProgressMonitor currentProgressMonitor; 085 086 private final Component dialogParent; 087 088 private int currentProgressValue; 089 private String customText; 090 private String title; 091 private boolean indeterminate; 092 093 private boolean isInBackground; 094 private PleaseWaitDialog dialog; 095 private String windowTitle; 096 protected ProgressTaskId taskId; 097 098 private boolean cancelable; 099 100 /** 101 * Returns the progress monitor being currently displayed. 102 * @return the progress monitor being currently displayed 103 * @since 12638 104 */ 105 public static PleaseWaitProgressMonitor getCurrent() { 106 return currentProgressMonitor; 107 } 108 109 private void doInEDT(Runnable runnable) { 110 // This must be invoke later even if current thread is EDT because inside there is dialog.setVisible 111 // which freeze current code flow until modal dialog is closed 112 SwingUtilities.invokeLater(() -> { 113 try { 114 runnable.run(); 115 } catch (RuntimeException e) { // NOPMD 116 throw BugReport.intercept(e).put("monitor", this); 117 } 118 }); 119 } 120 121 private void setDialogVisible(boolean visible) { 122 if (dialog.isVisible() != visible) { 123 dialog.setVisible(visible); 124 } 125 } 126 127 private ProgressMonitorDialog getDialog() { 128 129 BackgroundProgressMonitor backgroundMonitor = null; 130 MapFrame map = MainApplication.getMap(); 131 if (map != null) { 132 backgroundMonitor = map.statusLine.progressMonitor; 133 } 134 135 if (backgroundMonitor != null) { 136 backgroundMonitor.setVisible(isInBackground); 137 } 138 if (dialog != null) { 139 setDialogVisible(!isInBackground || backgroundMonitor == null); 140 } 141 142 if (isInBackground && backgroundMonitor != null) { 143 backgroundMonitor.setVisible(true); 144 if (dialog != null) { 145 setDialogVisible(false); 146 } 147 return backgroundMonitor; 148 } else if (backgroundMonitor != null) { 149 backgroundMonitor.setVisible(false); 150 if (dialog != null) { 151 setDialogVisible(true); 152 } 153 return dialog; 154 } else if (dialog != null) { 155 setDialogVisible(true); 156 return dialog; 157 } else 158 return null; 159 } 160 161 /** 162 * Constructs a new {@code PleaseWaitProgressMonitor}. 163 */ 164 public PleaseWaitProgressMonitor() { 165 this(""); 166 } 167 168 /** 169 * Constructs a new {@code PleaseWaitProgressMonitor}. 170 * @param windowTitle window title 171 */ 172 public PleaseWaitProgressMonitor(String windowTitle) { 173 this(Main.parent); 174 this.windowTitle = windowTitle; 175 } 176 177 /** 178 * Constructs a new {@code PleaseWaitProgressMonitor}. 179 * @param dialogParent component to get parent frame from 180 */ 181 public PleaseWaitProgressMonitor(Component dialogParent) { 182 super(new CancelHandler()); 183 if (GraphicsEnvironment.isHeadless()) { 184 this.dialogParent = dialogParent; 185 } else { 186 this.dialogParent = GuiHelper.getFrameForComponent(dialogParent); 187 } 188 this.cancelable = true; 189 } 190 191 /** 192 * Constructs a new {@code PleaseWaitProgressMonitor}. 193 * @param dialogParent component to get parent frame from 194 * @param windowTitle window title 195 */ 196 public PleaseWaitProgressMonitor(Component dialogParent, String windowTitle) { 197 this(GuiHelper.getFrameForComponent(dialogParent)); 198 this.windowTitle = windowTitle; 199 } 200 201 private final ActionListener cancelListener = e -> cancel(); 202 203 private final ActionListener inBackgroundListener = e -> { 204 isInBackground = true; 205 ProgressMonitorDialog dlg = getDialog(); 206 if (dlg != null) { 207 reset(); 208 dlg.setVisible(true); 209 } 210 }; 211 212 private final WindowListener windowListener = new WindowAdapter() { 213 @Override public void windowClosing(WindowEvent e) { 214 cancel(); 215 } 216 }; 217 218 /** 219 * See if this task is canceleable 220 * @return <code>true</code> if it can be canceled 221 */ 222 public final boolean isCancelable() { 223 return cancelable; 224 } 225 226 /** 227 * Sets this task to be cancelable 228 * @param cancelable Whether it can be canceled 229 */ 230 public final void setCancelable(boolean cancelable) { 231 this.cancelable = cancelable; 232 } 233 234 @Override 235 public void doBeginTask() { 236 doInEDT(() -> { 237 currentProgressMonitor = this; 238 if (GraphicsEnvironment.isHeadless()) { 239 return; 240 } 241 if (dialogParent != null && dialog == null) { 242 dialog = new PleaseWaitDialog(dialogParent); 243 } else { 244 throw new ProgressException("PleaseWaitDialog parent must be set"); 245 } 246 247 if (windowTitle != null) { 248 dialog.setTitle(windowTitle); 249 } 250 dialog.setCancelEnabled(cancelable); 251 dialog.setCancelCallback(cancelListener); 252 dialog.setInBackgroundCallback(inBackgroundListener); 253 dialog.setCustomText(""); 254 dialog.addWindowListener(windowListener); 255 dialog.setMaximumProgress(PROGRESS_BAR_MAX); 256 dialog.setVisible(true); 257 }); 258 } 259 260 @Override 261 public void doFinishTask() { 262 // do nothing 263 } 264 265 @Override 266 protected void updateProgress(double progressValue) { 267 final int newValue = (int) (progressValue * PROGRESS_BAR_MAX); 268 if (newValue != currentProgressValue) { 269 currentProgressValue = newValue; 270 doInEDT(() -> { 271 ProgressMonitorDialog dlg = getDialog(); 272 if (dlg != null) { 273 dlg.updateProgress(currentProgressValue); 274 } 275 }); 276 } 277 } 278 279 @Override 280 protected void doSetCustomText(final String title) { 281 checkState(State.IN_TASK, State.IN_SUBTASK); 282 this.customText = title; 283 doInEDT(() -> { 284 ProgressMonitorDialog dlg = getDialog(); 285 if (dlg != null) { 286 dlg.setCustomText(title); 287 } 288 }); 289 } 290 291 @Override 292 protected void doSetTitle(final String title) { 293 checkState(State.IN_TASK, State.IN_SUBTASK); 294 this.title = title; 295 doInEDT(() -> { 296 ProgressMonitorDialog dlg = getDialog(); 297 if (dlg != null) { 298 dlg.setCurrentAction(title); 299 } 300 }); 301 } 302 303 @Override 304 protected void doSetIntermediate(final boolean value) { 305 this.indeterminate = value; 306 doInEDT(() -> { 307 // Enable only if progress is at the beginning. Doing intermediate progress in the middle 308 // will hide already reached progress 309 ProgressMonitorDialog dlg = getDialog(); 310 if (dlg != null) { 311 dlg.setIndeterminate(value && currentProgressValue == 0); 312 } 313 }); 314 } 315 316 @Override 317 public void appendLogMessage(final String message) { 318 doInEDT(() -> { 319 ProgressMonitorDialog dlg = getDialog(); 320 if (dlg != null) { 321 dlg.appendLogMessage(message); 322 } 323 }); 324 } 325 326 /** 327 * Update the dialog values 328 */ 329 public void reset() { 330 if (dialog != null) { 331 dialog.setTitle(title); 332 dialog.setCustomText(customText); 333 dialog.updateProgress(currentProgressValue); 334 dialog.setIndeterminate(indeterminate && currentProgressValue == 0); 335 } 336 BackgroundProgressMonitor backgroundMonitor = null; 337 MapFrame map = MainApplication.getMap(); 338 if (map != null) { 339 backgroundMonitor = map.statusLine.progressMonitor; 340 } 341 if (backgroundMonitor != null) { 342 backgroundMonitor.setCurrentAction(title); 343 backgroundMonitor.setCustomText(customText); 344 backgroundMonitor.updateProgress(currentProgressValue); 345 backgroundMonitor.setIndeterminate(indeterminate && currentProgressValue == 0); 346 } 347 } 348 349 /** 350 * Close the progress dialog window. 351 */ 352 public void close() { 353 doInEDT(() -> { 354 if (dialog != null) { 355 dialog.setVisible(false); 356 dialog.setCancelCallback(null); 357 dialog.setInBackgroundCallback(null); 358 dialog.removeWindowListener(windowListener); 359 dialog.dispose(); 360 dialog = null; 361 currentProgressMonitor = null; 362 MapFrame map = MainApplication.getMap(); 363 if (map != null) { 364 map.statusLine.progressMonitor.setVisible(false); 365 } 366 } 367 }); 368 } 369 370 /** 371 * Show the progress dialog in foreground 372 */ 373 public void showForegroundDialog() { 374 isInBackground = false; 375 doInEDT(() -> { 376 if (dialog != null) { 377 dialog.setInBackgroundPossible(taskId != null && MainApplication.isDisplayingMapView()); 378 reset(); 379 getDialog(); 380 } 381 }); 382 } 383 384 @Override 385 public void setProgressTaskId(ProgressTaskId taskId) { 386 this.taskId = taskId; 387 doInEDT(() -> { 388 if (dialog != null) { 389 dialog.setInBackgroundPossible(taskId != null && MainApplication.isDisplayingMapView()); 390 } 391 }); 392 } 393 394 @Override 395 public ProgressTaskId getProgressTaskId() { 396 return taskId; 397 } 398 399 @Override 400 public Component getWindowParent() { 401 Component parent = dialog; 402 if (isInBackground || parent == null) 403 return Main.parent; 404 else 405 return parent; 406 } 407 408 @Override 409 public String toString() { 410 return "PleaseWaitProgressMonitor [currentProgressValue=" + currentProgressValue + ", customText=" + customText 411 + ", title=" + title + ", indeterminate=" + indeterminate + ", isInBackground=" + isInBackground 412 + ", windowTitle=" + windowTitle + ", taskId=" + taskId + ", cancelable=" + cancelable + ", state=" 413 + state + "]"; 414 } 415}