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