/******************************************************************************* * Copyright (c) 2016 Fabio Zadrozny and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation *******************************************************************************/ package org.eclipse.e4.ui.internal.css.swt.dom.scrollbar; import org.eclipse.core.runtime.Platform; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseWheelListener; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Scrollable; public abstract class AbstractThemedScrollBarAdapter implements ControlListener, Listener, DisposeListener, KeyListener, MouseWheelListener, SelectionListener { public static class ScrollBarSettings implements IScrollBarSettings { private Color fScrollForegroundColor; private Color fScrollBackgroundColor; private int fScrollBarWidth = 6; private int fMouseNearScrollScrollBarWidth = 15; private int fScrollBarBorderRadius; private boolean fScrollBarThemed; @Override public void setScrollBarThemed(boolean themed) { fScrollBarThemed = themed; } @Override public boolean getScrollBarThemed() { return fScrollBarThemed; } @Override public void setBackgroundColor(Color newColor) { this.fScrollBackgroundColor = newColor; } @Override public void setForegroundColor(Color newColor) { this.fScrollForegroundColor = newColor; } @Override public Color getForegroundColor() { if (this.fScrollForegroundColor != null && this.fScrollForegroundColor.isDisposed()) { this.fScrollForegroundColor = null; } return this.fScrollForegroundColor; } @Override public Color getBackgroundColor() { if (this.fScrollBackgroundColor != null && this.fScrollBackgroundColor.isDisposed()) { this.fScrollBackgroundColor = null; } return this.fScrollBackgroundColor; } @Override public void setScrollBarWidth(int width) { if (width < 1) { width = 1; } this.fScrollBarWidth = width; } @Override public int getScrollBarWidth() { return fScrollBarWidth; } @Override public void setMouseNearScrollScrollBarWidth(int width) { if (width < 1) { width = 1; } this.fMouseNearScrollScrollBarWidth = width; } @Override public int getMouseNearScrollScrollBarWidth() { return fMouseNearScrollScrollBarWidth; } @Override public void setScrollBarBorderRadius(int radius) { if (radius < 0) { radius = 0; } fScrollBarBorderRadius = radius; } @Override public int getScrollBarBorderRadius() { return fScrollBarBorderRadius; } } /** * Interface for the scrollbar painter. */ public static interface IScrollBarPainter extends PaintListener { /** * Makes sure that scrollbars are redrawn. */ void redrawScrollBars(); void install(Scrollable scrollable); void uninstall(); } protected final IScrollBarSettings fScrollBarSettings; protected final Scrollable fScrollable; protected final IScrollBarPainter fPainter; protected final AbstractScrollHandler fHorizontalScrollHandler; protected final AbstractScrollHandler fVerticalScrollHandler; private final Display fDisplay; protected Point fLastHorizontalAndTopPixel = null; private Cursor fOldCursor; private final ScrollOnMouseDownRunnable fScrollOnMouseDownTimer; protected boolean installed = false; private final static boolean isWindowsOS = Platform.OS_WIN32.equals(Platform.getOS()); public AbstractThemedScrollBarAdapter(Scrollable scrollable, AbstractScrollHandler horizontalScrollHandler, AbstractScrollHandler verticalScrollHandler, IScrollBarSettings scrollBarSettings) { this.fScrollable = scrollable; this.fScrollBarSettings = scrollBarSettings; fHorizontalScrollHandler = horizontalScrollHandler; fVerticalScrollHandler = verticalScrollHandler; fPainter = createPaintListener(); // We need to add ourselves to the display because these events // shouldn't get to the actual StyledText (i.e.: we have to handle // the click on the drag position without letting the event reach the // StyledText). Also, we want to get mouse positions out of the // StyledText (to detect if the thicker version of the scroll bar // should be used). fDisplay = Display.getCurrent(); fScrollOnMouseDownTimer = new ScrollOnMouseDownRunnable(fScrollable, fDisplay); } protected void install() { if (fScrollable.isDisposed() || fDisplay.isDisposed() || installed) { return; } installed = true; fHorizontalScrollHandler.install(this); fVerticalScrollHandler.install(this); fDisplay.addFilter(SWT.MouseDown, this); fDisplay.addFilter(SWT.MouseUp, this); fDisplay.addFilter(SWT.MouseMove, this); fDisplay.addFilter(SWT.MenuDetect, this); fScrollable.addControlListener(this); fScrollable.addKeyListener(this); fScrollable.addMouseWheelListener(this); fScrollable.addDisposeListener(this); fScrollable.addPaintListener(fPainter); fPainter.install(fScrollable); } protected void uninstall(boolean disposing) { if (!installed) { return; } installed = false; this.fPainter.uninstall(); if (!fScrollable.isDisposed()) { fScrollable.removeControlListener(this); fScrollable.removeKeyListener(this); fScrollable.removeMouseWheelListener(this); fScrollable.removeDisposeListener(this); fScrollable.removePaintListener(fPainter); } if (!fDisplay.isDisposed()) { fDisplay.removeFilter(SWT.MouseDown, this); fDisplay.removeFilter(SWT.MouseUp, this); fDisplay.removeFilter(SWT.MouseMove, this); fDisplay.removeFilter(SWT.MenuDetect, this); } fHorizontalScrollHandler.uninstall(this, disposing); fVerticalScrollHandler.uninstall(this, disposing); } protected abstract IScrollBarPainter createPaintListener(); private void setArrowCursor() { if (fOldCursor == null) { Display display = Display.getCurrent(); fOldCursor = fScrollable.getCursor(); Cursor arrowCursor = display.getSystemCursor(SWT.CURSOR_ARROW); fScrollable.setCursor(arrowCursor); } } private void restoreCursor() { if (fOldCursor != null) { fOldCursor = null; fScrollable.setCursor(null); } } /** * Helper class to keep scrolling when the mouse is down. */ private static class ScrollOnMouseDownRunnable implements Runnable { private final Scrollable fScrollable; private final Display fDisplay; private AbstractScrollHandler fTargetScrollHandler; private Point fControlPos; public ScrollOnMouseDownRunnable(Scrollable scrollable, Display display) { this.fScrollable = scrollable; this.fDisplay = display; } @Override public void run() { if (fControlPos == null || fScrollable.isDisposed() || this.fDisplay.isDisposed()) { return; } // Note: we re-click based on the current position. Point cursorLocation = fDisplay.getCursorLocation(); Point point = fScrollable.toControl(cursorLocation); Rectangle currClientArea = fScrollable.getClientArea(); fTargetScrollHandler.scrollOnMouseDown(fScrollable, point, currClientArea); // After the first, the interval is only 50 millis fDisplay.timerExec(50, this); } public void start(Point controlPos, AbstractScrollHandler targetScrollHandler) { this.fControlPos = controlPos; this.fTargetScrollHandler = targetScrollHandler; if (controlPos != null && targetScrollHandler != null) { // First one is 200 millis fDisplay.timerExec(200, this); } } public void stop() { this.fControlPos = null; } } @Override public void controlMoved(ControlEvent e) { setMouseNearScrollScrollBar(false, false); } @Override public void controlResized(ControlEvent e) { setMouseNearScrollScrollBar(false, false); fPainter.redrawScrollBars(); } @Override public void handleEvent(Event event) { // pos is always relative to widget if (!(event.widget instanceof Control)) { return; } Control control = (Control) event.widget; Point displayPos; if (event.type == SWT.MenuDetect) { // a MenuDetect is already in display coordinates displayPos = new Point(event.x, event.y); } else { // MouseUp/Down/Move is in control coordinates displayPos = control.toDisplay(event.x, event.y); } Point controlPos = fScrollable.toControl(displayPos); if (event.type == SWT.MenuDetect || (isWindowsOS && event.type == SWT.MouseDown && event.button != 1)) { // Bug 491577: on windows, don't scroll if we're not left-clicking // (and do nothing for the context menu on all platforms). if (this.fHorizontalScrollHandler.mousePosOverScroll(fScrollable, controlPos) || this.fVerticalScrollHandler.mousePosOverScroll(fScrollable, controlPos)) { this.stopEventPropagation(event); } } else if (event.type == SWT.MouseDown) { fLastHorizontalAndTopPixel = computeHorizontalAndTopPixel(); if (event.widget == fScrollable) { Rectangle currClientArea = fScrollable.getClientArea(); if (this.fHorizontalScrollHandler.startDragOnMouseDown(fScrollable, controlPos, fLastHorizontalAndTopPixel, currClientArea) || this.fVerticalScrollHandler.startDragOnMouseDown(fScrollable, controlPos, fLastHorizontalAndTopPixel, currClientArea)) { this.stopEventPropagation(event); } else if (this.fHorizontalScrollHandler.scrollOnMouseDown(fScrollable, controlPos, currClientArea)) { this.fScrollOnMouseDownTimer.start(controlPos, this.fHorizontalScrollHandler); this.stopEventPropagation(event); } else if (this.fVerticalScrollHandler.scrollOnMouseDown(fScrollable, controlPos, currClientArea)) { this.fScrollOnMouseDownTimer.start(controlPos, this.fVerticalScrollHandler); this.stopEventPropagation(event); } } checkChangedHorizontalAndTopPixel(); } else if (event.type == SWT.MouseUp) { this.fScrollOnMouseDownTimer.stop(); boolean handled = this.fHorizontalScrollHandler.stopDragOnMouseUp(fScrollable); handled |= this.fVerticalScrollHandler.stopDragOnMouseUp(fScrollable); checkChangedHorizontalAndTopPixel(); if (handled) { this.stopEventPropagation(event); } } else if (event.type == SWT.MouseMove) { if (!fHorizontalScrollHandler.isDragging() && !fVerticalScrollHandler.isDragging()) { Rectangle currClientArea = fScrollable.getClientArea(); Rectangle proximityRectHorizontal = fHorizontalScrollHandler.computeProximityRect(currClientArea); Rectangle proximityRectVertical = fVerticalScrollHandler.computeProximityRect(currClientArea); boolean showHorizontal = ((proximityRectHorizontal != null && proximityRectHorizontal.contains(controlPos.x, controlPos.y)) && currClientArea.width < (fHorizontalScrollHandler.fScrollBar.getMaximum() - fHorizontalScrollHandler.fScrollBar.getMinimum())); boolean showVertical = ((proximityRectVertical != null && proximityRectVertical.contains(controlPos.x, controlPos.y)) && currClientArea.height < (fVerticalScrollHandler.fScrollBar.getMaximum() - fVerticalScrollHandler.fScrollBar.getMinimum())); if (showVertical || showHorizontal) { setMouseNearScrollScrollBar(showVertical, showHorizontal); setArrowCursor(); } else { setMouseNearScrollScrollBar(false, false); restoreCursor(); } } if (fHorizontalScrollHandler.dragOnMouseMove(fScrollable, controlPos) || fVerticalScrollHandler.dragOnMouseMove(fScrollable, controlPos)) { this.stopEventPropagation(event); return; } checkChangedHorizontalAndTopPixel(); } } /** * Makes sure that the passed event is no longer propagated. */ private void stopEventPropagation(Event event) { // Note: just setting event.doit does not work (the display // keeps on going with it, so, we set the event type to None // -- which then stops it). event.type = SWT.None; event.doit = false; } protected boolean setMouseNearScrollScrollBar(boolean mouseNearVerticalScroll, boolean mouseNearHorizontalScroll) { if (mouseNearVerticalScroll != fVerticalScrollHandler.getMouseNearScrollScrollBar() || mouseNearHorizontalScroll != fHorizontalScrollHandler.getMouseNearScrollScrollBar()) { fHorizontalScrollHandler.setCursorNearScroll(mouseNearHorizontalScroll); fVerticalScrollHandler.setCursorNearScroll(mouseNearVerticalScroll); fPainter.redrawScrollBars(); return true; } return false; } protected void checkChangedHorizontalAndTopPixel() { Point newHorizontalAndTopPixel = computeHorizontalAndTopPixel(); if (!newHorizontalAndTopPixel.equals(fLastHorizontalAndTopPixel)) { fLastHorizontalAndTopPixel = newHorizontalAndTopPixel; fPainter.redrawScrollBars(); } } protected abstract Point computeHorizontalAndTopPixel(); // API to customize scroll bar. public void setScrollBarThemed(boolean themed) { if (themed != getScrollBarThemed()) { fScrollBarSettings.setScrollBarThemed(themed); if (themed) { install(); } else { uninstall(false); } } } public boolean getScrollBarThemed() { return fScrollBarSettings.getScrollBarThemed(); } public void setScrollBarBackgroundColor(Color newColor) { fScrollBarSettings.setBackgroundColor(newColor); this.fPainter.redrawScrollBars(); } public Color getScrollBarBackgroundColor() { return fScrollBarSettings.getBackgroundColor(); } public void setScrollBarForegroundColor(Color newColor) { fScrollBarSettings.setForegroundColor(newColor); this.fPainter.redrawScrollBars(); } public Color getScrollBarForegroundColor() { return fScrollBarSettings.getForegroundColor(); } public void setScrollBarWidth(int width) { fScrollBarSettings.setScrollBarWidth(width); this.fPainter.redrawScrollBars(); } public int getScrollBarWidth() { return fScrollBarSettings.getScrollBarWidth(); } public void setMouseNearScrollScrollBarWidth(int width) { fScrollBarSettings.setMouseNearScrollScrollBarWidth(width); this.fPainter.redrawScrollBars(); } public int getMouseNearScrollScrollBarWidth() { return fScrollBarSettings.getMouseNearScrollScrollBarWidth(); } public void setVerticalScrollBarVisible(boolean visible) { this.fVerticalScrollHandler.setVisible(visible); } public void setHorizontalScrollBarVisible(boolean visible) { this.fHorizontalScrollHandler.setVisible(visible); } public boolean getVerticalScrollBarVisible() { return this.fVerticalScrollHandler.getVisible(); } public boolean getHorizontalScrollBarVisible() { return this.fHorizontalScrollHandler.getVisible(); } public void setScrollBarBorderRadius(int radius) { fScrollBarSettings.setScrollBarBorderRadius(radius); this.fPainter.redrawScrollBars(); } public int getScrollBarBorderRadius() { return fScrollBarSettings.getScrollBarBorderRadius(); } @Override public void widgetDisposed(DisposeEvent e) { uninstall(true); } @Override public void keyPressed(KeyEvent e) { checkChangedHorizontalAndTopPixel(); } @Override public void keyReleased(KeyEvent e) { checkChangedHorizontalAndTopPixel(); } @Override public void mouseScrolled(MouseEvent e) { checkChangedHorizontalAndTopPixel(); } @Override public void widgetSelected(SelectionEvent e) { checkChangedHorizontalAndTopPixel(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }