001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.progress; 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.MapFrame; 015import org.openstreetmap.josm.gui.MapStatus.BackgroundProgressMonitor; 016import org.openstreetmap.josm.gui.PleaseWaitDialog; 017import org.openstreetmap.josm.gui.util.GuiHelper; 018import org.openstreetmap.josm.tools.bugreport.BugReport; 019 020public class PleaseWaitProgressMonitor extends AbstractProgressMonitor { 021 022 /** 023 * Implemented by both foreground dialog and background progress dialog (in status bar) 024 */ 025 public interface ProgressMonitorDialog { 026 void setVisible(boolean visible); 027 028 void updateProgress(int progress); 029 030 void setCustomText(String text); 031 032 void setCurrentAction(String text); 033 034 void setIndeterminate(boolean newValue); 035 036 // TODO Not implemented properly in background monitor, log message will get lost if progress runs in background 037 void appendLogMessage(String message); 038 } 039 040 public static final int PROGRESS_BAR_MAX = 10_000; 041 private final Component dialogParent; 042 043 private int currentProgressValue; 044 private String customText; 045 private String title; 046 private boolean indeterminate; 047 048 private boolean isInBackground; 049 private PleaseWaitDialog dialog; 050 private String windowTitle; 051 protected ProgressTaskId taskId; 052 053 private boolean cancelable; 054 055 private void doInEDT(Runnable runnable) { 056 // This must be invoke later even if current thread is EDT because inside there is dialog.setVisible 057 // which freeze current code flow until modal dialog is closed 058 SwingUtilities.invokeLater(() -> { 059 try { 060 runnable.run(); 061 } catch (RuntimeException e) { 062 throw BugReport.intercept(e).put("monitor", this); 063 } 064 }); 065 } 066 067 private void setDialogVisible(boolean visible) { 068 if (dialog.isVisible() != visible) { 069 dialog.setVisible(visible); 070 } 071 } 072 073 private ProgressMonitorDialog getDialog() { 074 075 BackgroundProgressMonitor backgroundMonitor = null; 076 MapFrame map = Main.map; 077 if (map != null) { 078 backgroundMonitor = map.statusLine.progressMonitor; 079 } 080 081 if (backgroundMonitor != null) { 082 backgroundMonitor.setVisible(isInBackground); 083 } 084 if (dialog != null) { 085 setDialogVisible(!isInBackground || backgroundMonitor == null); 086 } 087 088 if (isInBackground && backgroundMonitor != null) { 089 backgroundMonitor.setVisible(true); 090 if (dialog != null) { 091 setDialogVisible(false); 092 } 093 return backgroundMonitor; 094 } else if (backgroundMonitor != null) { 095 backgroundMonitor.setVisible(false); 096 if (dialog != null) { 097 setDialogVisible(true); 098 } 099 return dialog; 100 } else if (dialog != null) { 101 setDialogVisible(true); 102 return dialog; 103 } else 104 return null; 105 } 106 107 /** 108 * Constructs a new {@code PleaseWaitProgressMonitor}. 109 */ 110 public PleaseWaitProgressMonitor() { 111 this(""); 112 } 113 114 /** 115 * Constructs a new {@code PleaseWaitProgressMonitor}. 116 * @param windowTitle window title 117 */ 118 public PleaseWaitProgressMonitor(String windowTitle) { 119 this(Main.parent); 120 this.windowTitle = windowTitle; 121 } 122 123 /** 124 * Constructs a new {@code PleaseWaitProgressMonitor}. 125 * @param dialogParent component to get parent frame from 126 */ 127 public PleaseWaitProgressMonitor(Component dialogParent) { 128 super(new CancelHandler()); 129 if (GraphicsEnvironment.isHeadless()) { 130 this.dialogParent = dialogParent; 131 } else { 132 this.dialogParent = GuiHelper.getFrameForComponent(dialogParent); 133 } 134 this.cancelable = true; 135 } 136 137 /** 138 * Constructs a new {@code PleaseWaitProgressMonitor}. 139 * @param dialogParent component to get parent frame from 140 * @param windowTitle window title 141 */ 142 public PleaseWaitProgressMonitor(Component dialogParent, String windowTitle) { 143 this(GuiHelper.getFrameForComponent(dialogParent)); 144 this.windowTitle = windowTitle; 145 } 146 147 private final ActionListener cancelListener = e -> cancel(); 148 149 private final ActionListener inBackgroundListener = e -> { 150 isInBackground = true; 151 ProgressMonitorDialog dlg = getDialog(); 152 if (dlg != null) { 153 reset(); 154 dlg.setVisible(true); 155 } 156 }; 157 158 private final WindowListener windowListener = new WindowAdapter() { 159 @Override public void windowClosing(WindowEvent e) { 160 cancel(); 161 } 162 }; 163 164 public final boolean isCancelable() { 165 return cancelable; 166 } 167 168 public final void setCancelable(boolean cancelable) { 169 this.cancelable = cancelable; 170 } 171 172 @Override 173 public void doBeginTask() { 174 doInEDT(() -> { 175 Main.currentProgressMonitor = this; 176 if (GraphicsEnvironment.isHeadless()) { 177 return; 178 } 179 if (dialogParent != null && dialog == null) { 180 dialog = new PleaseWaitDialog(dialogParent); 181 } else { 182 throw new ProgressException("PleaseWaitDialog parent must be set"); 183 } 184 185 if (windowTitle != null) { 186 dialog.setTitle(windowTitle); 187 } 188 dialog.setCancelEnabled(cancelable); 189 dialog.setCancelCallback(cancelListener); 190 dialog.setInBackgroundCallback(inBackgroundListener); 191 dialog.setCustomText(""); 192 dialog.addWindowListener(windowListener); 193 dialog.progress.setMaximum(PROGRESS_BAR_MAX); 194 dialog.setVisible(true); 195 }); 196 } 197 198 @Override 199 public void doFinishTask() { 200 // do nothing 201 } 202 203 @Override 204 protected void updateProgress(double progressValue) { 205 final int newValue = (int) (progressValue * PROGRESS_BAR_MAX); 206 if (newValue != currentProgressValue) { 207 currentProgressValue = newValue; 208 doInEDT(() -> { 209 ProgressMonitorDialog dlg = getDialog(); 210 if (dlg != null) { 211 dlg.updateProgress(currentProgressValue); 212 } 213 }); 214 } 215 } 216 217 @Override 218 protected void doSetCustomText(final String title) { 219 checkState(State.IN_TASK, State.IN_SUBTASK); 220 this.customText = title; 221 doInEDT(() -> { 222 ProgressMonitorDialog dlg = getDialog(); 223 if (dlg != null) { 224 dlg.setCustomText(title); 225 } 226 }); 227 } 228 229 @Override 230 protected void doSetTitle(final String title) { 231 checkState(State.IN_TASK, State.IN_SUBTASK); 232 this.title = title; 233 doInEDT(() -> { 234 ProgressMonitorDialog dlg = getDialog(); 235 if (dlg != null) { 236 dlg.setCurrentAction(title); 237 } 238 }); 239 } 240 241 @Override 242 protected void doSetIntermediate(final boolean value) { 243 this.indeterminate = value; 244 doInEDT(() -> { 245 // Enable only if progress is at the beginning. Doing intermediate progress in the middle 246 // will hide already reached progress 247 ProgressMonitorDialog dlg = getDialog(); 248 if (dlg != null) { 249 dlg.setIndeterminate(value && currentProgressValue == 0); 250 } 251 }); 252 } 253 254 @Override 255 public void appendLogMessage(final String message) { 256 doInEDT(() -> { 257 ProgressMonitorDialog dlg = getDialog(); 258 if (dlg != null) { 259 dlg.appendLogMessage(message); 260 } 261 }); 262 } 263 264 public void reset() { 265 if (dialog != null) { 266 dialog.setTitle(title); 267 dialog.setCustomText(customText); 268 dialog.updateProgress(currentProgressValue); 269 dialog.setIndeterminate(indeterminate && currentProgressValue == 0); 270 } 271 BackgroundProgressMonitor backgroundMonitor = null; 272 MapFrame map = Main.map; 273 if (map != null) { 274 backgroundMonitor = map.statusLine.progressMonitor; 275 } 276 if (backgroundMonitor != null) { 277 backgroundMonitor.setCurrentAction(title); 278 backgroundMonitor.setCustomText(customText); 279 backgroundMonitor.updateProgress(currentProgressValue); 280 backgroundMonitor.setIndeterminate(indeterminate && currentProgressValue == 0); 281 } 282 } 283 284 public void close() { 285 doInEDT(() -> { 286 if (dialog != null) { 287 dialog.setVisible(false); 288 dialog.setCancelCallback(null); 289 dialog.setInBackgroundCallback(null); 290 dialog.removeWindowListener(windowListener); 291 dialog.dispose(); 292 dialog = null; 293 Main.currentProgressMonitor = null; 294 MapFrame map = Main.map; 295 if (map != null) { 296 map.statusLine.progressMonitor.setVisible(false); 297 } 298 } 299 }); 300 } 301 302 public void showForegroundDialog() { 303 isInBackground = false; 304 doInEDT(() -> { 305 if (dialog != null) { 306 dialog.setInBackgroundPossible(taskId != null && Main.isDisplayingMapView()); 307 reset(); 308 getDialog(); 309 } 310 }); 311 } 312 313 @Override 314 public void setProgressTaskId(ProgressTaskId taskId) { 315 this.taskId = taskId; 316 doInEDT(() -> { 317 if (dialog != null) { 318 dialog.setInBackgroundPossible(taskId != null && Main.isDisplayingMapView()); 319 } 320 }); 321 } 322 323 @Override 324 public ProgressTaskId getProgressTaskId() { 325 return taskId; 326 } 327 328 @Override 329 public Component getWindowParent() { 330 Component parent = dialog; 331 if (isInBackground || parent == null) 332 return Main.parent; 333 else 334 return parent; 335 } 336 337 @Override 338 public String toString() { 339 return "PleaseWaitProgressMonitor [currentProgressValue=" + currentProgressValue + ", customText=" + customText 340 + ", title=" + title + ", indeterminate=" + indeterminate + ", isInBackground=" + isInBackground 341 + ", windowTitle=" + windowTitle + ", taskId=" + taskId + ", cancelable=" + cancelable + ", state=" 342 + state + "]"; 343 } 344}