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

Last change on this file since 14426 was 14153, checked in by Don-vip, 6 years ago

see #15229 - deprecate Main.parent and Main itself

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