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.JPanel;
018import javax.swing.JScrollPane;
019import javax.swing.JTable;
020import javax.swing.JToolBar;
021
022import org.openstreetmap.josm.Main;
023import org.openstreetmap.josm.actions.downloadtasks.ChangesetHeaderDownloadTask;
024import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
025import org.openstreetmap.josm.data.osm.Changeset;
026import org.openstreetmap.josm.gui.MainApplication;
027import org.openstreetmap.josm.io.OnlineResource;
028import org.openstreetmap.josm.tools.ImageProvider;
029
030/**
031 * The panel which displays the public discussion around a changeset in a scrollable table.
032 *
033 * It listens to property change events for {@link ChangesetCacheManagerModel#CHANGESET_IN_DETAIL_VIEW_PROP}
034 * and updates its view accordingly.
035 *
036 * @since 7704
037 */
038public class ChangesetDiscussionPanel extends JPanel implements PropertyChangeListener {
039
040    private final UpdateChangesetDiscussionAction actUpdateChangesets = new UpdateChangesetDiscussionAction();
041
042    private final ChangesetDiscussionTableModel model = new ChangesetDiscussionTableModel();
043
044    private JTable table;
045
046    private transient Changeset current;
047
048    protected JPanel buildActionButtonPanel() {
049        JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
050
051        JToolBar tb = new JToolBar(JToolBar.VERTICAL);
052        tb.setFloatable(false);
053
054        // -- changeset discussion update
055        tb.add(actUpdateChangesets);
056        actUpdateChangesets.initProperties(current);
057
058        pnl.add(tb);
059        return pnl;
060    }
061
062    /**
063     * Updates the current changeset discussion from the OSM server
064     */
065    class UpdateChangesetDiscussionAction extends AbstractAction {
066        UpdateChangesetDiscussionAction() {
067            putValue(NAME, tr("Update changeset discussion"));
068            new ImageProvider("dialogs/changeset", "updatechangesetcontent").getResource().attachImageIcon(this);
069            putValue(SHORT_DESCRIPTION, tr("Update the changeset discussion from the OSM server"));
070        }
071
072        @Override
073        public void actionPerformed(ActionEvent evt) {
074            if (current == null)
075                return;
076            ChangesetHeaderDownloadTask task = new ChangesetHeaderDownloadTask(
077                    ChangesetDiscussionPanel.this,
078                    Collections.singleton(current.getId()),
079                    true /* include discussion */
080            );
081            MainApplication.worker.submit(new PostDownloadHandler(task, task.download()));
082        }
083
084        public void initProperties(Changeset cs) {
085            setEnabled(cs != null && !Main.isOffline(OnlineResource.OSM_API));
086        }
087    }
088
089    /**
090     * Constructs a new {@code ChangesetDiscussionPanel}.
091     */
092    public ChangesetDiscussionPanel() {
093        build();
094    }
095
096    protected void setCurrentChangeset(Changeset cs) {
097        current = cs;
098        if (cs == null) {
099            clearView();
100        } else {
101            updateView(cs);
102        }
103        actUpdateChangesets.initProperties(current);
104        if (cs != null && cs.getDiscussion().size() < cs.getCommentsCount()) {
105            actUpdateChangesets.actionPerformed(null);
106        }
107    }
108
109    protected final void build() {
110        setLayout(new BorderLayout());
111        setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
112        add(buildActionButtonPanel(), BorderLayout.WEST);
113        add(buildDiscussionPanel(), BorderLayout.CENTER);
114    }
115
116    private Component buildDiscussionPanel() {
117        JPanel pnl = new JPanel(new BorderLayout());
118        table = new JTable(model, new ChangesetDiscussionTableColumnModel());
119        table.getColumnModel().getColumn(2).addPropertyChangeListener(evt -> {
120            if ("width".equals(evt.getPropertyName())) {
121                updateRowHeights();
122            }
123        });
124        pnl.add(new JScrollPane(table), BorderLayout.CENTER);
125        return pnl;
126    }
127
128    protected void clearView() {
129        model.populate(null);
130    }
131
132    protected void updateView(Changeset cs) {
133        model.populate(cs.getDiscussion());
134        updateRowHeights();
135    }
136
137    protected void updateRowHeights() {
138        int intercellWidth = table.getIntercellSpacing().width;
139        int colWidth = table.getColumnModel().getColumn(2).getWidth();
140        // Update row heights
141        for (int row = 0; row < table.getRowCount(); row++) {
142            int rowHeight = table.getRowHeight();
143
144            Component comp = table.prepareRenderer(table.getCellRenderer(row, 2), row, 2);
145            // constrain width of component
146            comp.setBounds(new Rectangle(0, 0, colWidth - intercellWidth, Integer.MAX_VALUE));
147            rowHeight = Math.max(rowHeight, comp.getPreferredSize().height);
148
149            table.setRowHeight(row, rowHeight);
150        }
151    }
152
153    /* ---------------------------------------------------------------------------- */
154    /* interface PropertyChangeListener                                             */
155    /* ---------------------------------------------------------------------------- */
156    @Override
157    public void propertyChange(PropertyChangeEvent evt) {
158        if (!evt.getPropertyName().equals(ChangesetCacheManagerModel.CHANGESET_IN_DETAIL_VIEW_PROP))
159            return;
160        setCurrentChangeset((Changeset) evt.getNewValue());
161    }
162}