/* HypnogramPlot.java created 2007-11-06
*
*/
package org.signalml.app.view.signal;
import static org.signalml.app.util.i18n.SvarogI18n._;
import static org.signalml.app.util.i18n.SvarogI18n._R;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import javax.swing.ButtonGroup;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.signalml.app.document.TagDocument;
import org.signalml.domain.tag.SleepTagName;
import org.signalml.domain.tag.StyledTagSet;
import org.signalml.domain.tag.TagEvent;
import org.signalml.domain.tag.TagListener;
import org.signalml.plugin.export.signal.Tag;
import org.signalml.plugin.impl.PluginAccessClass;
/** HypnogramPlot
*
*
* @author Michal Dobaczewski © 2007-2008 CC Otwarte Systemy Komputerowe Sp. z o.o.
*/
public class HypnogramPlot extends JComponent implements PropertyChangeListener, ChangeListener, TagListener {
private static final long serialVersionUID = 1L;
public enum HypnogramMode {
SHOW_ALL,
SHOW_ACTIVE
}
private static final int SINGLE_HYPNO_SIZE = 18;
private static final Dimension minimumSize = new Dimension(300,SINGLE_HYPNO_SIZE+7);
private SignalView view;
private HashMap<StyledTagSet, HypnogramLine[]> hypnogramMap = new HashMap<StyledTagSet, HypnogramLine[]>();
private float pixelFactor;
private float pixelPerSecond;
private float pixelPerPage;
private float pageSize;
private int focusStart;
private int focusEnd;
private int focusWidth;
private int focusCenter;
private int factorStep;
private HypnogramMode mode = HypnogramMode.SHOW_ACTIVE;
private JRadioButtonMenuItem activeRadio;
private JRadioButtonMenuItem allRadio;
private JPopupMenu popupMenu;
private boolean focusCalculated = false;
public HypnogramPlot(SignalView view) {
this.view = view;
setBackground(view.getBackground());
setAutoscrolls(false);
HypnogramMouseListener hypnogramMouseListener = new HypnogramMouseListener();
addMouseListener(hypnogramMouseListener);
addMouseMotionListener(hypnogramMouseListener);
setToolTipText("");
}
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
reset();
}
@Override
public void setSize(Dimension d) {
super.setSize(d);
reset();
}
@Override
public void setSize(int width, int height) {
super.setSize(width, height);
reset();
}
public void revalidateAndReset() {
resetAllLines();
focusCalculated = false;
revalidate();
repaint();
}
public void reset() {
resetAllLines();
focusCalculated = false;
repaint();
}
private void calculateFocus() {
if (focusCalculated) {
return;
}
SignalPlot plot = view.getMasterPlot();
Dimension size = getSize();
Dimension plotSize = plot.getPreferredSize();
JViewport viewport = plot.getViewport();
Point viewportPoint = viewport.getViewPosition();
Dimension viewportSize = viewport.getExtentSize();
pixelFactor = ((float) size.width) / plotSize.width;
pixelPerSecond = ((float) size.width) / plot.getMaxTime();
pageSize = plot.getPageSize();
pixelPerPage = pixelPerSecond * pageSize;
focusStart = (int) Math.round(viewportPoint.x * pixelFactor);
focusWidth = (int) Math.round(viewportSize.width * pixelFactor);
focusWidth = Math.min(size.width, focusWidth);
focusEnd = focusStart + focusWidth - 1;
focusCenter = focusStart + focusWidth/2;
focusCenter = Math.max(0, Math.min(size.width-1, focusCenter));
focusEnd = Math.min(size.width-1, focusEnd);
factorStep = SINGLE_HYPNO_SIZE / 6;
focusCalculated = true;
}
private void resetAllLines() {
hypnogramMap.clear();
}
private void resetLines(StyledTagSet tagSet) {
hypnogramMap.remove(tagSet);
}
protected void paintHypnogram(Graphics2D g, TagDocument tagDocument, int offset, Dimension size) {
HypnogramLine[] hypnogramLines = null;
if (tagDocument != null) {
hypnogramLines = hypnogramMap.get(tagDocument.getTagSet());
// draw hypnogram
if (hypnogramLines == null) {
hypnogramLines = getHypnogramLines(tagDocument);
hypnogramMap.put(tagDocument.getTagSet(), hypnogramLines);
}
}
if (tagDocument != null && hypnogramLines.length > 0) {
int i;
for (i=0; i<hypnogramLines.length; i++) {
if (i > 0) {
if (hypnogramLines[i-1].level != hypnogramLines[i].level) {
if ((hypnogramLines[i].start - hypnogramLines[i-1].end) <= 1) {
g.setColor(Color.BLACK);
g.drawLine(
hypnogramLines[i].start,
offset+hypnogramLines[i-1].level,
hypnogramLines[i].start,
offset+hypnogramLines[i].level
);
}
}
}
g.setColor(hypnogramLines[i].color);
g.drawLine(
hypnogramLines[i].start,
offset+hypnogramLines[i].level,
hypnogramLines[i].end,
offset+hypnogramLines[i].level
);
}
}
}
@Override
protected void paintComponent(Graphics g1) {
calculateFocus();
Graphics2D g = (Graphics2D) g1;
// draw bg
g.setColor(getBackground());
Dimension size = getSize();
g.fill(new Rectangle(new Point(0,0), size));
// draw focus
if (focusEnd - focusStart > 15) {
// draw area focus
g.setColor(Color.WHITE);
g.fillRect(focusStart, 0, 1+(focusEnd-focusStart), size.height);
g.setColor(Color.RED);
g.drawLine(focusStart, 0, focusStart, 2);
g.drawLine(focusStart+1, 0, focusStart+1, 1);
g.drawLine(focusStart+2, 0, focusStart+2, 0);
g.drawLine(focusEnd, 0, focusEnd, 2);
g.drawLine(focusEnd-1, 0, focusEnd-1, 1);
g.drawLine(focusEnd-2, 0, focusEnd-2, 0);
g.drawLine(focusStart, size.height-1, focusStart, size.height-3);
g.drawLine(focusStart+1, size.height-1, focusStart+1, size.height-2);
g.drawLine(focusStart+2, size.height-1, focusStart+2, size.height-1);
g.drawLine(focusEnd, size.height-1, focusEnd, size.height-3);
g.drawLine(focusEnd-1, size.height-1, focusEnd-1, size.height-2);
g.drawLine(focusEnd-2, size.height-1, focusEnd-2, size.height-1);
}
// draw point focus
g.setColor(Color.RED);
g.drawLine(focusCenter-2, 0, focusCenter-2, 0);
g.drawLine(focusCenter-1, 0, focusCenter-1, 1);
g.drawLine(focusCenter, 0, focusCenter, 2);
g.drawLine(focusCenter+1, 0, focusCenter+1, 1);
g.drawLine(focusCenter+2, 0, focusCenter+2, 0);
g.drawLine(focusCenter-2, size.height-1, focusCenter-2, size.height-1);
g.drawLine(focusCenter-1, size.height-1, focusCenter-1, size.height-2);
g.drawLine(focusCenter, size.height-1, focusCenter, size.height-3);
g.drawLine(focusCenter+1, size.height-1, focusCenter+1, size.height-2);
g.drawLine(focusCenter+2, size.height-1, focusCenter+2, size.height-1);
g.setColor(Color.LIGHT_GRAY);
g.drawLine(focusCenter, 3, focusCenter, size.height-4);
int offset = 3;
List<TagDocument> tags = view.getDocument().getTagDocuments();
if (tags.size() == 0 || mode == HypnogramMode.SHOW_ACTIVE) {
paintHypnogram(g, view.getDocument().getActiveTag(), offset, size);
} else {
int tagCnt = tags.size();
int i;
for (i=0; i<tagCnt; i++) {
if (i > 0) {
offset += SINGLE_HYPNO_SIZE + 2;
g.setColor(Color.LIGHT_GRAY);
g.drawLine(0,offset,size.width-1,offset);
offset += 2;
}
paintHypnogram(g, tags.get(i), offset, size);
}
}
}
@Override
public JPopupMenu getComponentPopupMenu() {
if (popupMenu == null) {
popupMenu = new JPopupMenu();
ButtonGroup group = new ButtonGroup();
activeRadio = new JRadioButtonMenuItem(_("For active tag only"));
group.add(activeRadio);
popupMenu.add(activeRadio);
allRadio = new JRadioButtonMenuItem(_("For all tags"));
group.add(allRadio);
popupMenu.add(allRadio);
PluginAccessClass.getGUIImpl().addToHypnogramPlotPopupMenu(popupMenu);
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (allRadio.isSelected()) {
setMode(HypnogramMode.SHOW_ALL);
} else {
setMode(HypnogramMode.SHOW_ACTIVE);
}
}
};
activeRadio.addActionListener(actionListener);
allRadio.addActionListener(actionListener);
}
if (mode == HypnogramMode.SHOW_ACTIVE) {
activeRadio.setSelected(true);
} else {
allRadio.setSelected(true);
}
return popupMenu;
}
@Override
public String getToolTipText(MouseEvent event) {
Point point = event.getPoint();
TagDocument tagDocument = getTagDocumentAtPoint(point);
if (tagDocument != null) {
String message;
if (tagDocument.getBackingFile() == null) {
message = _R("Hypnogram for new tag {0}", tagDocument.getName());
} else {
message = _R("Hypnogram for {0}", tagDocument.getName());
}
float time = ((float) point.x) / pixelPerSecond ;
SortedSet<Tag> tags = tagDocument.getTagSet().getTagsBetween(time, time);
Tag pageTag = null;
for (Tag tag : tags) {
if (tag.getType().isPage()) {
if (time >= tag.getPosition() && time < tag.getEndPosition()) {
pageTag = tag;
break;
}
}
}
if (pageTag != null) {
message = pageTag.getStyle().getDescriptionOrName() + " ; " + message;
}
return message;
} else {
return _("Hypnogram");
}
}
private TagDocument getTagDocumentAtPoint(Point point) {
if (mode == HypnogramMode.SHOW_ACTIVE) {
return view.getDocument().getActiveTag();
} else {
int index = point.y / (SINGLE_HYPNO_SIZE + 4);
List<TagDocument> tags = view.getDocument().getTagDocuments();
if (index >= 0 && index < tags.size()) {
return tags.get(index);
}
return null;
}
}
private HypnogramLine[] getHypnogramLines(TagDocument tagDocument) {
LinkedList<HypnogramLine> lines = new LinkedList<HypnogramLine>();
HypnogramLine lastLine = null;
HypnogramLine line = null;
if (tagDocument != null) {
SortedSet<Tag> tags = tagDocument.getTagSet().getTags();
for (Tag tag : tags) {
if (tag.getType().isPage()) {
line = getHypnogramLine(tag);
if (line != null) {
if (lastLine == null || line.start != lastLine.start || line.end != lastLine.end) {
lines.add(line);
}
lastLine = line;
}
}
}
}
HypnogramLine[] lineArr = new HypnogramLine[lines.size()];
lines.toArray(lineArr);
return lineArr;
}
private HypnogramLine getHypnogramLine(Tag tag) {
String type = tag.getStyle().getName().toLowerCase();
int linePosition;
Color color = null;
linePosition = SleepTagName.getLevel(type) * factorStep;
color = SleepTagName.getColor(type);
if (color == null) {
color = Color.BLACK;
}
int start = (int) Math.round(tag.getPosition()*pixelPerSecond);
int end = (int) Math.round((tag.getPosition()+tag.getLength()) *pixelPerSecond);
return new HypnogramLine(color,linePosition, start, end);
}
private void showPoint(int x) {
boolean snapToPageMode = view.isSnapToPageMode();
if (snapToPageMode) {
int page = (int) Math.floor(((float) x) / pixelPerPage);
view.showTime((float)(page * pageSize));
} else {
view.showTimeCentered(((float) x) / pixelPerSecond);
}
}
public HypnogramMode getMode() {
return mode;
}
public void setMode(HypnogramMode mode) {
if (this.mode != mode) {
this.mode = mode;
revalidate();
repaint();
}
}
@Override
public boolean isOpaque() {
return true;
}
@Override
public Dimension getPreferredSize() {
Dimension size = new Dimension(minimumSize);
int tagCnt = view.getDocument().getTagDocuments().size();
if (tagCnt > 0 && mode == HypnogramMode.SHOW_ALL) {
size.height = 7 + (tagCnt * SINGLE_HYPNO_SIZE) + (tagCnt > 0 ? (tagCnt-1) * 4 : 0);
}
return size;
}
@Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
@Override
public Dimension getMaximumSize() {
return getPreferredSize();
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
reset();
}
@Override
public void stateChanged(ChangeEvent e) {
focusCalculated = false;
repaint();
}
@Override
public void tagAdded(TagEvent e) {
resetLines((StyledTagSet) e.getSource());
repaint();
}
@Override
public void tagChanged(TagEvent e) {
resetLines((StyledTagSet) e.getSource());
repaint();
}
@Override
public void tagRemoved(TagEvent e) {
resetLines((StyledTagSet) e.getSource());
repaint();
}
private class HypnogramLine {
private Color color;
private int level;
private int start;
private int end;
private HypnogramLine(Color color, int level, int start, int end) {
this.color = color;
this.level = level;
this.start = start;
this.end = end;
}
public Color getColor() {
return color;
}
public int getLevel() {
return level;
}
public int getStart() {
return start;
}
public int getEnd() {
return end;
}
}
private class HypnogramMouseListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
showPoint(e.getX());
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
showPoint(e.getX());
}
}
}
}