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

Last change on this file since 10478 was 10428, checked in by stoecker, 8 years ago

see #9995 - patch mainly by strump - improve HIDPI behaviour

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