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.SwingUtilities;
013
014import org.openstreetmap.josm.Main;
015import org.openstreetmap.josm.gui.MapFrame;
016import org.openstreetmap.josm.gui.MapStatus.BackgroundProgressMonitor;
017import org.openstreetmap.josm.gui.PleaseWaitDialog;
018import org.openstreetmap.josm.gui.util.GuiHelper;
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 = GuiHelper.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(GuiHelper.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}