001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.GridBagConstraints;
008import java.awt.GridBagLayout;
009import java.awt.event.ActionListener;
010import java.awt.event.ComponentAdapter;
011import java.awt.event.ComponentEvent;
012
013import javax.swing.BorderFactory;
014import javax.swing.BoundedRangeModel;
015import javax.swing.JButton;
016import javax.swing.JDialog;
017import javax.swing.JLabel;
018import javax.swing.JPanel;
019import javax.swing.JProgressBar;
020import javax.swing.JScrollPane;
021import javax.swing.UIManager;
022
023import org.openstreetmap.josm.Main;
024import org.openstreetmap.josm.gui.progress.PleaseWaitProgressMonitor.ProgressMonitorDialog;
025import org.openstreetmap.josm.gui.util.GuiHelper;
026import org.openstreetmap.josm.gui.widgets.JosmTextArea;
027import org.openstreetmap.josm.tools.GBC;
028import org.openstreetmap.josm.tools.ImageProvider;
029
030public class PleaseWaitDialog extends JDialog implements ProgressMonitorDialog {
031
032    private final JProgressBar progressBar = new JProgressBar();
033
034    private final JLabel currentAction = new JLabel("");
035    private final JLabel customText = new JLabel("");
036    public final transient BoundedRangeModel progress = progressBar.getModel();
037    private JButton btnCancel;
038    private JButton btnInBackground;
039    /** the text area and the scroll pane for the log */
040    private final JosmTextArea taLog = new JosmTextArea(5, 50);
041    private final JScrollPane spLog = new JScrollPane(taLog);
042
043    private void initDialog() {
044        setLayout(new GridBagLayout());
045        JPanel pane = new JPanel(new GridBagLayout());
046        pane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
047        pane.add(currentAction, GBC.eol().fill(GBC.HORIZONTAL));
048        pane.add(customText, GBC.eol().fill(GBC.HORIZONTAL));
049        pane.add(progressBar, GBC.eop().fill(GBC.HORIZONTAL));
050        JPanel buttons = new JPanel(new GridBagLayout());
051        btnCancel = new JButton(tr("Cancel"));
052        btnCancel.setIcon(ImageProvider.get("cancel"));
053        btnCancel.setToolTipText(tr("Click to cancel the current operation"));
054        buttons.add(btnCancel);
055        btnInBackground = new JButton(tr("In background"));
056        btnInBackground.setToolTipText(tr("Click to run job in background"));
057        buttons.add(btnInBackground, GBC.std().fill(GBC.VERTICAL).insets(5, 0, 0, 0));
058        pane.add(buttons, GBC.eol().anchor(GBC.CENTER));
059        GridBagConstraints gc = GBC.eol().fill(GBC.BOTH);
060        gc.weighty = 1.0;
061        gc.weightx = 1.0;
062        pane.add(spLog, gc);
063        spLog.setVisible(false);
064        setContentPane(pane);
065        setCustomText("");
066        setLocationRelativeTo(getParent());
067        addComponentListener(new ComponentAdapter() {
068            @Override
069            public void componentResized(ComponentEvent ev) {
070                int w = getWidth();
071                if (w > 200) {
072                    Main.pref.putInteger("progressdialog.size", w);
073                }
074            }
075        });
076    }
077
078    /**
079     * Constructs a new {@code PleaseWaitDialog}.
080     * @param parent the {@code Component} from which the dialog is displayed. Can be {@code null}.
081     */
082    public PleaseWaitDialog(Component parent) {
083        super(GuiHelper.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL);
084        initDialog();
085    }
086
087    @Override
088    public void setIndeterminate(boolean newValue) {
089        UIManager.put("ProgressBar.cycleTime", UIManager.getInt("ProgressBar.repaintInterval") * 100);
090        progressBar.setIndeterminate(newValue);
091    }
092
093    protected void adjustLayout() {
094        invalidate();
095        setDropTarget(null); // Workaround to JDK bug 7027598/7100524/7169912 (#8613)
096        pack();
097        setSize(Main.pref.getInteger("progressdialog.size", 600), getSize().height);
098    }
099
100    /**
101     * Sets a custom text line below currentAction. Can be used to display additional information
102     * @param text custom text
103     */
104    @Override
105    public void setCustomText(String text) {
106        if (text == null || text.trim().isEmpty()) {
107            customText.setVisible(false);
108            adjustLayout();
109            return;
110        }
111        customText.setText(text);
112        if (!customText.isVisible()) {
113            customText.setVisible(true);
114            adjustLayout();
115        }
116    }
117
118    @Override
119    public void setCurrentAction(String text) {
120        currentAction.setText(text);
121    }
122
123    /**
124     * Appends a log message to the progress dialog. If the log area isn't visible yet
125     * it becomes visible. The height of the progress dialog is slightly increased too.
126     *
127     * @param message the message to append to the log. Ignore if null or white space only.
128     */
129    @Override
130    public void appendLogMessage(String message) {
131        if (message == null || message.trim().isEmpty())
132            return;
133        if (!spLog.isVisible()) {
134            spLog.setVisible(true);
135            taLog.setVisible(true);
136            adjustLayout();
137        }
138        taLog.append(message);
139        taLog.append("\n");
140        spLog.getVerticalScrollBar().setValue(spLog.getVerticalScrollBar().getMaximum());
141    }
142
143    /**
144     * Sets whether the cancel button is enabled or not
145     *
146     * @param enabled true, if the cancel button is enabled; false otherwise
147     */
148    public void setCancelEnabled(boolean enabled) {
149        btnCancel.setEnabled(enabled);
150    }
151
152    public void setInBackgroundPossible(boolean value) {
153        btnInBackground.setVisible(value);
154    }
155
156    /**
157     * Installs a callback for the cancel button. If callback is null, all action listeners
158     * are removed from the cancel button.
159     *
160     * @param callback the cancel callback
161     */
162    public void setCancelCallback(ActionListener callback) {
163        if (callback == null) {
164            ActionListener[] listeners = btnCancel.getActionListeners();
165            for (ActionListener l: listeners) {
166                btnCancel.removeActionListener(l);
167            }
168        } else {
169            btnCancel.addActionListener(callback);
170        }
171    }
172
173    /**
174     * Installs a callback for the "In background" button. If callback is null, all action listeners
175     * are removed from the cancel button.
176     *
177     * @param callback the cancel callback
178     */
179    public void setInBackgroundCallback(ActionListener callback) {
180        if (callback == null) {
181            ActionListener[] listeners = btnInBackground.getActionListeners();
182            for (ActionListener l: listeners) {
183                btnInBackground.removeActionListener(l);
184            }
185        } else {
186            btnInBackground.addActionListener(callback);
187        }
188    }
189
190    @Override
191    public void updateProgress(int progress) {
192        this.progress.setValue(progress);
193        this.progressBar.repaint();
194    }
195}