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}