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 * @param windowTitle window title 111 */ 112 public PleaseWaitProgressMonitor(String windowTitle) { 113 this(Main.parent); 114 this.windowTitle = windowTitle; 115 } 116 117 /** 118 * Constructs a new {@code PleaseWaitProgressMonitor}. 119 * @param dialogParent component to get parent frame from 120 */ 121 public PleaseWaitProgressMonitor(Component dialogParent) { 122 super(new CancelHandler()); 123 if (GraphicsEnvironment.isHeadless()) { 124 this.dialogParent = dialogParent; 125 } else { 126 this.dialogParent = JOptionPane.getFrameForComponent(dialogParent); 127 } 128 this.cancelable = true; 129 } 130 131 /** 132 * Constructs a new {@code PleaseWaitProgressMonitor}. 133 * @param dialogParent component to get parent frame from 134 * @param windowTitle window title 135 */ 136 public PleaseWaitProgressMonitor(Component dialogParent, String windowTitle) { 137 this(JOptionPane.getFrameForComponent(dialogParent)); 138 this.windowTitle = windowTitle; 139 } 140 141 private final ActionListener cancelListener = new ActionListener() { 142 @Override 143 public void actionPerformed(ActionEvent e) { 144 cancel(); 145 } 146 }; 147 148 private final ActionListener inBackgroundListener = new ActionListener() { 149 @Override 150 public void actionPerformed(ActionEvent e) { 151 isInBackground = true; 152 ProgressMonitorDialog dialog = getDialog(); 153 if (dialog != null) { 154 reset(); 155 dialog.setVisible(true); 156 } 157 } 158 }; 159 160 private final WindowListener windowListener = new WindowAdapter() { 161 @Override public void windowClosing(WindowEvent e) { 162 cancel(); 163 } 164 }; 165 166 public final boolean isCancelable() { 167 return cancelable; 168 } 169 170 public final void setCancelable(boolean cancelable) { 171 this.cancelable = cancelable; 172 } 173 174 @Override 175 public void doBeginTask() { 176 doInEDT(new Runnable() { 177 @Override 178 public void run() { 179 Main.currentProgressMonitor = PleaseWaitProgressMonitor.this; 180 if (GraphicsEnvironment.isHeadless()) { 181 return; 182 } 183 if (dialogParent != null && dialog == null) { 184 dialog = new PleaseWaitDialog(dialogParent); 185 } else 186 throw new ProgressException("PleaseWaitDialog parent must be set"); 187 188 if (windowTitle != null) { 189 dialog.setTitle(windowTitle); 190 } 191 dialog.setCancelEnabled(cancelable); 192 dialog.setCancelCallback(cancelListener); 193 dialog.setInBackgroundCallback(inBackgroundListener); 194 dialog.setCustomText(""); 195 dialog.addWindowListener(windowListener); 196 dialog.progress.setMaximum(PROGRESS_BAR_MAX); 197 dialog.setVisible(true); 198 } 199 }); 200 } 201 202 @Override 203 public void doFinishTask() { 204 // do nothing 205 } 206 207 @Override 208 protected void updateProgress(double progressValue) { 209 final int newValue = (int) (progressValue * PROGRESS_BAR_MAX); 210 if (newValue != currentProgressValue) { 211 currentProgressValue = newValue; 212 doInEDT(new Runnable() { 213 @Override 214 public void run() { 215 ProgressMonitorDialog dialog = getDialog(); 216 if (dialog != null) { 217 dialog.updateProgress(currentProgressValue); 218 } 219 } 220 }); 221 } 222 } 223 224 @Override 225 protected void doSetCustomText(final String title) { 226 checkState(State.IN_TASK, State.IN_SUBTASK); 227 this.customText = title; 228 doInEDT(new Runnable() { 229 @Override 230 public void run() { 231 ProgressMonitorDialog dialog = getDialog(); 232 if (dialog != null) { 233 dialog.setCustomText(title); 234 } 235 } 236 }); 237 } 238 239 @Override 240 protected void doSetTitle(final String title) { 241 checkState(State.IN_TASK, State.IN_SUBTASK); 242 this.title = title; 243 doInEDT(new Runnable() { 244 @Override 245 public void run() { 246 ProgressMonitorDialog dialog = getDialog(); 247 if (dialog != null) { 248 dialog.setCurrentAction(title); 249 } 250 } 251 }); 252 } 253 254 @Override 255 protected void doSetIntermediate(final boolean value) { 256 this.indeterminate = value; 257 doInEDT(new Runnable() { 258 @Override 259 public void run() { 260 // Enable only if progress is at the beginning. Doing intermediate progress in the middle 261 // will hide already reached progress 262 ProgressMonitorDialog dialog = getDialog(); 263 if (dialog != null) { 264 dialog.setIndeterminate(value && currentProgressValue == 0); 265 } 266 } 267 }); 268 } 269 270 @Override 271 public void appendLogMessage(final String message) { 272 doInEDT(new Runnable() { 273 @Override 274 public void run() { 275 ProgressMonitorDialog dialog = getDialog(); 276 if (dialog != null) { 277 dialog.appendLogMessage(message); 278 } 279 } 280 }); 281 } 282 283 public void reset() { 284 if (dialog != null) { 285 dialog.setTitle(title); 286 dialog.setCustomText(customText); 287 dialog.updateProgress(currentProgressValue); 288 dialog.setIndeterminate(indeterminate && currentProgressValue == 0); 289 } 290 BackgroundProgressMonitor backgroundMonitor = null; 291 MapFrame map = Main.map; 292 if (map != null) { 293 backgroundMonitor = map.statusLine.progressMonitor; 294 } 295 if (backgroundMonitor != null) { 296 backgroundMonitor.setCurrentAction(title); 297 backgroundMonitor.setCustomText(customText); 298 backgroundMonitor.updateProgress(currentProgressValue); 299 backgroundMonitor.setIndeterminate(indeterminate && currentProgressValue == 0); 300 } 301 302 } 303 304 public void close() { 305 doInEDT(new Runnable() { 306 @Override 307 public void run() { 308 if (dialog != null) { 309 dialog.setVisible(false); 310 dialog.setCancelCallback(null); 311 dialog.setInBackgroundCallback(null); 312 dialog.removeWindowListener(windowListener); 313 dialog.dispose(); 314 dialog = null; 315 Main.currentProgressMonitor = null; 316 MapFrame map = Main.map; 317 if (map != null) { 318 map.statusLine.progressMonitor.setVisible(false); 319 } 320 } 321 } 322 }); 323 } 324 325 public void showForegroundDialog() { 326 isInBackground = false; 327 doInEDT(new Runnable() { 328 @Override 329 public void run() { 330 if (dialog != null) { 331 dialog.setInBackgroundPossible(PleaseWaitProgressMonitor.this.taskId != null && Main.isDisplayingMapView()); 332 reset(); 333 getDialog(); 334 } 335 } 336 }); 337 338 } 339 340 @Override 341 public void setProgressTaskId(ProgressTaskId taskId) { 342 this.taskId = taskId; 343 doInEDT(new Runnable() { 344 @Override 345 public void run() { 346 if (dialog != null) { 347 dialog.setInBackgroundPossible(PleaseWaitProgressMonitor.this.taskId != null && Main.isDisplayingMapView()); 348 } 349 } 350 }); 351 } 352 353 @Override 354 public ProgressTaskId getProgressTaskId() { 355 return taskId; 356 } 357 358 @Override 359 public Component getWindowParent() { 360 Component parent = dialog; 361 if (isInBackground || parent == null) 362 return Main.parent; 363 else 364 return parent; 365 } 366}