source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/NotesDialog.java@ 15889

Last change on this file since 15889 was 15385, checked in by Don-vip, 5 years ago

fix #18178 - NPE

  • Property svn:eol-style set to native
File size: 16.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.event.ActionEvent;
9import java.awt.event.MouseAdapter;
10import java.awt.event.MouseEvent;
11import java.text.DateFormat;
12import java.util.ArrayList;
13import java.util.Arrays;
14import java.util.Collection;
15import java.util.List;
16import java.util.Objects;
17
18import javax.swing.AbstractAction;
19import javax.swing.AbstractListModel;
20import javax.swing.DefaultListCellRenderer;
21import javax.swing.ImageIcon;
22import javax.swing.JLabel;
23import javax.swing.JList;
24import javax.swing.JOptionPane;
25import javax.swing.JPanel;
26import javax.swing.JScrollPane;
27import javax.swing.ListCellRenderer;
28import javax.swing.ListSelectionModel;
29import javax.swing.SwingUtilities;
30
31import org.openstreetmap.josm.actions.DownloadNotesInViewAction;
32import org.openstreetmap.josm.actions.UploadNotesAction;
33import org.openstreetmap.josm.actions.mapmode.AddNoteAction;
34import org.openstreetmap.josm.data.notes.Note;
35import org.openstreetmap.josm.data.notes.Note.State;
36import org.openstreetmap.josm.data.notes.NoteComment;
37import org.openstreetmap.josm.data.osm.NoteData;
38import org.openstreetmap.josm.data.osm.NoteData.NoteDataUpdateListener;
39import org.openstreetmap.josm.gui.MainApplication;
40import org.openstreetmap.josm.gui.MapFrame;
41import org.openstreetmap.josm.gui.NoteInputDialog;
42import org.openstreetmap.josm.gui.NoteSortDialog;
43import org.openstreetmap.josm.gui.SideButton;
44import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
45import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
46import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
47import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
48import org.openstreetmap.josm.gui.layer.NoteLayer;
49import org.openstreetmap.josm.spi.preferences.Config;
50import org.openstreetmap.josm.tools.ImageProvider;
51import org.openstreetmap.josm.tools.OpenBrowser;
52import org.openstreetmap.josm.tools.date.DateUtils;
53
54/**
55 * Dialog to display and manipulate notes.
56 * @since 7852 (renaming)
57 * @since 7608 (creation)
58 */
59public class NotesDialog extends ToggleDialog implements LayerChangeListener, NoteDataUpdateListener {
60
61 private NoteTableModel model;
62 private JList<Note> displayList;
63 private final AddCommentAction addCommentAction;
64 private final CloseAction closeAction;
65 private final DownloadNotesInViewAction downloadNotesInViewAction;
66 private final NewAction newAction;
67 private final ReopenAction reopenAction;
68 private final SortAction sortAction;
69 private final OpenInBrowserAction openInBrowserAction;
70 private final UploadNotesAction uploadAction;
71
72 private transient NoteData noteData;
73
74 /** Creates a new toggle dialog for notes */
75 public NotesDialog() {
76 super(tr("Notes"), "notes/note_open", tr("List of notes"), null, 150);
77 addCommentAction = new AddCommentAction();
78 closeAction = new CloseAction();
79 downloadNotesInViewAction = DownloadNotesInViewAction.newActionWithDownloadIcon();
80 newAction = new NewAction();
81 reopenAction = new ReopenAction();
82 sortAction = new SortAction();
83 openInBrowserAction = new OpenInBrowserAction();
84 uploadAction = new UploadNotesAction();
85 buildDialog();
86 MainApplication.getLayerManager().addLayerChangeListener(this);
87 }
88
89 private void buildDialog() {
90 model = new NoteTableModel();
91 displayList = new JList<>(model);
92 displayList.setCellRenderer(new NoteRenderer());
93 displayList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
94 displayList.addListSelectionListener(e -> {
95 if (noteData != null) { //happens when layer is deleted while note selected
96 noteData.setSelectedNote(displayList.getSelectedValue());
97 }
98 updateButtonStates();
99 });
100 displayList.addMouseListener(new MouseAdapter() {
101 //center view on selected note on double click
102 @Override
103 public void mouseClicked(MouseEvent e) {
104 if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2 && noteData != null && noteData.getSelectedNote() != null) {
105 MainApplication.getMap().mapView.zoomTo(noteData.getSelectedNote().getLatLon());
106 }
107 }
108 });
109
110 JPanel pane = new JPanel(new BorderLayout());
111 pane.add(new JScrollPane(displayList), BorderLayout.CENTER);
112
113 createLayout(pane, false, Arrays.asList(
114 new SideButton(downloadNotesInViewAction, false),
115 new SideButton(newAction, false),
116 new SideButton(addCommentAction, false),
117 new SideButton(closeAction, false),
118 new SideButton(reopenAction, false),
119 new SideButton(sortAction, false),
120 new SideButton(openInBrowserAction, false),
121 new SideButton(uploadAction, false)));
122 updateButtonStates();
123 }
124
125 private void updateButtonStates() {
126 if (noteData == null || noteData.getSelectedNote() == null) {
127 closeAction.setEnabled(false);
128 addCommentAction.setEnabled(false);
129 reopenAction.setEnabled(false);
130 } else if (noteData.getSelectedNote().getState() == State.OPEN) {
131 closeAction.setEnabled(true);
132 addCommentAction.setEnabled(true);
133 reopenAction.setEnabled(false);
134 } else { //note is closed
135 closeAction.setEnabled(false);
136 addCommentAction.setEnabled(false);
137 reopenAction.setEnabled(true);
138 }
139 openInBrowserAction.setEnabled(noteData != null && noteData.getSelectedNote() != null && noteData.getSelectedNote().getId() > 0);
140 uploadAction.setEnabled(noteData != null && noteData.isModified());
141 //enable sort button if any notes are loaded
142 sortAction.setEnabled(noteData != null && !noteData.getNotes().isEmpty());
143 }
144
145 @Override
146 public void layerAdded(LayerAddEvent e) {
147 if (e.getAddedLayer() instanceof NoteLayer) {
148 noteData = ((NoteLayer) e.getAddedLayer()).getNoteData();
149 model.setData(noteData.getNotes());
150 setNotes(noteData.getSortedNotes());
151 noteData.addNoteDataUpdateListener(this);
152 }
153 }
154
155 @Override
156 public void layerRemoving(LayerRemoveEvent e) {
157 if (e.getRemovedLayer() instanceof NoteLayer) {
158 NoteData removedNoteData = ((NoteLayer) e.getRemovedLayer()).getNoteData();
159 removedNoteData.removeNoteDataUpdateListener(this);
160 if (Objects.equals(noteData, removedNoteData)) {
161 noteData = null;
162 model.clearData();
163 MapFrame map = MainApplication.getMap();
164 if (map.mapMode instanceof AddNoteAction) {
165 map.selectMapMode(map.mapModeSelect);
166 }
167 }
168 }
169 }
170
171 @Override
172 public void layerOrderChanged(LayerOrderChangeEvent e) {
173 // ignored
174 }
175
176 @Override
177 public void noteDataUpdated(NoteData data) {
178 setNotes(data.getSortedNotes());
179 }
180
181 @Override
182 public void selectedNoteChanged(NoteData noteData) {
183 selectionChanged();
184 }
185
186 /**
187 * Sets the list of notes to be displayed in the dialog.
188 * The dialog should match the notes displayed in the note layer.
189 * @param noteList List of notes to display
190 */
191 public void setNotes(Collection<Note> noteList) {
192 model.setData(noteList);
193 updateButtonStates();
194 this.repaint();
195 }
196
197 /**
198 * Notify the dialog that the note selection has changed.
199 * Causes it to update or clear its selection in the UI.
200 */
201 public void selectionChanged() {
202 if (noteData == null || noteData.getSelectedNote() == null) {
203 displayList.clearSelection();
204 } else {
205 displayList.setSelectedValue(noteData.getSelectedNote(), true);
206 }
207 updateButtonStates();
208 // TODO make a proper listener mechanism to handle change of note selection
209 MainApplication.getMenu().infoweb.noteSelectionChanged();
210 }
211
212 /**
213 * Returns the currently selected note, if any.
214 * @return currently selected note, or null
215 * @since 8475
216 */
217 public Note getSelectedNote() {
218 return noteData != null ? noteData.getSelectedNote() : null;
219 }
220
221 @Override
222 public void destroy() {
223 MainApplication.getLayerManager().removeLayerChangeListener(this);
224 super.destroy();
225 }
226
227 private static class NoteRenderer implements ListCellRenderer<Note> {
228
229 private final DefaultListCellRenderer defaultListCellRenderer = new DefaultListCellRenderer();
230 private final DateFormat dateFormat = DateUtils.getDateTimeFormat(DateFormat.MEDIUM, DateFormat.SHORT);
231
232 @Override
233 public Component getListCellRendererComponent(JList<? extends Note> list, Note note, int index,
234 boolean isSelected, boolean cellHasFocus) {
235 Component comp = defaultListCellRenderer.getListCellRendererComponent(list, note, index, isSelected, cellHasFocus);
236 if (note != null && comp instanceof JLabel) {
237 NoteComment fstComment = note.getFirstComment();
238 JLabel jlabel = (JLabel) comp;
239 if (fstComment != null) {
240 String text = note.getFirstComment().getText();
241 String userName = note.getFirstComment().getUser().getName();
242 if (userName == null || userName.isEmpty()) {
243 userName = "<Anonymous>";
244 }
245 String toolTipText = userName + " @ " + dateFormat.format(note.getCreatedAt());
246 jlabel.setToolTipText(toolTipText);
247 jlabel.setText(note.getId() + ": " +text);
248 } else {
249 jlabel.setToolTipText(null);
250 jlabel.setText(Long.toString(note.getId()));
251 }
252 ImageIcon icon;
253 if (note.getId() < 0) {
254 icon = ImageProvider.get("dialogs/notes", "note_new", ImageProvider.ImageSizes.SMALLICON);
255 } else if (note.getState() == State.CLOSED) {
256 icon = ImageProvider.get("dialogs/notes", "note_closed", ImageProvider.ImageSizes.SMALLICON);
257 } else {
258 icon = ImageProvider.get("dialogs/notes", "note_open", ImageProvider.ImageSizes.SMALLICON);
259 }
260 jlabel.setIcon(icon);
261 }
262 return comp;
263 }
264 }
265
266 class NoteTableModel extends AbstractListModel<Note> {
267 private final transient List<Note> data;
268
269 /**
270 * Constructs a new {@code NoteTableModel}.
271 */
272 NoteTableModel() {
273 data = new ArrayList<>();
274 }
275
276 @Override
277 public int getSize() {
278 if (data == null) {
279 return 0;
280 }
281 return data.size();
282 }
283
284 @Override
285 public Note getElementAt(int index) {
286 return data.get(index);
287 }
288
289 public void setData(Collection<Note> noteList) {
290 data.clear();
291 data.addAll(noteList);
292 fireContentsChanged(this, 0, noteList.size());
293 }
294
295 public void clearData() {
296 displayList.clearSelection();
297 data.clear();
298 fireIntervalRemoved(this, 0, getSize());
299 }
300 }
301
302 class AddCommentAction extends AbstractAction {
303
304 /**
305 * Constructs a new {@code AddCommentAction}.
306 */
307 AddCommentAction() {
308 putValue(SHORT_DESCRIPTION, tr("Add comment"));
309 putValue(NAME, tr("Comment"));
310 new ImageProvider("dialogs/notes", "note_comment").getResource().attachImageIcon(this, true);
311 }
312
313 @Override
314 public void actionPerformed(ActionEvent e) {
315 Note note = displayList.getSelectedValue();
316 if (note == null) {
317 JOptionPane.showMessageDialog(MainApplication.getMap(),
318 "You must select a note first",
319 "No note selected",
320 JOptionPane.ERROR_MESSAGE);
321 return;
322 }
323 NoteInputDialog dialog = new NoteInputDialog(MainApplication.getMainFrame(), tr("Comment on note"), tr("Add comment"));
324 dialog.showNoteDialog(tr("Add comment to note:"), ImageProvider.get("dialogs/notes", "note_comment"));
325 if (dialog.getValue() != 1) {
326 return;
327 }
328 int selectedIndex = displayList.getSelectedIndex();
329 noteData.addCommentToNote(note, dialog.getInputText());
330 noteData.setSelectedNote(model.getElementAt(selectedIndex));
331 }
332 }
333
334 class CloseAction extends AbstractAction {
335
336 /**
337 * Constructs a new {@code CloseAction}.
338 */
339 CloseAction() {
340 putValue(SHORT_DESCRIPTION, tr("Close note"));
341 putValue(NAME, tr("Close"));
342 new ImageProvider("dialogs/notes", "note_closed").getResource().attachImageIcon(this, true);
343 }
344
345 @Override
346 public void actionPerformed(ActionEvent e) {
347 NoteInputDialog dialog = new NoteInputDialog(MainApplication.getMainFrame(), tr("Close note"), tr("Close note"));
348 dialog.showNoteDialog(tr("Close note with message:"), ImageProvider.get("dialogs/notes", "note_closed"));
349 if (dialog.getValue() != 1) {
350 return;
351 }
352 Note note = displayList.getSelectedValue();
353 int selectedIndex = displayList.getSelectedIndex();
354 noteData.closeNote(note, dialog.getInputText());
355 noteData.setSelectedNote(model.getElementAt(selectedIndex));
356 }
357 }
358
359 class NewAction extends AbstractAction {
360
361 /**
362 * Constructs a new {@code NewAction}.
363 */
364 NewAction() {
365 putValue(SHORT_DESCRIPTION, tr("Create a new note"));
366 putValue(NAME, tr("Create"));
367 new ImageProvider("dialogs/notes", "note_new").getResource().attachImageIcon(this, true);
368 }
369
370 @Override
371 public void actionPerformed(ActionEvent e) {
372 if (noteData == null) { //there is no notes layer. Create one first
373 MainApplication.getLayerManager().addLayer(new NoteLayer());
374 }
375 MainApplication.getMap().selectMapMode(new AddNoteAction(noteData));
376 }
377 }
378
379 class ReopenAction extends AbstractAction {
380
381 /**
382 * Constructs a new {@code ReopenAction}.
383 */
384 ReopenAction() {
385 putValue(SHORT_DESCRIPTION, tr("Reopen note"));
386 putValue(NAME, tr("Reopen"));
387 new ImageProvider("dialogs/notes", "note_open").getResource().attachImageIcon(this, true);
388 }
389
390 @Override
391 public void actionPerformed(ActionEvent e) {
392 NoteInputDialog dialog = new NoteInputDialog(MainApplication.getMainFrame(), tr("Reopen note"), tr("Reopen note"));
393 dialog.showNoteDialog(tr("Reopen note with message:"), ImageProvider.get("dialogs/notes", "note_open"));
394 if (dialog.getValue() != 1) {
395 return;
396 }
397
398 Note note = displayList.getSelectedValue();
399 int selectedIndex = displayList.getSelectedIndex();
400 noteData.reOpenNote(note, dialog.getInputText());
401 noteData.setSelectedNote(model.getElementAt(selectedIndex));
402 }
403 }
404
405 class SortAction extends AbstractAction {
406
407 /**
408 * Constructs a new {@code SortAction}.
409 */
410 SortAction() {
411 putValue(SHORT_DESCRIPTION, tr("Sort notes"));
412 putValue(NAME, tr("Sort"));
413 new ImageProvider("dialogs", "sort").getResource().attachImageIcon(this, true);
414 }
415
416 @Override
417 public void actionPerformed(ActionEvent e) {
418 NoteSortDialog sortDialog = new NoteSortDialog(MainApplication.getMainFrame(), tr("Sort notes"), tr("Apply"));
419 sortDialog.showSortDialog(noteData.getCurrentSortMethod());
420 if (sortDialog.getValue() == 1) {
421 noteData.setSortMethod(sortDialog.getSelectedComparator());
422 }
423 }
424 }
425
426 class OpenInBrowserAction extends AbstractAction {
427 OpenInBrowserAction() {
428 putValue(SHORT_DESCRIPTION, tr("Open the note in an external browser"));
429 new ImageProvider("help", "internet").getResource().attachImageIcon(this, true);
430 }
431
432 @Override
433 public void actionPerformed(ActionEvent e) {
434 final Note note = displayList.getSelectedValue();
435 if (note.getId() > 0) {
436 final String url = Config.getUrls().getBaseBrowseUrl() + "/note/" + note.getId();
437 OpenBrowser.displayUrl(url);
438 }
439 }
440 }
441
442}
Note: See TracBrowser for help on using the repository browser.