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

Last change on this file since 17188 was 17188, checked in by Klumbumbus, 4 years ago

fix #19851 - Fix shortcut names

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