Доброго времени суток. Есть JPanel, расчертил на ней сетку, нужно чтобы при движении мышки, курсор(не сам указатель мышки, а просто нарисованный мной крестик) перемещался только по узлам этой сетки. Попробовал реализовать(код ниже). Вроде как всё работает, но немного не так как надо, курсор не совсем точно встаёт в узлы сетки - как-то неправильно высчитываются координаты узлов, причём, если поставить курсор у левой стороны окна(где координата Х меньше), то всё нормально, но чем больше вправо сдвигаюсь по Х, тем сильнее «убегает» курсор, тоже самое и по оси Y.
Пояснение на скриншотах. Не пойму в чём причина, грешу на округление, либо как-то не так считаю помогите разобраться (
Теперь когда сдвигаемся в противоположный край окна - курсор убегает от узла сетки, причём, чем больше сдвиг - тем сильнее «убегание» - http://savepic.ru/10754039.png
public class DrawArea extends JPanel implements MouseListener,
MouseMotionListener,
MouseWheelListener {
private int aDPI = 300;
/** По умолчанию панель будет размером с лист А4 */
private Dimension aSize = TR_Utils.mmsToPixels(TR_Utils.A4_PAGE_SIZE_MM, aDPI);
/** Коэффициент зумирования */
private double scaleFactor = 1.0;
/** Размер сетки(во внутренник единицах измерения), если включена */
private int gridSize = 40;
/** Позиция курсора - позиция от которой рисуем(в логических, мировых координатах) */
private Point2D crossHairPosition = new Point2D.Double();
/** Привязывать ли курсор к сетке */
private boolean snapCursorToGrid = true;
/** Включаем выключаем сетку */
private boolean isGridEnabled = true;
/** Крест по центру экрана, если надо */
private boolean isCrossEnabled = true;
/** Сглаживание, если требуется */
private boolean isAnlialiasingEnabled = false;
/** Включен режим рисования(рисуется курсор для
* рисования на панели, иначе простой указатель) */
private boolean isDrawMode = true;
/** Включен ли режим просмотра для панели.
* В этом режиме запрещено редактировать или что-либо рисовать на панели,
* события мыши и клавиатуры игнорируются, изображение на панели
* панорамируется(если необходимо) */
private boolean isViewMode = false;
/** Координата, на которой нажали кнопку */
private Point mousePressedPos;
//GridSize s = new GridSize(15, SizeMeasureUnits.MIL);
private AffineTransform at = new AffineTransform();
public DrawArea() {
setPreferredSize(aSize);
System.out.println("Size w: " + aSize.width + " h: " + aSize.height);
addMouseMotionListener(this);
addMouseListener(this);
addMouseWheelListener(this);
}
@Override
public Dimension getPreferredSize() {
return new Dimension((int)(aSize.width*scaleFactor),
(int)(aSize.height*scaleFactor));
}
/** Переводит координаты окна в координаты панели(мировые координаты) */
private Point TranslateW2WCoordinates(int winX, int winY) {
Container parent = getParent();
Point point = null;
if(parent != null)
point = SwingUtilities.convertPoint(parent, new Point(winX, winY), this);
return point;
}
public void setCenterInViewport() {
Container parent = getParent();
if (parent instanceof JViewport) {
JViewport port = (JViewport)parent;
Rectangle viewRect = port.getViewRect();
int width = getWidth();
int height = getHeight();
viewRect.x = (width - viewRect.width) / 2 - viewRect.x;
viewRect.y = (height - viewRect.height) / 2 - viewRect.y;
port.scrollRectToVisible(viewRect);
}
}
/** Находит ближайший узел сетки для установки курсора.
* curX, curY - координаты курсора в мировых координатах */
private Point getNearestGridPosition(int curX, int curY) {
double scaledGridSize = gridSize * scaleFactor;
int cellx = (int)Math.round(curX / scaledGridSize);
int celly = (int)Math.round(curY / scaledGridSize);
int x = (int)Math.round(cellx * scaledGridSize);
int y = (int)Math.round(celly * scaledGridSize);
System.out.println("Grid size: " + gridSize + " ScaleFactor: " + scaleFactor + " Scaled grid size: " + scaledGridSize);
System.out.println("Cursor pos - x: " + curX + " y: " + curY);
System.out.println("Cell num - x: " + cellx + " y: " + celly);
System.out.println("CrossHairPos - x: " + x + " y: " + y);
System.out.println("--------------------");
return new Point(x, y);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
setBackground(Color.WHITE);
if(isAnlialiasingEnabled) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
}
/* Prepare draw area */
if(isGridEnabled) {
int w = getWidth();
int h = getHeight();
int gridStep = (int)Math.round(gridSize * scaleFactor);
g2d.setColor(new Color(235, 235, 235));
for(int i = 0; i < w; i += gridStep)
g2d.drawLine(i, 0, i, h);
for(int j = 0; j < h; j += gridStep)
g2d.drawLine(0, j , w, j);
}
if(isCrossEnabled) {
g2d.setColor(Color.GRAY);
float[] dash = {2f, 0f, 2f};
Stroke oldStroke = g2d.getStroke();
BasicStroke bs = new BasicStroke(1, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND, 1.0f, dash, 2f);
g2d.setStroke(bs);
g2d.drawLine(getWidth()/2, 0, getWidth()/2, getHeight());
g2d.drawLine(0, getHeight()/2, getWidth(), getHeight()/2);
g2d.setStroke(oldStroke);
}
if (isDrawMode) {
int cs = 12; // размер крестика-курсора в пикселях
g2d.setColor(Color.GRAY);
/* Координаты курсора в мировых координатах, приведем их к оконным */
int cx = (int)crossHairPosition.getX();
int cy = (int)crossHairPosition.getY();
Point p = SwingUtilities.convertPoint(this, new Point(cx, cy), getParent());
g2d.drawLine(p.x-cs/2, p.y, p.x+cs/2, p.y);
g2d.drawLine(p.x, p.y-cs/2, p.x, p.y+cs/2);
}
/* Отрисовываем всё остальное(рисуем модель) */
/*
g2d.setColor(Color.WHITE);
g2d.fillRect(100, 100, 80, 160);
g2d.setColor(Color.BLACK);
g2d.drawRect(100, 100, 80, 160); */
g2d.dispose();
}
public void setDPI(int dpi) {
aDPI = dpi;
repaint();
}
public int getDPI() {
return aDPI;
}
public void setGridSize(int sizePx) {
if(sizePx <= 0)
sizePx = 1;
gridSize = sizePx;
repaint();
}
public void setGridEnabled(boolean enabled) {
isGridEnabled = enabled;
repaint();
}
public void setCrossEnabled(boolean enabled) {
isCrossEnabled = enabled;
repaint();
}
public void setAntialiasingEnabled(boolean enabled) {
isAnlialiasingEnabled = enabled;
repaint();
}
public void setViewModeEnabled(boolean enabled) {
isViewMode = enabled;
repaint();
}
/** Включает режим просмотра и панорамирует всё что нарисовано
* (чтобы оно влезло в вид) */
public void setViewModeEnabled(boolean enabled, boolean panning) {
isViewMode = enabled;
/* Если включено панорамирование, перерисуем всё что есть
* предварительно отмасштабировав под размеры viewport-а */
//...
repaint();
}
/****** MOUSE EVENTS ******/
@Override
public void mouseClicked(MouseEvent me) {}
@Override
public void mousePressed(MouseEvent me) {
mousePressedPos = new Point(me.getPoint());
System.out.println("mousePressedPos " + mousePressedPos.x + "/" + mousePressedPos.y);
}
@Override
public void mouseReleased(MouseEvent me) {
}
@Override
public void mouseEntered(MouseEvent me) {}
@Override
public void mouseExited(MouseEvent me) {}
@Override
public void mouseDragged(MouseEvent me) {
if(!isViewMode) {
if(SwingUtilities.isMiddleMouseButton(me)) {
if(mousePressedPos != null) {
JViewport port = (JViewport)getParent();
Rectangle viewRect = port.getViewRect();
int deltaX = mousePressedPos.x - me.getX();
int deltaY = mousePressedPos.y - me.getY();
viewRect.x += deltaX;
viewRect.y += deltaY;
scrollRectToVisible(viewRect);
}
}
}
}
@Override
public void mouseMoved(MouseEvent me) {
if(!isViewMode) {
int x = me.getX();
int y = me.getY();
// в мировые координаты
Point p = SwingUtilities.convertPoint(getParent(), new Point(x, y), this);
JViewport port = (JViewport)getParent();
Point vp = port.getViewPosition();
//System.out.println( "view.x: " + x + " view.y: " + y +
// " / area.x: " + p.x + " area.y: " + p.y +
// " / vpos.x: " + vp.x + " vpos.y: " + vp.y);
if(snapCursorToGrid) {
// Привязываем курсор к узлам сетки
Point np = getNearestGridPosition(p.x, p.y);
crossHairPosition.setLocation(np.x, np.y);
}
else {
crossHairPosition.setLocation(p.x, p.y);
}
repaint();
}
}
@Override
public void mouseWheelMoved(MouseWheelEvent mwe) {
if(!isViewMode) {
/* минус для изменения направления движения колесика при маштабировании, так удобнее */
scaleFactor += -(0.035 * mwe.getWheelRotation());
scaleFactor = Math.max(0.05, scaleFactor);
scaleFactor = Math.min(scaleFactor, 4);
revalidate();
repaint();
}
}
}
P.S. Пытался убрать код под кат, не получилось (