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