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