001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.dialogs.changeset;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.BorderLayout;
007import java.awt.Component;
008import java.awt.FlowLayout;
009import java.awt.Rectangle;
010import java.awt.event.ActionEvent;
011import java.beans.PropertyChangeEvent;
012import java.beans.PropertyChangeListener;
013import java.util.Collections;
014
015import javax.swing.AbstractAction;
016import javax.swing.BorderFactory;
017import javax.swing.JOptionPane;
018import javax.swing.JPanel;
019import javax.swing.JScrollPane;
020import javax.swing.JTable;
021import javax.swing.JToolBar;
022import javax.swing.SwingConstants;
023
024import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
025import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
026import org.openstreetmap.josm.data.osm.Changeset;
027import org.openstreetmap.josm.gui.MainApplication;
028import org.openstreetmap.josm.gui.NoteInputDialog;
029import org.openstreetmap.josm.gui.widgets.LargeTextTable;
030import org.openstreetmap.josm.io.NetworkManager;
031import org.openstreetmap.josm.io.OnlineResource;
032import org.openstreetmap.josm.io.OsmApi;
033import org.openstreetmap.josm.io.OsmTransferException;
034import org.openstreetmap.josm.tools.ExceptionUtil;
035import org.openstreetmap.josm.tools.ImageProvider;
036import org.openstreetmap.josm.tools.Logging;
037
038/**
039 * The panel which displays the public discussion around a changeset in a scrollable table.
040 *
041 * It listens to property change events for {@link ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP}
042 * and updates its view accordingly.
043 *
044 * @since 7704
045 */
046public class ChangesetDiscussionPanel extends JPanel implements PropertyChangeListener {
047
048    private final UpdateChangesetDiscussionAction actUpdateChangesets = new UpdateChangesetDiscussionAction();
049    private final AddChangesetCommentAction actAddChangesetComment = new AddChangesetCommentAction();
050
051    private final ChangesetDiscussionTableModel model = new ChangesetDiscussionTableModel();
052
053    private JTable table;
054
055    private transient Changeset current;
056
057    protected JPanel buildActionButtonPanel() {
058        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
059
060        JToolBar tb = new JToolBar(SwingConstants.VERTICAL);
061        tb.setFloatable(false);
062
063        // -- changeset discussion update
064        tb.add(actUpdateChangesets);
065        // -- add a comment to changeset discussion
066        tb.add(actAddChangesetComment);
067
068        initProperties();
069
070        pnl.add(tb);
071        return pnl;
072    }
073
074    void initProperties() {
075        actUpdateChangesets.initProperties(current);
076        actAddChangesetComment.initProperties(current);
077    }
078
079    /**
080     * Updates the current changeset discussion from the OSM server
081     */
082    class UpdateChangesetDiscussionAction extends AbstractAction {
083        UpdateChangesetDiscussionAction() {
084            putValue(NAME, tr("Update changeset discussion"));
085            new ImageProvider("dialogs/changeset", "updatechangesetcontent").getResource().attachImageIcon(this);
086            putValue(SHORT_DESCRIPTION, tr("Update the changeset discussion from the OSM server"));
087        }
088
089        @Override
090        public void actionPerformed(ActionEvent evt) {
091            if (current == null)
092                return;
093            ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(
094                    ChangesetDiscussionPanel.this,
095                    Collections.singleton(current.getId()),
096                    true /* include discussion */
097            );
098            MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
099        }
100
101        void initProperties(Changeset cs) {
102            setEnabled(cs != null && !NetworkManager.isOffline(OnlineResource.OSM_API));
103        }
104    }
105
106    /**
107     * Adds a discussion comment to the current changeset
108     */
109    class AddChangesetCommentAction extends AbstractAction {
110        AddChangesetCommentAction() {
111            putValue(NAME, tr("Comment"));
112            new ImageProvider("dialogs/notes", "note_comment").getResource().attachImageIcon(this);
113            putValue(SHORT_DESCRIPTION, tr("Add comment"));
114        }
115
116        @Override
117        public void actionPerformed(ActionEvent evt) {
118            if (current == null)
119                return;
120            NoteInputDialog dialog = new NoteInputDialog(MainApplication.getMainFrame(), tr("Comment on changeset"), tr("Add comment"));
121            dialog.showNoteDialog(tr("Add comment to changeset:"), ImageProvider.get("dialogs/notes", "note_comment"));
122            if (dialog.getValue() != 1) {
123                return;
124            }
125            try {
126                OsmApi.getOsmApi().addCommentToChangeset(current, dialog.getInputText(), null);
127                actUpdateChangesets.actionPerformed(null);
128            } catch (OsmTransferException | IllegalArgumentException e) {
129                Logging.error(e);
130                JOptionPane.showMessageDialog(
131                        MainApplication.getMainFrame(),
132                        ExceptionUtil.explainException(e),
133                        tr("Error"),
134                        JOptionPane.ERROR_MESSAGE);
135            }
136        }
137
138        void initProperties(Changeset cs) {
139            setEnabled(cs != null && !cs.isOpen() && !NetworkManager.isOffline(OnlineResource.OSM_API));
140        }
141    }
142
143    /**
144     * Constructs a new {@code ChangesetDiscussionPanel}.
145     */
146    public ChangesetDiscussionPanel() {
147        build();
148    }
149
150    protected void setCurrentChangeset(Changeset cs) {
151        current = cs;
152        if (cs == null) {
153            clearView();
154        } else {
155            updateView(cs);
156        }
157        initProperties();
158        if (cs != null && cs.getDiscussion().size() < cs.getCommentsCount()) {
159            actUpdateChangesets.actionPerformed(null);
160        }
161    }
162
163    protected final void build() {
164        setLayout(new BorderLayout());
165        setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
166        add(buildActionButtonPanel(), BorderLayout.WEST);
167        add(buildDiscussionPanel(), BorderLayout.CENTER);
168    }
169
170    private Component buildDiscussionPanel() {
171        JPanel pnl = new JPanel(new BorderLayout());
172        table = new LargeTextTable(model, new ChangesetDiscussionTableColumnModel());
173        table.setRowSorter(new ChangesetDiscussionTableRowSorter(model));
174        table.getTableHeader().setReorderingAllowed(false);
175
176        table.getColumnModel().getColumn(2).addPropertyChangeListener(evt -> {
177            if ("width".equals(evt.getPropertyName())) {
178                updateRowHeights();
179            }
180        });
181        pnl.add(new JScrollPane(table), BorderLayout.CENTER);
182        return pnl;
183    }
184
185    protected void clearView() {
186        model.populate(null);
187    }
188
189    protected void updateView(Changeset cs) {
190        model.populate(cs.getDiscussion());
191        updateRowHeights();
192    }
193
194    protected void updateRowHeights() {
195        int intercellWidth = table.getIntercellSpacing().width;
196        int colWidth = table.getColumnModel().getColumn(2).getWidth();
197        // Update row heights
198        for (int row = 0; row < table.getRowCount(); row++) {
199            int rowHeight = table.getRowHeight();
200
201            Component comp = table.prepareRenderer(table.getCellRenderer(row, 2), row, 2);
202            // constrain width of component
203            comp.setBounds(new Rectangle(0, 0, colWidth - intercellWidth, Integer.MAX_VALUE));
204            rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
205
206            table.setRowHeight(row, rowHeight);
207        }
208    }
209
210    /* ---------------------------------------------------------------------------- */
211    /* interface PropertyChangeListener                                             */
212    /* ---------------------------------------------------------------------------- */
213    @Override
214    public void propertyChange(PropertyChangeEvent evt) {
215        if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
216            return;
217        setCurrentChangeset((Changeset) evt.getNewValue());
218    }
219}