Ticket #10554: note_dialog.patch

File note_dialog.patch, 33.8 KB (added by ToeBee, 12 years ago)
  • src/org/openstreetmap/josm/actions/downloadtasks/DownloadNotesTask.java

     
    9696            NoteLayer layer;
    9797            if (noteLayers != null && noteLayers.size() > 0) {
    9898                layer = noteLayers.get(0);
    99                 layer.addNotes(notesData);
     99                layer.getNoteData().addNotes(notesData);
    100100            } else {
    101101                layer = new NoteLayer(notesData, "Notes");
    102102                Main.main.addLayer(layer);
  • src/org/openstreetmap/josm/actions/mapmode/AddNoteAction.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.actions.mapmode;
     3
     4import static org.openstreetmap.josm.tools.I18n.tr;
     5
     6import java.awt.event.MouseEvent;
     7
     8import javax.swing.JLabel;
     9import javax.swing.JOptionPane;
     10import javax.swing.JScrollPane;
     11import javax.swing.JTextArea;
     12
     13import org.openstreetmap.josm.Main;
     14import org.openstreetmap.josm.data.coor.LatLon;
     15import org.openstreetmap.josm.data.osm.NoteData;
     16import org.openstreetmap.josm.gui.MapFrame;
     17import org.openstreetmap.josm.gui.Notification;
     18import org.openstreetmap.josm.gui.dialogs.NoteDialog;
     19import org.openstreetmap.josm.tools.ImageProvider;
     20
     21/**
     22 * Map mode to add a new note. Listens for a mouse click and then
     23 * prompts the user for text and adds a note to the note layer
     24 */
     25public class AddNoteAction extends MapMode {
     26
     27    private NoteData noteData;
     28
     29    /**
     30     * Construct a new map mode.
     31     * @param mapFrame Map frame to pass to the superconstructor
     32     * @param data Note data container. Must not be null
     33     */
     34    public AddNoteAction(MapFrame mapFrame, NoteData data) {
     35        super(tr("Add a new Note"), "addnote.png",
     36            tr("Add note mode"),
     37            mapFrame, ImageProvider.getCursor("crosshair", "create_note"));
     38        if (data == null) {
     39            throw new IllegalArgumentException("Note data must not be null");
     40        }
     41        noteData = data;
     42    }
     43
     44    @Override
     45    public String getModeHelpText() {
     46        return tr("Click the location where you wish to create a new note");
     47    }
     48
     49    @Override
     50    public void enterMode() {
     51        super.enterMode();
     52        Main.map.mapView.addMouseListener(this);
     53    }
     54
     55    @Override
     56    public void exitMode() {
     57        super.exitMode();
     58        Main.map.mapView.removeMouseListener(this);
     59    }
     60
     61    @Override
     62    public void mouseClicked(MouseEvent e) {
     63        Main.map.selectMapMode(Main.map.mapModeSelect);
     64        LatLon latlon = Main.map.mapView.getLatLon(e.getPoint().x, e.getPoint().y);
     65        JLabel label = new JLabel(tr("Enter a comment for a new note"));
     66        JTextArea textArea = new JTextArea();
     67        textArea.setRows(6);
     68        textArea.setColumns(30);
     69        textArea.setLineWrap(true);
     70        JScrollPane scrollPane = new JScrollPane(textArea);
     71
     72        Object[] components = new Object[]{label, scrollPane};
     73        int option = JOptionPane.showConfirmDialog(Main.map,
     74                components,
     75                tr("Create new note"),
     76                JOptionPane.OK_CANCEL_OPTION,
     77                JOptionPane.PLAIN_MESSAGE,
     78                NoteDialog.ICON_NEW);
     79        if (option == JOptionPane.OK_OPTION) {
     80            String input = textArea.getText();
     81            if (input != null && !input.isEmpty()) {
     82                noteData.createNote(latlon, input);
     83            } else {
     84                Notification notification = new Notification("You must enter a comment to create a new note");
     85                notification.setIcon(JOptionPane.WARNING_MESSAGE);
     86                notification.show();
     87            }
     88        }
     89    }
     90}
  • src/org/openstreetmap/josm/data/osm/NoteData.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.data.osm;
     3
     4import java.util.ArrayList;
     5import java.util.Date;
     6import java.util.List;
     7
     8import org.openstreetmap.josm.Main;
     9import org.openstreetmap.josm.data.coor.LatLon;
     10import org.openstreetmap.josm.data.notes.Note;
     11import org.openstreetmap.josm.data.notes.Note.State;
     12import org.openstreetmap.josm.data.notes.NoteComment;
     13import org.openstreetmap.josm.gui.JosmUserIdentityManager;
     14
     15/**
     16 * Class to hold and perform operations on a set of notes
     17 */
     18public class NoteData {
     19
     20    private long newNoteId = -1;
     21
     22    private final List<Note> noteList;
     23    private Note selectedNote = null;
     24
     25    /**
     26     * Construct a new note container with an empty note list
     27     */
     28    public NoteData() {
     29        noteList = new ArrayList<>();
     30    }
     31
     32    /**
     33     * Construct a new note container with a given list of notes
     34     * @param notes The list of notes to populate the container with
     35     */
     36    public NoteData(List<Note> notes) {
     37        noteList = notes;
     38    }
     39
     40    /**
     41     * Returns the notes stored in this layer
     42     * @return List of Note objects
     43     */
     44    public List<Note> getNotes() {
     45        return noteList;
     46    }
     47
     48    /** Returns the currently selected note */
     49    public Note getSelectedNote() {
     50        return selectedNote;
     51    }
     52
     53    /** Set a selected note. Causes the dialog to select the note and
     54     * the note layer to draw the selected note's comments.
     55     * @param note Selected note. Null indicates no selection
     56     */
     57    public void setSelectedNote(Note note) {
     58        selectedNote = note;
     59        Main.map.noteDialog.selectionChanged();
     60        Main.map.mapView.repaint();
     61    }
     62
     63    /**
     64     * Add notes to the data set. It only adds a note if the ID is not already present
     65     * @param newNotes A list of notes to add
     66     */
     67    public void addNotes(List<Note> newNotes) {
     68        for (Note newNote : newNotes) {
     69            if (!noteList.contains(newNote)) {
     70                noteList.add(newNote);
     71            }
     72            if (newNote.getId() <= newNoteId) {
     73                newNoteId = newNote.getId() - 1;
     74            }
     75        }
     76        dataUpdated();
     77        Main.debug("notes in current set: " + noteList.size());
     78    }
     79
     80    /**
     81     * Create a new note
     82     * @param location Location of note
     83     * @param text Required comment with which to open the note
     84     */
     85    public void createNote(LatLon location, String text) {
     86        if(text == null || text.isEmpty()) {
     87            throw new IllegalArgumentException("Comment can not be blank when creating a note");
     88        }
     89        Note note = new Note(location);
     90        note.setCreatedAt(new Date());
     91        note.setState(State.open);
     92        note.setId(newNoteId--);
     93        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.opened, true);
     94        note.addComment(comment);
     95        Main.debug("Created note {0} with comment: {1}", note.getId(), text);
     96        noteList.add(note);
     97        dataUpdated();
     98    }
     99
     100    /**
     101     * Add a new comment to an existing note
     102     * @param note Note to add comment to. Must already exist in the layer
     103     * @param text Comment to add
     104     */
     105    public void addCommentToNote(Note note, String text) {
     106        if (!noteList.contains(note)) {
     107            throw new IllegalArgumentException("Note to modify must be in layer");
     108        }
     109        if (note.getState() == State.closed) {
     110            throw new IllegalStateException("Cannot add a comment to a closed note");
     111        }
     112        Main.debug("Adding comment to note {0}: {1}", note.getId(), text);
     113        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.commented, true);
     114        note.addComment(comment);
     115        dataUpdated();
     116    }
     117
     118    /**
     119     * Close note with comment
     120     * @param note Note to close. Must already exist in the layer
     121     * @param text Comment to attach to close action, if desired
     122     */
     123    public void closeNote(Note note, String text) {
     124        if (!noteList.contains(note)) {
     125            throw new IllegalArgumentException("Note to close must be in layer");
     126        }
     127        if (note.getState() != State.open) {
     128            throw new IllegalStateException("Cannot close a note that isn't open");
     129        }
     130        Main.debug("closing note {0} with comment: {1}", note.getId(), text);
     131        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.closed, true);
     132        note.addComment(comment);
     133        note.setState(State.closed);
     134        note.setClosedAt(new Date());
     135        dataUpdated();
     136    }
     137
     138    /**
     139     * Reopen a closed note.
     140     * @param note Note to reopen. Must already exist in the layer
     141     * @param text Comment to attach to the reopen action, if desired
     142     */
     143    public void reOpenNote(Note note, String text) {
     144        if (!noteList.contains(note)) {
     145            throw new IllegalArgumentException("Note to reopen must be in layer");
     146        }
     147        if (note.getState() != State.closed) {
     148            throw new IllegalStateException("Cannot reopen a note that isn't closed");
     149        }
     150        Main.debug("reopening note {0} with comment: {1}", note.getId(), text);
     151        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.reopened, true);
     152        note.addComment(comment);
     153        note.setState(State.open);
     154        dataUpdated();
     155    }
     156
     157    private void dataUpdated() {
     158        Main.map.noteDialog.setNoteList(noteList);
     159        Main.map.mapView.repaint();
     160    }
     161
     162    private User getCurrentUser() {
     163        JosmUserIdentityManager userMgr = JosmUserIdentityManager.getInstance();
     164        return User.createOsmUser(userMgr.getUserId(), userMgr.getUserName());
     165    }
     166}
  • src/org/openstreetmap/josm/gui/MapFrame.java

     
    6666import org.openstreetmap.josm.gui.dialogs.HistoryDialog;
    6767import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    6868import org.openstreetmap.josm.gui.dialogs.MapPaintDialog;
     69import org.openstreetmap.josm.gui.dialogs.NoteDialog;
    6970import org.openstreetmap.josm.gui.dialogs.RelationListDialog;
    7071import org.openstreetmap.josm.gui.dialogs.SelectionListDialog;
    7172import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
     
    132133    public ValidatorDialog validatorDialog;
    133134    public SelectionListDialog selectionListDialog;
    134135    public PropertiesDialog propertiesDialog;
     136    public NoteDialog noteDialog;
    135137
    136138    // Map modes
    137139    public final SelectAction mapModeSelect;
     
    242244        addToggleDialog(filterDialog = new FilterDialog());
    243245        addToggleDialog(new ChangesetDialog(), true);
    244246        addToggleDialog(new MapPaintDialog());
     247        //TODO: remove this if statement once note suppot is complete
     248        if(Main.pref.getBoolean("osm.notes.enableDownload", false)) {
     249            addToggleDialog(noteDialog = new NoteDialog());
     250        }
    245251        toolBarToggle.setFloatable(false);
    246252
    247253        // status line below the map
  • src/org/openstreetmap/josm/gui/dialogs/NoteDialog.java

     
     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.Image;
     9import java.awt.event.ActionEvent;
     10import java.text.SimpleDateFormat;
     11import java.util.ArrayList;
     12import java.util.Arrays;
     13import java.util.List;
     14
     15import javax.swing.AbstractAction;
     16import javax.swing.AbstractListModel;
     17import javax.swing.DefaultListCellRenderer;
     18import javax.swing.ImageIcon;
     19import javax.swing.JLabel;
     20import javax.swing.JList;
     21import javax.swing.JOptionPane;
     22import javax.swing.JPanel;
     23import javax.swing.JScrollPane;
     24import javax.swing.ListCellRenderer;
     25import javax.swing.ListSelectionModel;
     26import javax.swing.event.ListSelectionEvent;
     27import javax.swing.event.ListSelectionListener;
     28
     29import org.openstreetmap.josm.Main;
     30import org.openstreetmap.josm.actions.mapmode.AddNoteAction;
     31import org.openstreetmap.josm.data.notes.Note;
     32import org.openstreetmap.josm.data.notes.Note.State;
     33import org.openstreetmap.josm.data.osm.NoteData;
     34import org.openstreetmap.josm.gui.MapView;
     35import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
     36import org.openstreetmap.josm.gui.SideButton;
     37import org.openstreetmap.josm.gui.layer.Layer;
     38import org.openstreetmap.josm.gui.layer.NoteLayer;
     39import org.openstreetmap.josm.tools.ImageProvider;
     40
     41/**
     42 * Dialog to display and manipulate notes
     43 */
     44public class NoteDialog extends ToggleDialog implements LayerChangeListener {
     45
     46
     47    /** Small icon size for use in graphics calculations */
     48    public static final int ICON_SMALL_SIZE = 16;
     49    /** Large icon size for use in graphics calculations */
     50    public static final int ICON_LARGE_SIZE = 24;
     51    /** 24x24 icon for unresolved notes */
     52    public static final ImageIcon ICON_OPEN = ImageProvider.get("dialogs/notes", "note_open.png");
     53    /** 16x16 icon for unresolved notes */
     54    public static final ImageIcon ICON_OPEN_SMALL =
     55            new ImageIcon(ICON_OPEN.getImage().getScaledInstance(ICON_SMALL_SIZE, ICON_SMALL_SIZE, Image.SCALE_SMOOTH));
     56    /** 24x24 icon for resolved notes */
     57    public static final ImageIcon ICON_CLOSED = ImageProvider.get("dialogs/notes", "note_closed.png");
     58    /** 16x16 icon for resolved notes */
     59    public static final ImageIcon ICON_CLOSED_SMALL =
     60            new ImageIcon(ICON_CLOSED.getImage().getScaledInstance(ICON_SMALL_SIZE, ICON_SMALL_SIZE, Image.SCALE_SMOOTH));
     61    /** 24x24 icon for new notes */
     62    public static final ImageIcon ICON_NEW = ImageProvider.get("dialogs/notes", "note_new.png");
     63    /** 16x16 icon for new notes */
     64    public static final ImageIcon ICON_NEW_SMALL =
     65            new ImageIcon(ICON_NEW.getImage().getScaledInstance(ICON_SMALL_SIZE, ICON_SMALL_SIZE, Image.SCALE_SMOOTH));
     66    /** Icon for note comments */
     67    public static final ImageIcon ICON_COMMENT = ImageProvider.get("dialogs/notes", "note_comment.png");
     68
     69    private NoteTableModel model;
     70    private JList<Note> displayList;
     71    private final AddCommentAction addCommentAction;
     72    private final CloseAction closeAction;
     73    private final NewAction newAction;
     74    private final ReopenAction reopenAction;
     75
     76    private NoteData noteData;
     77
     78    /** Creates a new toggle dialog for notes */
     79    public NoteDialog() {
     80        super("Notes", "notes/note_open.png", "List of notes", null, 150);
     81        Main.debug("constructed note dialog");
     82
     83        addCommentAction = new AddCommentAction();
     84        closeAction = new CloseAction();
     85        newAction = new NewAction();
     86        reopenAction = new ReopenAction();
     87        buildDialog();
     88    }
     89
     90    @Override
     91    public void showDialog() {
     92        super.showDialog();
     93    }
     94
     95    private void buildDialog() {
     96        model = new NoteTableModel();
     97        displayList = new JList<Note>(model);
     98        displayList.setCellRenderer(new NoteRenderer());
     99        displayList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
     100        displayList.addListSelectionListener(new ListSelectionListener() {
     101            @Override
     102            public void valueChanged(ListSelectionEvent e) {
     103                if (noteData != null) { //happens when layer is deleted while note selected
     104                    noteData.setSelectedNote(displayList.getSelectedValue());
     105                }
     106                updateButtonStates();
     107            }});
     108
     109        JPanel pane = new JPanel(new BorderLayout());
     110        pane.add(new JScrollPane(displayList), BorderLayout.CENTER);
     111
     112        createLayout(pane, false, Arrays.asList(new SideButton[]{
     113                new SideButton(newAction, false),
     114                new SideButton(addCommentAction, false),
     115                new SideButton(closeAction, false),
     116                new SideButton(reopenAction, false)}));
     117        updateButtonStates();
     118    }
     119
     120    private void updateButtonStates() {
     121        if (noteData == null || noteData.getSelectedNote() == null) {
     122            closeAction.setEnabled(false);
     123            addCommentAction.setEnabled(false);
     124            reopenAction.setEnabled(false);
     125        } else if (noteData.getSelectedNote().getState() == State.open){
     126            closeAction.setEnabled(true);
     127            addCommentAction.setEnabled(true);
     128            reopenAction.setEnabled(false);
     129        } else { //note is closed
     130            closeAction.setEnabled(false);
     131            addCommentAction.setEnabled(false);
     132            reopenAction.setEnabled(true);
     133        }
     134    }
     135
     136    @Override
     137    public void showNotify() {
     138        MapView.addLayerChangeListener(this);
     139    }
     140
     141    @Override
     142    public void hideNotify() {
     143        MapView.removeLayerChangeListener(this);
     144    }
     145
     146    @Override
     147    public void activeLayerChange(Layer oldLayer, Layer newLayer) { }
     148
     149    @Override
     150    public void layerAdded(Layer newLayer) {
     151        Main.debug("layer added: " + newLayer);
     152        if (newLayer instanceof NoteLayer) {
     153            Main.debug("note layer added");
     154            noteData = ((NoteLayer)newLayer).getNoteData();
     155            model.setData(noteData.getNotes());
     156        }
     157    }
     158
     159    @Override
     160    public void layerRemoved(Layer oldLayer) {
     161        if (oldLayer instanceof NoteLayer) {
     162            Main.debug("note layer removed. Clearing everything");
     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    /**
     172     * Sets the list of notes to be displayed in the dialog.
     173     * The dialog should match the notes displayed in the note layer.
     174     * @param noteList List of notes to display
     175     */
     176    public void setNoteList(List<Note> noteList) {
     177        model.setData(noteList);
     178        updateButtonStates();
     179        this.repaint();
     180    }
     181
     182    /**
     183     * Notify the dialog that the note selection has changed.
     184     * Causes it to update or clear its selection in the UI.
     185     */
     186    public void selectionChanged() {
     187        if (noteData == null || noteData.getSelectedNote() == null) {
     188            displayList.clearSelection();
     189        } else {
     190            displayList.setSelectedValue(noteData.getSelectedNote(), true);
     191        }
     192        updateButtonStates();
     193    }
     194
     195    private class NoteRenderer implements ListCellRenderer<Note> {
     196
     197        private DefaultListCellRenderer defaultListCellRenderer = new DefaultListCellRenderer();
     198        private final SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy kk:mm");
     199
     200        @Override
     201        public Component getListCellRendererComponent(JList<? extends Note> list, Note note, int index,
     202                boolean isSelected, boolean cellHasFocus) {
     203            Component comp = defaultListCellRenderer.getListCellRendererComponent(list, note, index, isSelected, cellHasFocus);
     204            if (note != null && comp instanceof JLabel) {
     205                String text = note.getFirstComment().getText();
     206                String userName = note.getFirstComment().getUser().getName();
     207                if (userName == null || userName.isEmpty()) {
     208                    userName = "<Anonymous>";
     209                }
     210                String toolTipText = userName + " @ " + sdf.format(note.getCreatedAt());
     211                JLabel jlabel = (JLabel)comp;
     212                jlabel.setText(text);
     213                ImageIcon icon;
     214                if (note.getId() < 0) {
     215                    icon = ICON_NEW_SMALL;
     216                } else if (note.getState() == State.closed) {
     217                    icon = ICON_CLOSED_SMALL;
     218                } else {
     219                    icon = ICON_OPEN_SMALL;
     220                }
     221                jlabel.setIcon(icon);
     222                jlabel.setToolTipText(toolTipText);
     223            }
     224            return comp;
     225        }
     226    }
     227
     228    class NoteTableModel extends AbstractListModel<Note> {
     229        private List<Note> data;
     230
     231        public NoteTableModel() {
     232            data = new ArrayList<Note>();
     233        }
     234
     235        @Override
     236        public int getSize() {
     237            if (data == null) {
     238                return 0;
     239            }
     240            return data.size();
     241        }
     242
     243        @Override
     244        public Note getElementAt(int index) {
     245            return data.get(index);
     246        }
     247
     248        public void setData(List<Note> noteList) {
     249            data.clear();
     250            data.addAll(noteList);
     251            fireContentsChanged(this, 0, noteList.size());
     252        }
     253
     254        public void clearData() {
     255            displayList.clearSelection();
     256            data.clear();
     257            fireIntervalRemoved(this, 0, getSize());
     258        }
     259    }
     260
     261    class AddCommentAction extends AbstractAction {
     262
     263        public AddCommentAction() {
     264            putValue(SHORT_DESCRIPTION,tr("Add comment"));
     265            putValue(NAME, tr("Comment"));
     266            putValue(SMALL_ICON, ICON_COMMENT);
     267        }
     268
     269        @Override
     270        public void actionPerformed(ActionEvent e) {
     271            Note note = displayList.getSelectedValue();
     272            if (note == null) {
     273                JOptionPane.showMessageDialog(Main.map,
     274                        "You must select a note first",
     275                        "No note selected",
     276                        JOptionPane.ERROR_MESSAGE);
     277                return;
     278            }
     279            Object userInput = JOptionPane.showInputDialog(Main.map,
     280                    tr("Add comment to note:"),
     281                    tr("Add comment"),
     282                    JOptionPane.QUESTION_MESSAGE,
     283                    ICON_COMMENT,
     284                    null,null);
     285            if (userInput == null) { //user pressed cancel
     286                return;
     287            }
     288            noteData.addCommentToNote(note, userInput.toString());
     289        }
     290    }
     291
     292    class CloseAction extends AbstractAction {
     293
     294        public CloseAction() {
     295            putValue(SHORT_DESCRIPTION,tr("Close note"));
     296            putValue(NAME, tr("Close"));
     297            putValue(SMALL_ICON, ICON_CLOSED);
     298        }
     299
     300        @Override
     301        public void actionPerformed(ActionEvent e) {
     302            Object userInput = JOptionPane.showInputDialog(Main.map,
     303                    tr("Close note with message:"),
     304                    tr("Close Note"),
     305                    JOptionPane.QUESTION_MESSAGE,
     306                    ICON_CLOSED,
     307                    null,null);
     308            if (userInput == null) { //user pressed cancel
     309                return;
     310            }
     311            Note note = displayList.getSelectedValue();
     312            noteData.closeNote(note, userInput.toString());
     313        }
     314    }
     315
     316    class NewAction extends AbstractAction {
     317
     318        public NewAction() {
     319            putValue(SHORT_DESCRIPTION,tr("Create a new note"));
     320            putValue(NAME, tr("Create"));
     321            putValue(SMALL_ICON, ICON_NEW);
     322        }
     323
     324        @Override
     325        public void actionPerformed(ActionEvent e) {
     326            if (noteData == null) { //there is no notes layer. Create one first
     327                Main.map.mapView.addLayer(new NoteLayer());
     328            }
     329            Main.map.selectMapMode(new AddNoteAction(Main.map, noteData));
     330        }
     331    }
     332
     333    class ReopenAction extends AbstractAction {
     334
     335        public ReopenAction() {
     336            putValue(SHORT_DESCRIPTION,tr("Reopen note"));
     337            putValue(NAME, tr("Reopen"));
     338            putValue(SMALL_ICON, ICON_OPEN);
     339        }
     340
     341        @Override
     342        public void actionPerformed(ActionEvent e) {
     343            Object userInput = JOptionPane.showInputDialog(Main.map,
     344                    tr("Reopen note with message:"),
     345                    tr("Reopen note"),
     346                    JOptionPane.QUESTION_MESSAGE,
     347                    ICON_OPEN,
     348                    null,null);
     349            if (userInput == null) { //user pressed cancel
     350                return;
     351            }
     352            Note note = displayList.getSelectedValue();
     353            noteData.reOpenNote(note, userInput.toString());
     354        }
     355    }
     356}
  • src/org/openstreetmap/josm/gui/layer/NoteLayer.java

     
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.Dimension;
    67import java.awt.Graphics2D;
    78import java.awt.Point;
     9import java.awt.event.MouseEvent;
     10import java.awt.event.MouseListener;
     11import java.text.SimpleDateFormat;
    812import java.util.ArrayList;
    913import java.util.List;
    1014
    1115import javax.swing.Action;
    1216import javax.swing.Icon;
    1317import javax.swing.ImageIcon;
     18import javax.swing.JToolTip;
    1419
    1520import org.openstreetmap.josm.Main;
    1621import org.openstreetmap.josm.data.Bounds;
    1722import org.openstreetmap.josm.data.notes.Note;
    1823import org.openstreetmap.josm.data.notes.Note.State;
    1924import org.openstreetmap.josm.data.notes.NoteComment;
     25import org.openstreetmap.josm.data.osm.NoteData;
    2026import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
    2127import org.openstreetmap.josm.gui.MapView;
    2228import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
    2329import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
    24 import org.openstreetmap.josm.tools.ImageProvider;
     30import org.openstreetmap.josm.gui.dialogs.NoteDialog;
     31import org.openstreetmap.josm.io.XmlWriter;
     32import org.openstreetmap.josm.tools.ColorHelper;
    2533
    2634/**
    2735 * A layer to hold Note objects
    2836 */
    29 public class NoteLayer extends AbstractModifiableLayer {
     37public class NoteLayer extends AbstractModifiableLayer implements MouseListener {
    3038
    31     private final List<Note> notes;
     39    private final NoteData noteData;
    3240
    3341    /**
    3442     * Create a new note layer with a set of notes
     
    3745     */
    3846    public NoteLayer(List<Note> notes, String name) {
    3947        super(name);
    40         this.notes = notes;
     48        noteData = new NoteData(notes);
     49        init();
    4150    }
    4251
     52    /** Convenience constructor that creates a layer with an empty note list */
     53    public NoteLayer() {
     54        super(tr("Notes"));
     55        noteData = new NoteData();
     56        init();
     57    }
     58
     59    private void init() {
     60        if (Main.map != null && Main.map.mapView != null) {
     61            Main.map.mapView.addMouseListener(this);
     62        }
     63    }
     64
     65    /**
     66     * Returns the note data store being used by this layer
     67     * @return noteData containing layer notes
     68     */
     69    public NoteData getNoteData() {
     70        return noteData;
     71    }
     72
    4373    @Override
    4474    public boolean isModified() {
    45         for (Note note : notes) {
     75        for (Note note : noteData.getNotes()) {
    4676            if (note.getId() < 0) { //notes with negative IDs are new
    4777                return true;
    4878            }
     
    6292
    6393    @Override
    6494    public void paint(Graphics2D g, MapView mv, Bounds box) {
    65         for (Note note : notes) {
     95        for (Note note : noteData.getNotes()) {
    6696            Point p = mv.getPoint(note.getLatLon());
    6797
    6898            ImageIcon icon = null;
    6999            if (note.getId() < 0) {
    70                 icon = ImageProvider.get("notes", "note_new_16x16.png");
     100                icon = NoteDialog.ICON_NEW_SMALL;
    71101            } else if (note.getState() == State.closed) {
    72                 icon = ImageProvider.get("notes", "note_closed_16x16.png");
     102                icon = NoteDialog.ICON_CLOSED_SMALL;
    73103            } else {
    74                 icon = ImageProvider.get("notes", "note_open_16x16.png");
     104                icon = NoteDialog.ICON_OPEN_SMALL;
    75105            }
    76106            int width = icon.getIconWidth();
    77107            int height = icon.getIconHeight();
    78108            g.drawImage(icon.getImage(), p.x - (width / 2), p.y - height, Main.map.mapView);
    79109        }
     110        if (noteData.getSelectedNote() != null) {
     111            StringBuilder sb = new StringBuilder("<html>");
     112            List<NoteComment> comments = noteData.getSelectedNote().getComments();
     113            String sep = "";
     114            SimpleDateFormat dayFormat = new SimpleDateFormat("MMM d, yyyy");
     115            for (NoteComment comment : comments) {
     116                String commentText = comment.getText();
     117                //closing a note creates an empty comment that we don't want to show
     118                if (commentText != null && commentText.trim().length() > 0) {
     119                    sb.append(sep);
     120                    String userName = comment.getUser().getName();
     121                    if (userName == null || userName.trim().length() == 0) {
     122                        userName = "&lt;Anonymous&gt;";
     123                    }
     124                    sb.append(userName);
     125                    sb.append(" on ");
     126                    sb.append(dayFormat.format(comment.getCommentTimestamp()));
     127                    sb.append(":<br/>");
     128                    String htmlText = XmlWriter.encode(comment.getText(), true);
     129                    htmlText = htmlText.replace("&#xA;", "<br/>"); //encode method leaves us with entity instead of \n
     130                    sb.append(htmlText);
     131                }
     132                sep = "<hr/>";
     133            }
     134            sb.append("</html>");
     135            JToolTip toolTip = new JToolTip();
     136            toolTip.setTipText(sb.toString());
     137            Point p = mv.getPoint(noteData.getSelectedNote().getLatLon());
     138
     139            g.setColor(ColorHelper.html2color(Main.pref.get("color.selected")));
     140            g.drawRect(p.x - (NoteDialog.ICON_SMALL_SIZE / 2), p.y - NoteDialog.ICON_SMALL_SIZE, NoteDialog.ICON_SMALL_SIZE - 1, NoteDialog.ICON_SMALL_SIZE - 1);
     141
     142            int tx = p.x + (NoteDialog.ICON_SMALL_SIZE / 2) + 5;
     143            int ty = p.y - NoteDialog.ICON_SMALL_SIZE - 1;
     144            g.translate(tx, ty);
     145
     146            //Carried over from the OSB plugin. Not entirely sure why it is needed
     147            //but without it, the tooltip doesn't get sized correctly
     148            for (int x = 0; x < 2; x++) {
     149                Dimension d = toolTip.getUI().getPreferredSize(toolTip);
     150                d.width = Math.min(d.width, (mv.getWidth() * 1 / 2));
     151                toolTip.setSize(d);
     152                toolTip.paint(g);
     153            }
     154            g.translate(-tx, -ty);
     155        }
    80156    }
    81157
    82158    @Override
    83159    public Icon getIcon() {
    84         return ImageProvider.get("notes", "note_open_16x16.png");
     160        return NoteDialog.ICON_OPEN_SMALL;
    85161    }
    86162
    87163    @Override
    88164    public String getToolTipText() {
    89         return notes.size() + " " + tr("Notes");
     165        return noteData.getNotes().size() + " " + tr("Notes");
    90166    }
    91167
    92168    @Override
     
    110186        sb.append("\n");
    111187        sb.append(tr("Total notes:"));
    112188        sb.append(" ");
    113         sb.append(notes.size());
     189        sb.append(noteData.getNotes().size());
    114190        sb.append("\n");
    115191        sb.append(tr("Changes need uploading?"));
    116192        sb.append(" ");
     
    127203        return actions.toArray(new Action[actions.size()]);
    128204    }
    129205
    130     /**
    131      * Returns the notes stored in this layer
    132      * @return List of Note objects
    133      */
    134     public List<Note> getNotes() {
    135         return notes;
    136     }
    137 
    138     /**
    139      * Add notes to the layer. It only adds a note if the ID is not already present
    140      * @param newNotes A list of notes to add
    141      */
    142     public void addNotes(List<Note> newNotes) {
    143         for (Note newNote : newNotes) {
    144             if (!notes.contains(newNote)) {
    145                 notes.add(newNote);
     206    @Override
     207    public void mouseClicked(MouseEvent e) {
     208        if (e.getButton() != MouseEvent.BUTTON1) {
     209            return;
     210        }
     211        Point clickPoint = e.getPoint();
     212        double snapDistance = 10;
     213        double minDistance = Double.MAX_VALUE;
     214        Note closestNote = null;
     215        for (Note note : noteData.getNotes()) {
     216            Point notePoint = Main.map.mapView.getPoint(note.getLatLon());
     217            //move the note point to the center of the icon where users are most likely to click when selecting
     218            notePoint.setLocation(notePoint.getX(), notePoint.getY() - NoteDialog.ICON_SMALL_SIZE / 2);
     219            double dist = clickPoint.distanceSq(notePoint);
     220            if (minDistance > dist && clickPoint.distance(notePoint) < snapDistance ) {
     221                minDistance = dist;
     222                closestNote = note;
    146223            }
    147224        }
    148         Main.map.mapView.repaint();
    149         Main.debug("notes in layer: " + notes.size());
     225        noteData.setSelectedNote(closestNote);
    150226    }
     227
     228    @Override
     229    public void mousePressed(MouseEvent e) { }
     230
     231    @Override
     232    public void mouseReleased(MouseEvent e) { }
     233
     234    @Override
     235    public void mouseEntered(MouseEvent e) { }
     236
     237    @Override
     238    public void mouseExited(MouseEvent e) { }
    151239}
  • src/org/openstreetmap/josm/io/NoteImporter.java

     
    5151            }
    5252            if (noteLayers != null && noteLayers.size() > 0) {
    5353                NoteLayer layer = noteLayers.get(0);
    54                 layer.addNotes(fileNotes);
     54                layer.getNoteData().addNotes(fileNotes);
    5555            } else {
    5656                GuiHelper.runInEDT(new Runnable() {
    5757                    @Override