/*******************************************************************************
* Copyright (c) 2007, 2014 compeople AG 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:
* compeople AG - initial API and implementation
*******************************************************************************/
package org.eclipse.riena.navigation.ui.swt.views;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.riena.navigation.IModuleGroupNode;
import org.eclipse.riena.navigation.IModuleNode;
import org.eclipse.riena.navigation.INavigationNode;
import org.eclipse.riena.navigation.ISubModuleNode;
import org.eclipse.riena.ui.swt.lnf.LnfKeyConstants;
import org.eclipse.riena.ui.swt.lnf.LnfManager;
import org.eclipse.riena.ui.swt.utils.SwtUtilities;
/**
* This class provides scrolling logic for the navigation with scroll buttons.
*
* @since 3.0
*/
public class ScrollButtonsSupport extends AbstractScrollingSupport {
// the SWT composite
private final ScrollControlComposite scrollControlComposite;
// offset of the scrolled composite relative to the main navigation composite
private int scrolledCompositeOffset = 0;
/**
*
* @param parent
* the composite parent
* @param flags
* SWT style flags
* @param navigationComponentProvider
*/
public ScrollButtonsSupport(final Composite parent, final IModuleNavigationComponentProvider navigationComponentProvider) {
super(navigationComponentProvider);
scrollControlComposite = new ScrollControlComposite(parent, SWT.NONE);
setBodyCompositeOffset(0);
}
/**
* {@inheritDoc}
*/
@Override
public void scroll() {
scrollControlComposite.setVisible(mayScroll());
scrollToActive();
}
/**
* @return the scrolled composite which is placed relative to the navigation main composite
*/
public Composite getScrollComposite() {
return scrollControlComposite;
}
/**
* Returns the height of the scroll buttons.
*
* @return height of buttons
*/
public int getButtonHeight() {
final Integer height = LnfManager.getLnf().getIntegerSetting(LnfKeyConstants.NAVIGATION_SCROLL_BUTTON_HEIGHT, 15);
return SwtUtilities.convertYToDpi(height);
}
/**
* {@inheritDoc}
*/
@Override
protected void scrollUp(final int pixels) {
final int offset = Math.min(scrolledCompositeOffset + pixels, 0);
setBodyCompositeOffset(offset);
}
/**
* {@inheritDoc}
*/
@Override
protected void scrollDown(final int pixels) {
final int offset = Math.max(getNavigationComponentHeight() - getScrolledComponentHeight(), scrolledCompositeOffset - pixels);
setBodyCompositeOffset(offset);
}
/**
* Triggers a single scroll-tick in the direction specified in the parameter.
*
* @param direction
* the direction in which to scroll
*/
private void scroll(final ScrollDirection direction) {
if (direction == ScrollDirection.UP) {
scrollUp(SCROLLING_STEP);
} else {
scrollDown(SCROLLING_STEP);
}
}
/**
* {@inheritDoc}
*/
@Override
protected boolean scrollTo(final Composite topComp, final Composite bottomComp) {
final int compBottomEdge = bottomComp.toDisplay(0, bottomComp.getSize().y).y;
boolean result = scrollToBottom(compBottomEdge);
final int compTopEdge = topComp.toDisplay(0, 0).y;
result = result || scrollToTop(compTopEdge);
return result;
}
/**
* {@inheritDoc}
*/
@Override
protected boolean scrollTo(final Tree tree) {
boolean result = false;
if (tree.getSelectionCount() > 0) {
final TreeItem item = tree.getSelection()[0];
final Rectangle itemBounds = item.getBounds();
final int treeBottomEdge = tree.toDisplay(itemBounds.x, itemBounds.y + tree.getItemHeight()).y;
result = scrollToBottom(treeBottomEdge);
final int treeTopEdge = tree.toDisplay(itemBounds.x, itemBounds.y).y;
result = result || scrollToTop(treeTopEdge);
}
return result;
}
// helping methods
//////////////////
private void scrollToActive() {
if (!mayScroll()) {
resetScrolling();
return;
}
final INavigationNode<?> activeNode = getActiveNode();
if (!scrollTo(activeNode)) {
ensureFilledToBottom();
}
}
private boolean scrollTo(final INavigationNode<?> activeNode) {
boolean result = false;
if (activeNode instanceof IModuleGroupNode) {
final ModuleGroupView view = navigationComponentProvider.getModuleGroupViewForNode((IModuleGroupNode) activeNode);
result = scrollTo(view, view);
} else if (activeNode instanceof IModuleNode) {
result = scrollTo((IModuleNode) activeNode);
} else if (activeNode instanceof ISubModuleNode) {
final IModuleNode module = (IModuleNode) activeNode.getParent();
result = scrollTo(module);
}
return result;
}
private boolean scrollToBottom(final int bottomEdgeToDisplay) {
final int bottomEdge = getNavigationComponent().toDisplay(0, getNavigationComponentHeight()).y;
final boolean scroll = bottomEdgeToDisplay > bottomEdge;
if (scroll) {
scrollDown(bottomEdgeToDisplay - bottomEdge);
}
return scroll;
}
private boolean scrollToTop(final int topEdgeToDisplay) {
final int topEdge = getNavigationComponent().toDisplay(0, 0).y;
final boolean scroll = topEdgeToDisplay < topEdge;
if (scroll) {
scrollUp(topEdge - topEdgeToDisplay);
}
return scroll;
}
private void ensureFilledToBottom() {
// use height
final int bodyBottomY = scrolledCompositeOffset + getScrolledComponentHeight();
if (bodyBottomY < getNavigationComponentHeight()) {
setBodyCompositeOffset(scrolledCompositeOffset + (getNavigationComponentHeight() - bodyBottomY));
}
}
private void resetScrolling() {
setBodyCompositeOffset(0);
scrollControlComposite.setVisible(false);
}
/**
* this method moves the scrolled component upwards and downwards for simulation of scrolling
*
* @param yScrolledOffset
* the new vertical offset of the scrolled composite
*/
private void setBodyCompositeOffset(final int yScrolledOffset) {
if (yScrolledOffset != scrolledCompositeOffset) {
scrolledCompositeOffset = yScrolledOffset;
updateUI();
}
}
private void updateUI() {
final FormData formData = new FormData();
formData.top = new FormAttachment(0, scrolledCompositeOffset);
getScrolledComponent().setLayoutData(formData);
getScrolledComponent().getParent().layout();
}
/**
* this composite contains the "up"- and "down" buttons. Manual scrolling is triggered here!
*/
private final class ScrollControlComposite extends Composite {
private Button upButton;
private Button downButton;
public ScrollControlComposite(final Composite parent, final int style) {
super(parent, style);
getNavigationComponent().addControlListener(new NavigationResizeListener());
setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.NAVIGATION_BACKGROUND));
initControlButtons();
}
/*
* defines the layout
*/
private void initControlButtons() {
setLayout(new FormLayout());
//final ScrollDelegation delegationAdaper = new ScrollDelegation();
final ScrollDelegate scrollingButtons = new ScrollDelegate();
upButton = new Button(this, SWT.NONE);
upButton.setBackground(this.getBackground());
upButton.setImage(LnfManager.getLnf().getImage(LnfKeyConstants.NAVIGATION_SCROLL_UP_ICON));
//upButton.addSelectionListener(delegationAdaper);
upButton.addMouseListener(scrollingButtons);
layoutUpButton();
downButton = new Button(this, SWT.NONE);
downButton.setBackground(this.getBackground());
downButton.setImage(LnfManager.getLnf().getImage(LnfKeyConstants.NAVIGATION_SCROLL_DOWN_ICON));
//downButton.addSelectionListener(delegationAdaper);
downButton.addMouseListener(scrollingButtons);
layoutDownButton();
}
private void layoutDownButton() {
final FormData fd = new FormData();
fd.left = new FormAttachment(upButton, SwtUtilities.convertXToDpi(1));
fd.right = new FormAttachment(100, SwtUtilities.convertXToDpiTruncate(-2));
fd.height = getButtonHeight();
downButton.setLayoutData(fd);
}
private void layoutUpButton() {
final FormData fd = new FormData();
fd.left = new FormAttachment(0, SwtUtilities.convertYToDpi(2));
fd.right = new FormAttachment(50, 0);
fd.height = getButtonHeight();
upButton.setLayoutData(fd);
}
private final class NavigationResizeListener extends ControlAdapter {
@Override
public void controlResized(final ControlEvent e) {
setVisible(mayScroll());
}
}
// /**
// * action delegation for manual scrolling
// */
// private final class ScrollDelegation extends SelectionAdapter {
// @Override
// public void widgetSelected(final SelectionEvent e) {
// if (e.getSource() == upButton) {
// scrollUp(SCROLLING_STEP);
// } else {
// scrollDown(SCROLLING_STEP);
// }
// }
// }
/**
* Responsible for effecting the manual vertical scrolling in the navigation tree.
*/
private final class ScrollDelegate implements MouseListener {
private final ScrollRunnable scrollRunnable = new ScrollRunnable();
/** The runnable that is invoked to trigger the actual scrolling. */
private class ScrollRunnable implements Runnable {
/**
* Maximum (and default) time interval between triggering scroll-ticks, in milliseconds
*/
private static final int MAX_SCROLL_INTERVAL = 150;
/** Minimum time interval between scroll-ticks, in ms */
private static final int MIN_SCROLL_INTERVAL = 30;
/**
* By how much scrolling accelerates after each scroll-tick. Should be greater than 1 for speed-up, exactly 1 for constant speed, and less than
* 1 for slow-down.
*/
public static final double SCROLL_INTERVAL_SPEEDUP = 1.05;
/** In which direction the scroll shall go */
private volatile ScrollDirection scrollDirection;
/**
* Current speed-adjusted scroll interval. Starts as {@value MAX_SCROLL_INTERVAL}, never drops below {@value MIN_SCROLL_INTERVAL}.
*/
private int currentScrollInterval;
public ScrollRunnable() {
reset();
}
void setDirection(final ScrollDirection scrollDirection) {
this.scrollDirection = scrollDirection;
}
public void run() {
if (getNavigationComponent().isDisposed() || getScrolledComponent().isDisposed()) {
return;
}
scroll(scrollDirection);
if (currentScrollInterval / SCROLL_INTERVAL_SPEEDUP > MIN_SCROLL_INTERVAL) {
currentScrollInterval /= SCROLL_INTERVAL_SPEEDUP;
}
getDisplay().timerExec(currentScrollInterval, this); // continue scrolling, recurse
}
public void reset() {
currentScrollInterval = MAX_SCROLL_INTERVAL;
}
}
public void mouseDoubleClick(final MouseEvent e) {
}
public void mouseDown(final MouseEvent e) {
if (e.getSource() == upButton) {
scrollRunnable.setDirection(ScrollDirection.UP);
} else {
scrollRunnable.setDirection(ScrollDirection.DOWN);
}
getDisplay().timerExec(0, scrollRunnable); // start scrolling
}
public void mouseUp(final MouseEvent e) {
getDisplay().timerExec(-1, scrollRunnable); // stop scrolling
scrollRunnable.reset();
}
}
}
private enum ScrollDirection {
UP, DOWN;
}
}