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.ActionEvent; 007import java.awt.event.ActionListener; 008import java.awt.event.WindowAdapter; 009import java.awt.event.WindowEvent; 010import java.awt.event.WindowListener; 011 012import javax.swing.JOptionPane; 013import javax.swing.SwingUtilities; 014 015import org.openstreetmap.josm.Main; 016import org.openstreetmap.josm.gui.MapFrame; 017import org.openstreetmap.josm.gui.MapStatus.BackgroundProgressMonitor; 018import org.openstreetmap.josm.gui.PleaseWaitDialog; 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 = 10000; 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 static 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(runnable); 059 } 060 061 private void setDialogVisible(boolean visible) { 062 if (dialog.isVisible() != visible) { 063 dialog.setVisible(visible); 064 } 065 } 066 067 private ProgressMonitorDialog getDialog() { 068 069 BackgroundProgressMonitor backgroundMonitor = null; 070 MapFrame map = Main.map; 071 if (map != null) { 072 backgroundMonitor = map.statusLine.progressMonitor; 073 } 074 075 if (backgroundMonitor != null) { 076 backgroundMonitor.setVisible(isInBackground); 077 } 078 if (dialog != null) { 079 setDialogVisible(!isInBackground || backgroundMonitor == null); 080 } 081 082 if (isInBackground && backgroundMonitor != null) { 083 backgroundMonitor.setVisible(true); 084 if (dialog != null) { 085 setDialogVisible(false); 086 } 087 return backgroundMonitor; 088 } else if (backgroundMonitor != null) { 089 backgroundMonitor.setVisible(false); 090 if (dialog != null) { 091 setDialogVisible(true); 092 } 093 return dialog; 094 } else if (dialog != null) { 095 setDialogVisible(true); 096 return dialog; 097 } else 098 return null; 099 } 100 101 /** 102 * Constructs a new {@code PleaseWaitProgressMonitor}. 103 */ 104 public PleaseWaitProgressMonitor() { 105 this(""); 106 } 107 108 /** 109 * Constructs a new {@code PleaseWaitProgressMonitor}. 110 */ 111 public PleaseWaitProgressMonitor(String windowTitle) { 112 this(Main.parent); 113 this.windowTitle = windowTitle; 114 } 115 116 /** 117 * Constructs a new {@code PleaseWaitProgressMonitor}. 118 */ 119 public PleaseWaitProgressMonitor(Component dialogParent) { 120 super(new CancelHandler()); 121 if (GraphicsEnvironment.isHeadless()) { 122 this.dialogParent = dialogParent; 123 } else { 124 this.dialogParent = JOptionPane.getFrameForComponent(dialogParent); 125 } 126 this.cancelable = true; 127 } 128 129 /** 130 * Constructs a new {@code PleaseWaitProgressMonitor}. 131 */ 132 public PleaseWaitProgressMonitor(Component dialogParent, String windowTitle) { 133 this(JOptionPane.getFrameForComponent(dialogParent)); 134 this.windowTitle = windowTitle; 135 } 136 137 private ActionListener cancelListener = new ActionListener() { 138 @Override 139 public void actionPerformed(ActionEvent e) { 140 cancel(); 141 } 142 }; 143 144 private ActionListener inBackgroundListener = new ActionListener() { 145 @Override 146 public void actionPerformed(ActionEvent e) { 147 isInBackground = true; 148 ProgressMonitorDialog dialog = getDialog(); 149 if (dialog != null) { 150 reset(); 151 dialog.setVisible(true); 152 } 153 } 154 }; 155 156 private WindowListener windowListener = new WindowAdapter() { 157 @Override public void windowClosing(WindowEvent e) { 158 cancel(); 159 } 160 }; 161 162 public final boolean isCancelable() { 163 return cancelable; 164 } 165 166 public final void setCancelable(boolean cancelable) { 167 this.cancelable = cancelable; 168 } 169 170 @Override 171 public void doBeginTask() { 172 doInEDT(new Runnable() { 173 @Override 174 public void run() { 175 Main.currentProgressMonitor = PleaseWaitProgressMonitor.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 if (windowTitle != null) { 185 dialog.setTitle(windowTitle); 186 } 187 dialog.setCancelEnabled(cancelable); 188 dialog.setCancelCallback(cancelListener); 189 dialog.setInBackgroundCallback(inBackgroundListener); 190 dialog.setCustomText(""); 191 dialog.addWindowListener(windowListener); 192 dialog.progress.setMaximum(PROGRESS_BAR_MAX); 193 dialog.setVisible(true); 194 } 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(new Runnable() { 209 @Override 210 public void run() { 211 ProgressMonitorDialog dialog = getDialog(); 212 if (dialog != null) { 213 dialog.updateProgress(currentProgressValue); 214 } 215 } 216 }); 217 } 218 } 219 220 @Override 221 protected void doSetCustomText(final String title) { 222 checkState(State.IN_TASK, State.IN_SUBTASK); 223 this.customText = title; 224 doInEDT(new Runnable() { 225 @Override 226 public void run() { 227 ProgressMonitorDialog dialog = getDialog(); 228 if (dialog != null) { 229 dialog.setCustomText(title); 230 } 231 } 232 }); 233 } 234 235 @Override 236 protected void doSetTitle(final String title) { 237 checkState(State.IN_TASK, State.IN_SUBTASK); 238 this.title = title; 239 doInEDT(new Runnable() { 240 @Override 241 public void run() { 242 ProgressMonitorDialog dialog = getDialog(); 243 if (dialog != null) { 244 dialog.setCurrentAction(title); 245 } 246 } 247 }); 248 } 249 250 @Override 251 protected void doSetIntermediate(final boolean value) { 252 this.indeterminate = value; 253 doInEDT(new Runnable() { 254 @Override 255 public void run() { 256 // Enable only if progress is at the beginning. Doing intermediate progress in the middle 257 // will hide already reached progress 258 ProgressMonitorDialog dialog = getDialog(); 259 if (dialog != null) { 260 dialog.setIndeterminate(value && currentProgressValue == 0); 261 } 262 } 263 }); 264 } 265 266 @Override 267 public void appendLogMessage(final String message) { 268 doInEDT(new Runnable() { 269 @Override 270 public void run() { 271 ProgressMonitorDialog dialog = getDialog(); 272 if (dialog != null) { 273 dialog.appendLogMessage(message); 274 } 275 } 276 }); 277 } 278 279 public void reset() { 280 if (dialog != null) { 281 dialog.setTitle(title); 282 dialog.setCustomText(customText); 283 dialog.updateProgress(currentProgressValue); 284 dialog.setIndeterminate(indeterminate && currentProgressValue == 0); 285 } 286 BackgroundProgressMonitor backgroundMonitor = null; 287 MapFrame map = Main.map; 288 if (map != null) { 289 backgroundMonitor = map.statusLine.progressMonitor; 290 } 291 if (backgroundMonitor != null) { 292 backgroundMonitor.setCurrentAction(title); 293 backgroundMonitor.setCustomText(customText); 294 backgroundMonitor.updateProgress(currentProgressValue); 295 backgroundMonitor.setIndeterminate(indeterminate && currentProgressValue == 0); 296 } 297 298 } 299 300 public void close() { 301 doInEDT(new Runnable() { 302 @Override 303 public void run() { 304 if (dialog != null) { 305 dialog.setVisible(false); 306 dialog.setCancelCallback(null); 307 dialog.setInBackgroundCallback(null); 308 dialog.removeWindowListener(windowListener); 309 dialog.dispose(); 310 dialog = null; 311 Main.currentProgressMonitor = null; 312 MapFrame map = Main.map; 313 if (map != null) { 314 map.statusLine.progressMonitor.setVisible(false); 315 } 316 } 317 } 318 }); 319 } 320 321 public void showForegroundDialog() { 322 isInBackground = false; 323 doInEDT(new Runnable() { 324 @Override 325 public void run() { 326 if (dialog != null) { 327 dialog.setInBackgroundPossible(PleaseWaitProgressMonitor.this.taskId != null && Main.isDisplayingMapView()); 328 reset(); 329 getDialog(); 330 } 331 } 332 }); 333 334 } 335 336 @Override 337 public void setProgressTaskId(ProgressTaskId taskId) { 338 this.taskId = taskId; 339 doInEDT(new Runnable() { 340 @Override 341 public void run() { 342 if (dialog != null) { 343 dialog.setInBackgroundPossible(PleaseWaitProgressMonitor.this.taskId != null && Main.isDisplayingMapView()); 344 } 345 } 346 }); 347 } 348 349 @Override 350 public ProgressTaskId getProgressTaskId() { 351 return taskId; 352 } 353 354 @Override 355 public Component getWindowParent() { 356 Component parent = dialog; 357 if (isInBackground || parent == null) 358 return Main.parent; 359 else 360 return parent; 361 } 362}