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}