001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset.query;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.BorderLayout;
007import java.awt.Container;
008import java.awt.Dimension;
009import java.awt.FlowLayout;
010import java.awt.Window;
011import java.awt.event.ActionEvent;
012import java.awt.event.WindowAdapter;
013import java.awt.event.WindowEvent;
014
015import javax.swing.AbstractAction;
016import javax.swing.JButton;
017import javax.swing.JDialog;
018import javax.swing.JOptionPane;
019import javax.swing.JPanel;
020import javax.swing.JTabbedPane;
021
022import org.openstreetmap.josm.gui.HelpAwareOptionPane;
023import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
024import org.openstreetmap.josm.gui.help.HelpUtil;
025import org.openstreetmap.josm.gui.util.WindowGeometry;
026import org.openstreetmap.josm.io.ChangesetQuery;
027import org.openstreetmap.josm.tools.ImageProvider;
028import org.openstreetmap.josm.tools.InputMapUtils;
029import org.openstreetmap.josm.tools.Logging;
030
031/**
032 * This is a modal dialog for entering query criteria to search for changesets.
033 * @since 2689
034 */
035public class ChangesetQueryDialog extends JDialog {
036
037    private JTabbedPane tpQueryPanels;
038    private final BasicChangesetQueryPanel pnlBasicChangesetQueries = new BasicChangesetQueryPanel();
039    private final UrlBasedQueryPanel pnlUrlBasedQueries = new UrlBasedQueryPanel();
040    private final AdvancedChangesetQueryPanel pnlAdvancedQueries = new AdvancedChangesetQueryPanel();
041    private boolean canceled;
042
043    /**
044     * Constructs a new {@code ChangesetQueryDialog}.
045     * @param parent parent window
046     */
047    public ChangesetQueryDialog(Window parent) {
048        super(parent, ModalityType.DOCUMENT_MODAL);
049        build();
050    }
051
052    protected JPanel buildContentPanel() {
053        tpQueryPanels = new JTabbedPane();
054        tpQueryPanels.add(pnlBasicChangesetQueries);
055        tpQueryPanels.add(pnlUrlBasedQueries);
056        tpQueryPanels.add(pnlAdvancedQueries);
057
058        tpQueryPanels.setTitleAt(0, tr("Basic"));
059        tpQueryPanels.setToolTipTextAt(0, tr("Download changesets using predefined queries"));
060
061        tpQueryPanels.setTitleAt(1, tr("From URL"));
062        tpQueryPanels.setToolTipTextAt(1, tr("Query changesets from a server URL"));
063
064        tpQueryPanels.setTitleAt(2, tr("Advanced"));
065        tpQueryPanels.setToolTipTextAt(2, tr("Use a custom changeset query"));
066
067        JPanel pnl = new JPanel(new BorderLayout());
068        pnl.add(tpQueryPanels, BorderLayout.CENTER);
069        return pnl;
070    }
071
072    protected JPanel buildButtonPanel() {
073        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.CENTER));
074
075        final JButton queryButton = new JButton(new QueryAction());
076        queryButton.setName("queryButton");
077        pnl.add(queryButton);
078        final JButton cancelButton = new JButton(new CancelAction());
079        cancelButton.setName("cancelButton");
080        pnl.add(cancelButton);
081        pnl.add(new JButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/ChangesetQuery"))));
082
083        return pnl;
084    }
085
086    protected final void build() {
087        setTitle(tr("Query changesets"));
088        Container cp = getContentPane();
089        cp.setLayout(new BorderLayout());
090        cp.add(buildContentPanel(), BorderLayout.CENTER);
091        cp.add(buildButtonPanel(), BorderLayout.SOUTH);
092
093        // cancel on ESC
094        InputMapUtils.addEscapeAction(getRootPane(), new CancelAction());
095
096        // context sensitive help
097        HelpUtil.setHelpContext(getRootPane(), HelpUtil.ht("/Dialog/ChangesetQuery"));
098
099        addWindowListener(new WindowEventHandler());
100    }
101
102    /**
103     * Determines if the dialog has been canceled.
104     * @return {@code true} if the dialog has been canceled
105     */
106    public boolean isCanceled() {
107        return canceled;
108    }
109
110    /**
111     * Initializes HMI for user input.
112     */
113    public void initForUserInput() {
114        pnlBasicChangesetQueries.init();
115    }
116
117    protected void setCanceled(boolean canceled) {
118        this.canceled = canceled;
119    }
120
121    /**
122     * Returns the changeset query.
123     * @return the changeset query
124     */
125    public ChangesetQuery getChangesetQuery() {
126        if (isCanceled())
127            return null;
128        switch(tpQueryPanels.getSelectedIndex()) {
129        case 0:
130            return pnlBasicChangesetQueries.buildChangesetQuery();
131        case 1:
132            return pnlUrlBasedQueries.buildChangesetQuery();
133        case 2:
134            return pnlAdvancedQueries.buildChangesetQuery();
135        default:
136            // FIXME: extend with advanced queries
137            return null;
138        }
139    }
140
141    /**
142     * Initializes HMI for user input.
143     */
144    public void startUserInput() {
145        pnlUrlBasedQueries.startUserInput();
146        pnlAdvancedQueries.startUserInput();
147    }
148
149    @Override
150    public void setVisible(boolean visible) {
151        if (visible) {
152            new WindowGeometry(
153                    getClass().getName() + ".geometry",
154                    WindowGeometry.centerInWindow(
155                            getParent(),
156                            new Dimension(600, 400)
157                    )
158            ).applySafe(this);
159            setCanceled(false);
160            startUserInput();
161        } else if (isShowing()) { // Avoid IllegalComponentStateException like in #8775
162            new WindowGeometry(this).remember(getClass().getName() + ".geometry");
163            pnlAdvancedQueries.rememberSettings();
164        }
165        super.setVisible(visible);
166    }
167
168    class QueryAction extends AbstractAction {
169        QueryAction() {
170            putValue(NAME, tr("Query"));
171            new ImageProvider("dialogs", "search").getResource().attachImageIcon(this);
172            putValue(SHORT_DESCRIPTION, tr("Query and download changesets"));
173        }
174
175        protected void alertInvalidChangesetQuery() {
176            HelpAwareOptionPane.showOptionDialog(
177                    ChangesetQueryDialog.this,
178                    tr("Please enter a valid changeset query URL first."),
179                    tr("Illegal changeset query URL"),
180                    JOptionPane.WARNING_MESSAGE,
181                    HelpUtil.ht("/Dialog/ChangesetQueryDialog#EnterAValidChangesetQueryUrlFirst")
182            );
183        }
184
185        @Override
186        public void actionPerformed(ActionEvent arg0) {
187            try {
188                switch(tpQueryPanels.getSelectedIndex()) {
189                case 0:
190                    // currently, query specifications can't be invalid in the basic query panel.
191                    // We select from a couple of predefined queries and there is always a query
192                    // selected
193                    break;
194                case 1:
195                    if (getChangesetQuery() == null) {
196                        alertInvalidChangesetQuery();
197                        pnlUrlBasedQueries.startUserInput();
198                        return;
199                    }
200                    break;
201                case 2:
202                    if (getChangesetQuery() == null) {
203                        pnlAdvancedQueries.displayMessageIfInvalid();
204                        return;
205                    }
206                }
207                setCanceled(false);
208                setVisible(false);
209            } catch (IllegalStateException e) {
210                Logging.error(e);
211                JOptionPane.showMessageDialog(ChangesetQueryDialog.this, e.getMessage(), tr("Error"), JOptionPane.ERROR_MESSAGE);
212            }
213        }
214    }
215
216    class CancelAction extends AbstractAction {
217
218        CancelAction() {
219            putValue(NAME, tr("Cancel"));
220            new ImageProvider("cancel").getResource().attachImageIcon(this);
221            putValue(SHORT_DESCRIPTION, tr("Close the dialog and abort querying of changesets"));
222        }
223
224        public void cancel() {
225            setCanceled(true);
226            setVisible(false);
227        }
228
229        @Override
230        public void actionPerformed(ActionEvent arg0) {
231            cancel();
232        }
233    }
234
235    class WindowEventHandler extends WindowAdapter {
236        @Override
237        public void windowClosing(WindowEvent arg0) {
238            new CancelAction().cancel();
239        }
240    }
241}