Index: trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadNotesTask.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadNotesTask.java	(revision 7606)
+++ trunk/src/org/openstreetmap/josm/actions/downloadtasks/DownloadNotesTask.java	(revision 7608)
@@ -96,5 +96,5 @@
             if (noteLayers != null && noteLayers.size() > 0) {
                 layer = noteLayers.get(0);
-                layer.addNotes(notesData);
+                layer.getNoteData().addNotes(notesData);
             } else {
                 layer = new NoteLayer(notesData, "Notes");
Index: trunk/src/org/openstreetmap/josm/actions/mapmode/AddNoteAction.java
===================================================================
--- trunk/src/org/openstreetmap/josm/actions/mapmode/AddNoteAction.java	(revision 7608)
+++ trunk/src/org/openstreetmap/josm/actions/mapmode/AddNoteAction.java	(revision 7608)
@@ -0,0 +1,90 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions.mapmode;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.event.MouseEvent;
+
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.osm.NoteData;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.Notification;
+import org.openstreetmap.josm.gui.dialogs.NoteDialog;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Map mode to add a new note. Listens for a mouse click and then
+ * prompts the user for text and adds a note to the note layer
+ */
+public class AddNoteAction extends MapMode {
+
+    private NoteData noteData;
+
+    /**
+     * Construct a new map mode.
+     * @param mapFrame Map frame to pass to the superconstructor
+     * @param data Note data container. Must not be null
+     */
+    public AddNoteAction(MapFrame mapFrame, NoteData data) {
+        super(tr("Add a new Note"), "addnote.png",
+            tr("Add note mode"),
+            mapFrame, ImageProvider.getCursor("crosshair", "create_note"));
+        if (data == null) {
+            throw new IllegalArgumentException("Note data must not be null");
+        }
+        noteData = data;
+    }
+
+    @Override
+    public String getModeHelpText() {
+        return tr("Click the location where you wish to create a new note");
+    }
+
+    @Override
+    public void enterMode() {
+        super.enterMode();
+        Main.map.mapView.addMouseListener(this);
+    }
+
+    @Override
+    public void exitMode() {
+        super.exitMode();
+        Main.map.mapView.removeMouseListener(this);
+    }
+
+    @Override
+    public void mouseClicked(MouseEvent e) {
+        Main.map.selectMapMode(Main.map.mapModeSelect);
+        LatLon latlon = Main.map.mapView.getLatLon(e.getPoint().x, e.getPoint().y);
+        JLabel label = new JLabel(tr("Enter a comment for a new note"));
+        JTextArea textArea = new JTextArea();
+        textArea.setRows(6);
+        textArea.setColumns(30);
+        textArea.setLineWrap(true);
+        JScrollPane scrollPane = new JScrollPane(textArea);
+
+        Object[] components = new Object[]{label, scrollPane};
+        int option = JOptionPane.showConfirmDialog(Main.map,
+                components,
+                tr("Create new note"),
+                JOptionPane.OK_CANCEL_OPTION,
+                JOptionPane.PLAIN_MESSAGE,
+                NoteDialog.ICON_NEW);
+        if (option == JOptionPane.OK_OPTION) {
+            String input = textArea.getText();
+            if (input != null && !input.isEmpty()) {
+                noteData.createNote(latlon, input);
+            } else {
+                Notification notification = new Notification("You must enter a comment to create a new note");
+                notification.setIcon(JOptionPane.WARNING_MESSAGE);
+                notification.show();
+            }
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/data/osm/NoteData.java
===================================================================
--- trunk/src/org/openstreetmap/josm/data/osm/NoteData.java	(revision 7608)
+++ trunk/src/org/openstreetmap/josm/data/osm/NoteData.java	(revision 7608)
@@ -0,0 +1,168 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.data.coor.LatLon;
+import org.openstreetmap.josm.data.notes.Note;
+import org.openstreetmap.josm.data.notes.Note.State;
+import org.openstreetmap.josm.data.notes.NoteComment;
+import org.openstreetmap.josm.gui.JosmUserIdentityManager;
+
+/**
+ * Class to hold and perform operations on a set of notes
+ */
+public class NoteData {
+
+    private long newNoteId = -1;
+
+    private final List<Note> noteList;
+    private Note selectedNote = null;
+
+    /**
+     * Construct a new note container with an empty note list
+     */
+    public NoteData() {
+        noteList = new ArrayList<>();
+    }
+
+    /**
+     * Construct a new note container with a given list of notes
+     * @param notes The list of notes to populate the container with
+     */
+    public NoteData(List<Note> notes) {
+        noteList = notes;
+    }
+
+    /**
+     * Returns the notes stored in this layer
+     * @return List of Note objects
+     */
+    public List<Note> getNotes() {
+        return noteList;
+    }
+
+    /** Returns the currently selected note
+     * @return currently selected note
+     */
+    public Note getSelectedNote() {
+        return selectedNote;
+    }
+
+    /** Set a selected note. Causes the dialog to select the note and
+     * the note layer to draw the selected note's comments.
+     * @param note Selected note. Null indicates no selection
+     */
+    public void setSelectedNote(Note note) {
+        selectedNote = note;
+        Main.map.noteDialog.selectionChanged();
+        Main.map.mapView.repaint();
+    }
+
+    /**
+     * Add notes to the data set. It only adds a note if the ID is not already present
+     * @param newNotes A list of notes to add
+     */
+    public void addNotes(List<Note> newNotes) {
+        for (Note newNote : newNotes) {
+            if (!noteList.contains(newNote)) {
+                noteList.add(newNote);
+            }
+            if (newNote.getId() <= newNoteId) {
+                newNoteId = newNote.getId() - 1;
+            }
+        }
+        dataUpdated();
+        Main.debug("notes in current set: " + noteList.size());
+    }
+
+    /**
+     * Create a new note
+     * @param location Location of note
+     * @param text Required comment with which to open the note
+     */
+    public void createNote(LatLon location, String text) {
+        if(text == null || text.isEmpty()) {
+            throw new IllegalArgumentException("Comment can not be blank when creating a note");
+        }
+        Note note = new Note(location);
+        note.setCreatedAt(new Date());
+        note.setState(State.open);
+        note.setId(newNoteId--);
+        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.opened, true);
+        note.addComment(comment);
+        Main.debug("Created note {0} with comment: {1}", note.getId(), text);
+        noteList.add(note);
+        dataUpdated();
+    }
+
+    /**
+     * Add a new comment to an existing note
+     * @param note Note to add comment to. Must already exist in the layer
+     * @param text Comment to add
+     */
+    public void addCommentToNote(Note note, String text) {
+        if (!noteList.contains(note)) {
+            throw new IllegalArgumentException("Note to modify must be in layer");
+        }
+        if (note.getState() == State.closed) {
+            throw new IllegalStateException("Cannot add a comment to a closed note");
+        }
+        Main.debug("Adding comment to note {0}: {1}", note.getId(), text);
+        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.commented, true);
+        note.addComment(comment);
+        dataUpdated();
+    }
+
+    /**
+     * Close note with comment
+     * @param note Note to close. Must already exist in the layer
+     * @param text Comment to attach to close action, if desired
+     */
+    public void closeNote(Note note, String text) {
+        if (!noteList.contains(note)) {
+            throw new IllegalArgumentException("Note to close must be in layer");
+        }
+        if (note.getState() != State.open) {
+            throw new IllegalStateException("Cannot close a note that isn't open");
+        }
+        Main.debug("closing note {0} with comment: {1}", note.getId(), text);
+        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.closed, true);
+        note.addComment(comment);
+        note.setState(State.closed);
+        note.setClosedAt(new Date());
+        dataUpdated();
+    }
+
+    /**
+     * Reopen a closed note.
+     * @param note Note to reopen. Must already exist in the layer
+     * @param text Comment to attach to the reopen action, if desired
+     */
+    public void reOpenNote(Note note, String text) {
+        if (!noteList.contains(note)) {
+            throw new IllegalArgumentException("Note to reopen must be in layer");
+        }
+        if (note.getState() != State.closed) {
+            throw new IllegalStateException("Cannot reopen a note that isn't closed");
+        }
+        Main.debug("reopening note {0} with comment: {1}", note.getId(), text);
+        NoteComment comment = new NoteComment(new Date(), getCurrentUser(), text, NoteComment.Action.reopened, true);
+        note.addComment(comment);
+        note.setState(State.open);
+        dataUpdated();
+    }
+
+    private void dataUpdated() {
+        Main.map.noteDialog.setNoteList(noteList);
+        Main.map.mapView.repaint();
+    }
+
+    private User getCurrentUser() {
+        JosmUserIdentityManager userMgr = JosmUserIdentityManager.getInstance();
+        return User.createOsmUser(userMgr.getUserId(), userMgr.getUserName());
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/MapFrame.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/MapFrame.java	(revision 7606)
+++ trunk/src/org/openstreetmap/josm/gui/MapFrame.java	(revision 7608)
@@ -67,4 +67,5 @@
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.MapPaintDialog;
+import org.openstreetmap.josm.gui.dialogs.NoteDialog;
 import org.openstreetmap.josm.gui.dialogs.RelationListDialog;
 import org.openstreetmap.josm.gui.dialogs.SelectionListDialog;
@@ -133,4 +134,5 @@
     public SelectionListDialog selectionListDialog;
     public PropertiesDialog propertiesDialog;
+    public NoteDialog noteDialog;
 
     // Map modes
@@ -243,4 +245,8 @@
         addToggleDialog(new ChangesetDialog(), true);
         addToggleDialog(new MapPaintDialog());
+        //TODO: remove this if statement once note support is complete
+        if(Main.pref.getBoolean("osm.notes.enableDownload", false)) {
+            addToggleDialog(noteDialog = new NoteDialog());
+        }
         toolBarToggle.setFloatable(false);
 
Index: trunk/src/org/openstreetmap/josm/gui/dialogs/NoteDialog.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/dialogs/NoteDialog.java	(revision 7608)
+++ trunk/src/org/openstreetmap/josm/gui/dialogs/NoteDialog.java	(revision 7608)
@@ -0,0 +1,356 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Image;
+import java.awt.event.ActionEvent;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractListModel;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.mapmode.AddNoteAction;
+import org.openstreetmap.josm.data.notes.Note;
+import org.openstreetmap.josm.data.notes.Note.State;
+import org.openstreetmap.josm.data.osm.NoteData;
+import org.openstreetmap.josm.gui.MapView;
+import org.openstreetmap.josm.gui.MapView.LayerChangeListener;
+import org.openstreetmap.josm.gui.SideButton;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.NoteLayer;
+import org.openstreetmap.josm.tools.ImageProvider;
+
+/**
+ * Dialog to display and manipulate notes
+ */
+public class NoteDialog extends ToggleDialog implements LayerChangeListener {
+
+
+    /** Small icon size for use in graphics calculations */
+    public static final int ICON_SMALL_SIZE = 16;
+    /** Large icon size for use in graphics calculations */
+    public static final int ICON_LARGE_SIZE = 24;
+    /** 24x24 icon for unresolved notes */
+    public static final ImageIcon ICON_OPEN = ImageProvider.get("dialogs/notes", "note_open.png");
+    /** 16x16 icon for unresolved notes */
+    public static final ImageIcon ICON_OPEN_SMALL =
+            new ImageIcon(ICON_OPEN.getImage().getScaledInstance(ICON_SMALL_SIZE, ICON_SMALL_SIZE, Image.SCALE_SMOOTH));
+    /** 24x24 icon for resolved notes */
+    public static final ImageIcon ICON_CLOSED = ImageProvider.get("dialogs/notes", "note_closed.png");
+    /** 16x16 icon for resolved notes */
+    public static final ImageIcon ICON_CLOSED_SMALL =
+            new ImageIcon(ICON_CLOSED.getImage().getScaledInstance(ICON_SMALL_SIZE, ICON_SMALL_SIZE, Image.SCALE_SMOOTH));
+    /** 24x24 icon for new notes */
+    public static final ImageIcon ICON_NEW = ImageProvider.get("dialogs/notes", "note_new.png");
+    /** 16x16 icon for new notes */
+    public static final ImageIcon ICON_NEW_SMALL =
+            new ImageIcon(ICON_NEW.getImage().getScaledInstance(ICON_SMALL_SIZE, ICON_SMALL_SIZE, Image.SCALE_SMOOTH));
+    /** Icon for note comments */
+    public static final ImageIcon ICON_COMMENT = ImageProvider.get("dialogs/notes", "note_comment.png");
+
+    private NoteTableModel model;
+    private JList<Note> displayList;
+    private final AddCommentAction addCommentAction;
+    private final CloseAction closeAction;
+    private final NewAction newAction;
+    private final ReopenAction reopenAction;
+
+    private NoteData noteData;
+
+    /** Creates a new toggle dialog for notes */
+    public NoteDialog() {
+        super("Notes", "notes/note_open.png", "List of notes", null, 150);
+        Main.debug("constructed note dialog");
+
+        addCommentAction = new AddCommentAction();
+        closeAction = new CloseAction();
+        newAction = new NewAction();
+        reopenAction = new ReopenAction();
+        buildDialog();
+    }
+
+    @Override
+    public void showDialog() {
+        super.showDialog();
+    }
+
+    private void buildDialog() {
+        model = new NoteTableModel();
+        displayList = new JList<Note>(model);
+        displayList.setCellRenderer(new NoteRenderer());
+        displayList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+        displayList.addListSelectionListener(new ListSelectionListener() {
+            @Override
+            public void valueChanged(ListSelectionEvent e) {
+                if (noteData != null) { //happens when layer is deleted while note selected
+                    noteData.setSelectedNote(displayList.getSelectedValue());
+                }
+                updateButtonStates();
+            }});
+
+        JPanel pane = new JPanel(new BorderLayout());
+        pane.add(new JScrollPane(displayList), BorderLayout.CENTER);
+
+        createLayout(pane, false, Arrays.asList(new SideButton[]{
+                new SideButton(newAction, false),
+                new SideButton(addCommentAction, false),
+                new SideButton(closeAction, false),
+                new SideButton(reopenAction, false)}));
+        updateButtonStates();
+    }
+
+    private void updateButtonStates() {
+        if (noteData == null || noteData.getSelectedNote() == null) {
+            closeAction.setEnabled(false);
+            addCommentAction.setEnabled(false);
+            reopenAction.setEnabled(false);
+        } else if (noteData.getSelectedNote().getState() == State.open){
+            closeAction.setEnabled(true);
+            addCommentAction.setEnabled(true);
+            reopenAction.setEnabled(false);
+        } else { //note is closed
+            closeAction.setEnabled(false);
+            addCommentAction.setEnabled(false);
+            reopenAction.setEnabled(true);
+        }
+    }
+
+    @Override
+    public void showNotify() {
+        MapView.addLayerChangeListener(this);
+    }
+
+    @Override
+    public void hideNotify() {
+        MapView.removeLayerChangeListener(this);
+    }
+
+    @Override
+    public void activeLayerChange(Layer oldLayer, Layer newLayer) { }
+
+    @Override
+    public void layerAdded(Layer newLayer) {
+        Main.debug("layer added: " + newLayer);
+        if (newLayer instanceof NoteLayer) {
+            Main.debug("note layer added");
+            noteData = ((NoteLayer)newLayer).getNoteData();
+            model.setData(noteData.getNotes());
+        }
+    }
+
+    @Override
+    public void layerRemoved(Layer oldLayer) {
+        if (oldLayer instanceof NoteLayer) {
+            Main.debug("note layer removed. Clearing everything");
+            noteData = null;
+            model.clearData();
+            if (Main.map.mapMode instanceof AddNoteAction) {
+                Main.map.selectMapMode(Main.map.mapModeSelect);
+            }
+        }
+    }
+
+    /**
+     * Sets the list of notes to be displayed in the dialog.
+     * The dialog should match the notes displayed in the note layer.
+     * @param noteList List of notes to display
+     */
+    public void setNoteList(List<Note> noteList) {
+        model.setData(noteList);
+        updateButtonStates();
+        this.repaint();
+    }
+
+    /**
+     * Notify the dialog that the note selection has changed.
+     * Causes it to update or clear its selection in the UI.
+     */
+    public void selectionChanged() {
+        if (noteData == null || noteData.getSelectedNote() == null) {
+            displayList.clearSelection();
+        } else {
+            displayList.setSelectedValue(noteData.getSelectedNote(), true);
+        }
+        updateButtonStates();
+    }
+
+    private class NoteRenderer implements ListCellRenderer<Note> {
+
+        private DefaultListCellRenderer defaultListCellRenderer = new DefaultListCellRenderer();
+        private final SimpleDateFormat sdf = new SimpleDateFormat("dd MMM yyyy kk:mm");
+
+        @Override
+        public Component getListCellRendererComponent(JList<? extends Note> list, Note note, int index,
+                boolean isSelected, boolean cellHasFocus) {
+            Component comp = defaultListCellRenderer.getListCellRendererComponent(list, note, index, isSelected, cellHasFocus);
+            if (note != null && comp instanceof JLabel) {
+                String text = note.getFirstComment().getText();
+                String userName = note.getFirstComment().getUser().getName();
+                if (userName == null || userName.isEmpty()) {
+                    userName = "<Anonymous>";
+                }
+                String toolTipText = userName + " @ " + sdf.format(note.getCreatedAt());
+                JLabel jlabel = (JLabel)comp;
+                jlabel.setText(text);
+                ImageIcon icon;
+                if (note.getId() < 0) {
+                    icon = ICON_NEW_SMALL;
+                } else if (note.getState() == State.closed) {
+                    icon = ICON_CLOSED_SMALL;
+                } else {
+                    icon = ICON_OPEN_SMALL;
+                }
+                jlabel.setIcon(icon);
+                jlabel.setToolTipText(toolTipText);
+            }
+            return comp;
+        }
+    }
+
+    class NoteTableModel extends AbstractListModel<Note> {
+        private List<Note> data;
+
+        public NoteTableModel() {
+            data = new ArrayList<Note>();
+        }
+
+        @Override
+        public int getSize() {
+            if (data == null) {
+                return 0;
+            }
+            return data.size();
+        }
+
+        @Override
+        public Note getElementAt(int index) {
+            return data.get(index);
+        }
+
+        public void setData(List<Note> noteList) {
+            data.clear();
+            data.addAll(noteList);
+            fireContentsChanged(this, 0, noteList.size());
+        }
+
+        public void clearData() {
+            displayList.clearSelection();
+            data.clear();
+            fireIntervalRemoved(this, 0, getSize());
+        }
+    }
+
+    class AddCommentAction extends AbstractAction {
+
+        public AddCommentAction() {
+            putValue(SHORT_DESCRIPTION,tr("Add comment"));
+            putValue(NAME, tr("Comment"));
+            putValue(SMALL_ICON, ICON_COMMENT);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            Note note = displayList.getSelectedValue();
+            if (note == null) {
+                JOptionPane.showMessageDialog(Main.map,
+                        "You must select a note first",
+                        "No note selected",
+                        JOptionPane.ERROR_MESSAGE);
+                return;
+            }
+            Object userInput = JOptionPane.showInputDialog(Main.map,
+                    tr("Add comment to note:"),
+                    tr("Add comment"),
+                    JOptionPane.QUESTION_MESSAGE,
+                    ICON_COMMENT,
+                    null,null);
+            if (userInput == null) { //user pressed cancel
+                return;
+            }
+            noteData.addCommentToNote(note, userInput.toString());
+        }
+    }
+
+    class CloseAction extends AbstractAction {
+
+        public CloseAction() {
+            putValue(SHORT_DESCRIPTION,tr("Close note"));
+            putValue(NAME, tr("Close"));
+            putValue(SMALL_ICON, ICON_CLOSED);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            Object userInput = JOptionPane.showInputDialog(Main.map,
+                    tr("Close note with message:"),
+                    tr("Close Note"),
+                    JOptionPane.QUESTION_MESSAGE,
+                    ICON_CLOSED,
+                    null,null);
+            if (userInput == null) { //user pressed cancel
+                return;
+            }
+            Note note = displayList.getSelectedValue();
+            noteData.closeNote(note, userInput.toString());
+        }
+    }
+
+    class NewAction extends AbstractAction {
+
+        public NewAction() {
+            putValue(SHORT_DESCRIPTION,tr("Create a new note"));
+            putValue(NAME, tr("Create"));
+            putValue(SMALL_ICON, ICON_NEW);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            if (noteData == null) { //there is no notes layer. Create one first
+                Main.map.mapView.addLayer(new NoteLayer());
+            }
+            Main.map.selectMapMode(new AddNoteAction(Main.map, noteData));
+        }
+    }
+
+    class ReopenAction extends AbstractAction {
+
+        public ReopenAction() {
+            putValue(SHORT_DESCRIPTION,tr("Reopen note"));
+            putValue(NAME, tr("Reopen"));
+            putValue(SMALL_ICON, ICON_OPEN);
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            Object userInput = JOptionPane.showInputDialog(Main.map,
+                    tr("Reopen note with message:"),
+                    tr("Reopen note"),
+                    JOptionPane.QUESTION_MESSAGE,
+                    ICON_OPEN,
+                    null,null);
+            if (userInput == null) { //user pressed cancel
+                return;
+            }
+            Note note = displayList.getSelectedValue();
+            noteData.reOpenNote(note, userInput.toString());
+        }
+    }
+}
Index: trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java
===================================================================
--- trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(revision 7606)
+++ trunk/src/org/openstreetmap/josm/gui/layer/NoteLayer.java	(revision 7608)
@@ -4,6 +4,10 @@
 import static org.openstreetmap.josm.tools.I18n.tr;
 
+import java.awt.Dimension;
 import java.awt.Graphics2D;
 import java.awt.Point;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.List;
@@ -12,4 +16,5 @@
 import javax.swing.Icon;
 import javax.swing.ImageIcon;
+import javax.swing.JToolTip;
 
 import org.openstreetmap.josm.Main;
@@ -18,16 +23,19 @@
 import org.openstreetmap.josm.data.notes.Note.State;
 import org.openstreetmap.josm.data.notes.NoteComment;
+import org.openstreetmap.josm.data.osm.NoteData;
 import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
-import org.openstreetmap.josm.tools.ImageProvider;
+import org.openstreetmap.josm.gui.dialogs.NoteDialog;
+import org.openstreetmap.josm.io.XmlWriter;
+import org.openstreetmap.josm.tools.ColorHelper;
 
 /**
  * A layer to hold Note objects
  */
-public class NoteLayer extends AbstractModifiableLayer {
-
-    private final List<Note> notes;
+public class NoteLayer extends AbstractModifiableLayer implements MouseListener {
+
+    private final NoteData noteData;
 
     /**
@@ -38,10 +46,32 @@
     public NoteLayer(List<Note> notes, String name) {
         super(name);
-        this.notes = notes;
+        noteData = new NoteData(notes);
+        init();
+    }
+
+    /** Convenience constructor that creates a layer with an empty note list */
+    public NoteLayer() {
+        super(tr("Notes"));
+        noteData = new NoteData();
+        init();
+    }
+
+    private void init() {
+        if (Main.map != null && Main.map.mapView != null) {
+            Main.map.mapView.addMouseListener(this);
+        }
+    }
+
+    /**
+     * Returns the note data store being used by this layer
+     * @return noteData containing layer notes
+     */
+    public NoteData getNoteData() {
+        return noteData;
     }
 
     @Override
     public boolean isModified() {
-        for (Note note : notes) {
+        for (Note note : noteData.getNotes()) {
             if (note.getId() < 0) { //notes with negative IDs are new
                 return true;
@@ -63,14 +93,14 @@
     @Override
     public void paint(Graphics2D g, MapView mv, Bounds box) {
-        for (Note note : notes) {
+        for (Note note : noteData.getNotes()) {
             Point p = mv.getPoint(note.getLatLon());
 
             ImageIcon icon = null;
             if (note.getId() < 0) {
-                icon = ImageProvider.get("notes", "note_new_16x16.png");
+                icon = NoteDialog.ICON_NEW_SMALL;
             } else if (note.getState() == State.closed) {
-                icon = ImageProvider.get("notes", "note_closed_16x16.png");
+                icon = NoteDialog.ICON_CLOSED_SMALL;
             } else {
-                icon = ImageProvider.get("notes", "note_open_16x16.png");
+                icon = NoteDialog.ICON_OPEN_SMALL;
             }
             int width = icon.getIconWidth();
@@ -78,14 +108,60 @@
             g.drawImage(icon.getImage(), p.x - (width / 2), p.y - height, Main.map.mapView);
         }
+        if (noteData.getSelectedNote() != null) {
+            StringBuilder sb = new StringBuilder("<html>");
+            List<NoteComment> comments = noteData.getSelectedNote().getComments();
+            String sep = "";
+            SimpleDateFormat dayFormat = new SimpleDateFormat("MMM d, yyyy");
+            for (NoteComment comment : comments) {
+                String commentText = comment.getText();
+                //closing a note creates an empty comment that we don't want to show
+                if (commentText != null && commentText.trim().length() > 0) {
+                    sb.append(sep);
+                    String userName = comment.getUser().getName();
+                    if (userName == null || userName.trim().length() == 0) {
+                        userName = "&lt;Anonymous&gt;";
+                    }
+                    sb.append(userName);
+                    sb.append(" on ");
+                    sb.append(dayFormat.format(comment.getCommentTimestamp()));
+                    sb.append(":<br/>");
+                    String htmlText = XmlWriter.encode(comment.getText(), true);
+                    htmlText = htmlText.replace("&#xA;", "<br/>"); //encode method leaves us with entity instead of \n
+                    sb.append(htmlText);
+                }
+                sep = "<hr/>";
+            }
+            sb.append("</html>");
+            JToolTip toolTip = new JToolTip();
+            toolTip.setTipText(sb.toString());
+            Point p = mv.getPoint(noteData.getSelectedNote().getLatLon());
+
+            g.setColor(ColorHelper.html2color(Main.pref.get("color.selected")));
+            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);
+
+            int tx = p.x + (NoteDialog.ICON_SMALL_SIZE / 2) + 5;
+            int ty = p.y - NoteDialog.ICON_SMALL_SIZE - 1;
+            g.translate(tx, ty);
+
+            //Carried over from the OSB plugin. Not entirely sure why it is needed
+            //but without it, the tooltip doesn't get sized correctly
+            for (int x = 0; x < 2; x++) {
+                Dimension d = toolTip.getUI().getPreferredSize(toolTip);
+                d.width = Math.min(d.width, (mv.getWidth() * 1 / 2));
+                toolTip.setSize(d);
+                toolTip.paint(g);
+            }
+            g.translate(-tx, -ty);
+        }
     }
 
     @Override
     public Icon getIcon() {
-        return ImageProvider.get("notes", "note_open_16x16.png");
+        return NoteDialog.ICON_OPEN_SMALL;
     }
 
     @Override
     public String getToolTipText() {
-        return notes.size() + " " + tr("Notes");
+        return noteData.getNotes().size() + " " + tr("Notes");
     }
 
@@ -111,5 +187,5 @@
         sb.append(tr("Total notes:"));
         sb.append(" ");
-        sb.append(notes.size());
+        sb.append(noteData.getNotes().size());
         sb.append("\n");
         sb.append(tr("Changes need uploading?"));
@@ -128,24 +204,36 @@
     }
 
-    /**
-     * Returns the notes stored in this layer
-     * @return List of Note objects
-     */
-    public List<Note> getNotes() {
-        return notes;
-    }
-
-    /**
-     * Add notes to the layer. It only adds a note if the ID is not already present
-     * @param newNotes A list of notes to add
-     */
-    public void addNotes(List<Note> newNotes) {
-        for (Note newNote : newNotes) {
-            if (!notes.contains(newNote)) {
-                notes.add(newNote);
-            }
-        }
-        Main.map.mapView.repaint();
-        Main.debug("notes in layer: " + notes.size());
-    }
+    @Override
+    public void mouseClicked(MouseEvent e) {
+        if (e.getButton() != MouseEvent.BUTTON1) {
+            return;
+        }
+        Point clickPoint = e.getPoint();
+        double snapDistance = 10;
+        double minDistance = Double.MAX_VALUE;
+        Note closestNote = null;
+        for (Note note : noteData.getNotes()) {
+            Point notePoint = Main.map.mapView.getPoint(note.getLatLon());
+            //move the note point to the center of the icon where users are most likely to click when selecting
+            notePoint.setLocation(notePoint.getX(), notePoint.getY() - NoteDialog.ICON_SMALL_SIZE / 2);
+            double dist = clickPoint.distanceSq(notePoint);
+            if (minDistance > dist && clickPoint.distance(notePoint) < snapDistance ) {
+                minDistance = dist;
+                closestNote = note;
+            }
+        }
+        noteData.setSelectedNote(closestNote);
+    }
+
+    @Override
+    public void mousePressed(MouseEvent e) { }
+
+    @Override
+    public void mouseReleased(MouseEvent e) { }
+
+    @Override
+    public void mouseEntered(MouseEvent e) { }
+
+    @Override
+    public void mouseExited(MouseEvent e) { }
 }
Index: trunk/src/org/openstreetmap/josm/io/NoteImporter.java
===================================================================
--- trunk/src/org/openstreetmap/josm/io/NoteImporter.java	(revision 7606)
+++ trunk/src/org/openstreetmap/josm/io/NoteImporter.java	(revision 7608)
@@ -52,5 +52,5 @@
             if (noteLayers != null && noteLayers.size() > 0) {
                 NoteLayer layer = noteLayers.get(0);
-                layer.addNotes(fileNotes);
+                layer.getNoteData().addNotes(fileNotes);
             } else {
                 GuiHelper.runInEDT(new Runnable() {
