001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.progress;
003    
004    import java.awt.Component;
005    import java.awt.Dialog;
006    import java.awt.Frame;
007    import java.awt.Window;
008    import java.awt.event.ActionEvent;
009    import java.awt.event.ActionListener;
010    import java.awt.event.WindowAdapter;
011    import java.awt.event.WindowEvent;
012    import java.awt.event.WindowListener;
013    
014    import javax.swing.JOptionPane;
015    import javax.swing.SwingUtilities;
016    
017    import org.openstreetmap.josm.Main;
018    import org.openstreetmap.josm.gui.MapFrame;
019    import org.openstreetmap.josm.gui.MapStatus.BackgroundProgressMonitor;
020    import org.openstreetmap.josm.gui.PleaseWaitDialog;
021    
022    public 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 = 100;
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            public void actionPerformed(ActionEvent e) {
119                cancel();
120            }
121        };
122    
123        private ActionListener inBackgroundListener = new ActionListener() {
124            @Override
125            public void actionPerformed(ActionEvent e) {
126                isInBackground = true;
127                ProgressMonitorDialog dialog = getDialog();
128                if (dialog != null) {
129                    reset();
130                    dialog.setVisible(true);
131                }
132            }
133        };
134    
135        private WindowListener windowListener = new WindowAdapter(){
136            @Override public void windowClosing(WindowEvent e) {
137                cancel();
138            }
139        };
140    
141        public final boolean isCancelable() {
142            return cancelable;
143        }
144    
145        public final void setCancelable(boolean cancelable) {
146            this.cancelable = cancelable;
147        }
148    
149        @Override
150        public void doBeginTask() {
151            doInEDT(new Runnable() {
152                public void run() {
153                    Main.currentProgressMonitor = PleaseWaitProgressMonitor.this;
154                    if (dialogParent instanceof Frame && dialog == null) {
155                        dialog = new PleaseWaitDialog(dialogParent);
156                    } else if (dialogParent instanceof Dialog && dialog == null) {
157                        dialog = new PleaseWaitDialog(dialogParent);
158                    } else
159                        throw new ProgressException("PleaseWaitDialog parent must be either Frame or Dialog");
160    
161                    if (windowTitle != null) {
162                        dialog.setTitle(windowTitle);
163                    }
164                    dialog.setCancelEnabled(cancelable);
165                    dialog.setCancelCallback(cancelListener);
166                    dialog.setInBackgroundCallback(inBackgroundListener);
167                    dialog.setCustomText("");
168                    dialog.addWindowListener(windowListener);
169                    dialog.progress.setMaximum(PROGRESS_BAR_MAX);
170                    dialog.setVisible(true);
171                }
172            });
173        }
174    
175        @Override
176        public void doFinishTask() {
177            // do nothing
178        }
179    
180        @Override
181        protected void updateProgress(double progressValue) {
182            final int newValue = (int)(progressValue * PROGRESS_BAR_MAX);
183            if (newValue != currentProgressValue) {
184                currentProgressValue = newValue;
185                doInEDT(new Runnable() {
186                    public void run() {
187                        ProgressMonitorDialog dialog = getDialog();
188                        if (dialog != null) {
189                            dialog.updateProgress(currentProgressValue);
190                        }
191                    }
192                });
193            }
194        }
195    
196        @Override
197        protected void doSetCustomText(final String title) {
198            checkState(State.IN_TASK, State.IN_SUBTASK);
199            this.customText = title;
200            doInEDT(new Runnable() {
201                public void run() {
202                    ProgressMonitorDialog dialog = getDialog();
203                    if (dialog != null) {
204                        dialog.setCustomText(title);
205                    }
206                }
207            });
208        }
209    
210        @Override
211        protected void doSetTitle(final String title) {
212            checkState(State.IN_TASK, State.IN_SUBTASK);
213            this.title = title;
214            doInEDT(new Runnable() {
215                public void run() {
216                    ProgressMonitorDialog dialog = getDialog();
217                    if (dialog != null) {
218                        dialog.setCurrentAction(title);
219                    }
220                }
221            });
222        }
223    
224        @Override
225        protected void doSetIntermediate(final boolean value) {
226            this.indeterminate = value;
227            doInEDT(new Runnable() {
228                public void run() {
229                    // Enable only if progress is at the beginning. Doing intermediate progress in the middle
230                    // will hide already reached progress
231                    ProgressMonitorDialog dialog = getDialog();
232                    if (dialog != null) {
233                        dialog.setIndeterminate(value && currentProgressValue == 0);
234                    }
235                }
236            });
237        }
238    
239        @Override
240        public void appendLogMessage(final String message) {
241            doInEDT(new Runnable() {
242                public void run() {
243                    ProgressMonitorDialog dialog = getDialog();
244                    if (dialog != null) {
245                        dialog.appendLogMessage(message);
246                    }
247                }
248            });
249        }
250    
251        public void reset() {
252            if (dialog != null) {
253                dialog.setTitle(title);
254                dialog.setCustomText(customText);
255                dialog.updateProgress(currentProgressValue);
256                dialog.setIndeterminate(indeterminate && currentProgressValue == 0);
257            }
258            BackgroundProgressMonitor backgroundMonitor = null;
259            MapFrame map = Main.map;
260            if (map != null) {
261                backgroundMonitor = map.statusLine.progressMonitor;
262            }
263            if (backgroundMonitor != null) {
264                backgroundMonitor.setCurrentAction(title);
265                backgroundMonitor.setCustomText(customText);
266                backgroundMonitor.updateProgress(currentProgressValue);
267                backgroundMonitor.setIndeterminate(indeterminate && currentProgressValue == 0);
268            }
269    
270        }
271    
272        public void close() {
273            doInEDT(new Runnable() {
274                @Override
275                public void run() {
276                    if (dialog != null) {
277                        dialog.setVisible(false);
278                        dialog.setCancelCallback(null);
279                        dialog.setInBackgroundCallback(null);
280                        dialog.removeWindowListener(windowListener);
281                        dialog.dispose();
282                        dialog = null;
283                        Main.currentProgressMonitor = null;
284                        MapFrame map = Main.map;
285                        if (map != null) {
286                            map.statusLine.progressMonitor.setVisible(false);
287                        }
288                    }
289                }
290            });
291        }
292    
293        public void showForegroundDialog() {
294            isInBackground = false;
295            doInEDT(new Runnable() {
296                @Override
297                public void run() {
298                    if (dialog != null) {
299                        dialog.setInBackgroundPossible(PleaseWaitProgressMonitor.this.taskId != null && Main.isDisplayingMapView());
300                        reset();
301                        getDialog();
302                    }
303                }
304            });
305    
306        }
307    
308        @Override
309        public void setProgressTaskId(ProgressTaskId taskId) {
310            this.taskId = taskId;
311            doInEDT(new Runnable() {
312                @Override
313                public void run() {
314                    dialog.setInBackgroundPossible(PleaseWaitProgressMonitor.this.taskId != null && Main.isDisplayingMapView());
315                }
316            });
317        }
318    
319        @Override
320        public ProgressTaskId getProgressTaskId() {
321            return taskId;
322        }
323    
324    
325        @Override
326        public Component getWindowParent() {
327            Component parent = dialog;
328            if (isInBackground || parent == null)
329                return Main.parent;
330            else
331                return parent;
332        }
333    }