blob: 764ae807286e41fee9fbde8b46c705b83afc59f3 [file] [log] [blame]
/**
* Copyright (c) 2019 Laurent CARON.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Laurent CARON (laurent.caron at gmail dot com) - initial API and implementation (bug 542777)
*/
package org.eclipse.swt.custom;
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;
/**
* This class add the following behaviour to <code>StyledText</code> widgets:
* <p>
* When the user clicks on the wheel, a circle with arrows appears. When the user moves the mouse,
* the StyledText content is scrolled (on the right or on the left for horizontal movements, up or down for vertical movements).
* </p>
*
* @since 3.110
*/
class MouseNavigator {
private final StyledText parent;
boolean navigationActivated = false;
private GC gc;
private static final int CIRCLE_RADIUS = 15;
private static final int CENTRAL_POINT_RADIUS = 2;
private Point originalMouseLocation;
private final Listener mouseDownListener, mouseUpListener, paintListener, mouseMoveListener, focusOutListener;
private boolean hasHBar, hasVBar;
private Cursor previousCursor;
MouseNavigator(final StyledText styledText) {
if (styledText == null) {
SWT.error(SWT.ERROR_NULL_ARGUMENT);
}
if (styledText.isDisposed()) {
SWT.error(SWT.ERROR_WIDGET_DISPOSED);
}
parent = styledText;
mouseDownListener = (event) -> {
onMouseDown(event);
};
parent.addListener(SWT.MouseDown, mouseDownListener);
mouseUpListener = (event) -> {
onMouseUp(event);
};
parent.addListener(SWT.MouseUp, mouseUpListener);
paintListener = (event) -> {
onPaint(event);
};
parent.addListener(SWT.Paint, paintListener);
mouseMoveListener = (event) -> {
onMouseMove(event);
};
parent.addListener(SWT.MouseMove, mouseMoveListener);
focusOutListener = (event) -> {
onFocusOut(event);
};
parent.addListener(SWT.FocusOut, focusOutListener);
}
void onMouseDown(Event e) {
if ((e.button != 2) || navigationActivated) {
return;
}
if (!parent.isVisible() || !parent.getEnabled() || parent.middleClickPressed) {
return;
}
// Widget has no bar or bars are not enabled
initBarState();
if (!hasHBar && !hasVBar) {
return;
}
navigationActivated = true;
previousCursor = parent.getCursor();
parent.setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_ARROW));
originalMouseLocation = getMouseLocation();
parent.redraw();
}
private void initBarState() {
hasHBar = computeHasHorizontalBar();
hasVBar = computeHasVerticalBar();
}
private boolean computeHasHorizontalBar() {
final ScrollBar horizontalBar = parent.getHorizontalBar();
final boolean hasHorizontalBar = horizontalBar != null && horizontalBar.isVisible();
final boolean exceedHorizontalSpace = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT).x > parent.getSize().x;
return hasHorizontalBar && exceedHorizontalSpace;
}
private boolean computeHasVerticalBar() {
final ScrollBar verticalBar = parent.getVerticalBar();
final boolean hasVerticalBar = verticalBar != null && verticalBar.isEnabled();
final boolean exceedVerticalSpace = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT).y > parent.getSize().y;
return hasVerticalBar && exceedVerticalSpace;
}
private void onMouseUp(Event e) {
if ((computeDist() < CIRCLE_RADIUS) && (computeDist() >= 0)) {
return;
}
deactivate();
}
public int computeDist() {
if (originalMouseLocation == null) {
return -1;
}
final Point mouseLocation = getMouseLocation();
final int deltaX = originalMouseLocation.x - mouseLocation.x;
final int deltaY = originalMouseLocation.y - mouseLocation.y;
final int dist = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
return dist;
}
private void deactivate() {
parent.setCursor(previousCursor);
navigationActivated = false;
originalMouseLocation = null;
parent.redraw();
}
private void onFocusOut(Event e) {
deactivate();
}
private void onMouseMove(Event e) {
if (!navigationActivated) {
return;
}
final Point mouseLocation = getMouseLocation();
final int deltaX = originalMouseLocation.x - mouseLocation.x;
final int deltaY = originalMouseLocation.y - mouseLocation.y;
final int dist = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (dist < CIRCLE_RADIUS) {
return;
}
parent.setRedraw(false);
if (hasHBar) {
final ScrollBar bar = parent.getHorizontalBar();
bar.setSelection((int) (bar.getSelection() - deltaX * .1));
fireSelectionEvent(e, bar);
}
if (hasVBar) {
final ScrollBar bar = parent.getVerticalBar();
bar.setSelection((int) (bar.getSelection() - deltaY * .1));
fireSelectionEvent(e, bar);
}
parent.setRedraw(true);
parent.redraw();
}
private void fireSelectionEvent(final Event e, final ScrollBar bar) {
final Event event = new Event();
event.widget = bar;
event.display = parent.getDisplay();
event.type = SWT.Selection;
event.time = e.time;
for (final Listener selectionListener : bar.getListeners(SWT.Selection)) {
selectionListener.handleEvent(event);
}
}
private Point getMouseLocation() {
final Point cursorLocation = Display.getCurrent().getCursorLocation();
final Point relativeCursorLocation = parent.toControl(cursorLocation);
return relativeCursorLocation;
}
private void onPaint(final Event e) {
if (!navigationActivated) {
return;
}
final Rectangle rect = parent.getClientArea();
if (rect.width == 0 || rect.height == 0) {
return;
}
gc = e.gc;
gc.setAntialias(SWT.ON);
gc.setAdvanced(true);
final Color oldForegroundColor = gc.getForeground();
final Color oldBackgroundColor = gc.getBackground();
gc.setBackground(parent.getForeground());
drawCircle();
drawCentralPoint();
drawArrows();
gc.setForeground(oldForegroundColor);
gc.setBackground(oldBackgroundColor);
}
private void drawCircle() {
gc.setBackground(parent.getBackground());
gc.setForeground(parent.getForeground());
gc.setAlpha(200);
gc.fillOval(originalMouseLocation.x - CIRCLE_RADIUS, originalMouseLocation.y - CIRCLE_RADIUS, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2);
gc.setBackground(parent.getForeground());
gc.setAlpha(255);
gc.drawOval(originalMouseLocation.x - CIRCLE_RADIUS, originalMouseLocation.y - CIRCLE_RADIUS, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2);
}
private void drawCentralPoint() {
gc.fillOval(originalMouseLocation.x - CENTRAL_POINT_RADIUS, originalMouseLocation.y - CENTRAL_POINT_RADIUS, CENTRAL_POINT_RADIUS * 2, CENTRAL_POINT_RADIUS * 2);
}
private void drawArrows() {
gc.setLineWidth(2);
if (hasHBar) {
drawHorizontalArrows();
}
if (hasVBar) {
drawVerticalArrows();
}
}
private void drawHorizontalArrows() {
final int[] points = new int[6];
// Left
points[0] = originalMouseLocation.x - 6;
points[1] = originalMouseLocation.y + 3;
points[2] = originalMouseLocation.x - 9;
points[3] = originalMouseLocation.y;
points[4] = originalMouseLocation.x - 6;
points[5] = originalMouseLocation.y - 3;
gc.drawPolyline(points);
// Right
points[0] = originalMouseLocation.x + 7;
points[1] = originalMouseLocation.y + 3;
points[2] = originalMouseLocation.x + 10;
points[3] = originalMouseLocation.y;
points[4] = originalMouseLocation.x + 7;
points[5] = originalMouseLocation.y - 3;
gc.drawPolyline(points);
}
private void drawVerticalArrows() {
final int[] points = new int[6];
// Upper
points[0] = originalMouseLocation.x - 3;
points[1] = originalMouseLocation.y - 6;
points[2] = originalMouseLocation.x;
points[3] = originalMouseLocation.y - 10;
points[4] = originalMouseLocation.x + 3;
points[5] = originalMouseLocation.y - 6;
gc.drawPolyline(points);
// Lower
points[0] = originalMouseLocation.x - 3;
points[1] = originalMouseLocation.y + 7;
points[2] = originalMouseLocation.x;
points[3] = originalMouseLocation.y + 11;
points[4] = originalMouseLocation.x + 3;
points[5] = originalMouseLocation.y + 7;
gc.drawPolyline(points);
}
void dispose() {
if (parent.isDisposed()) {
return;
}
parent.removeListener(SWT.MouseDown, mouseDownListener);
parent.removeListener(SWT.MouseUp, mouseUpListener);
parent.removeListener(SWT.Paint, paintListener);
parent.removeListener(SWT.MouseMove, mouseMoveListener);
parent.removeListener(SWT.MouseExit, focusOutListener);
}
}