org.netbeans.api.visual.widget.ScrollWidget Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.api.visual.widget;
import org.netbeans.api.visual.action.WidgetAction;
import org.netbeans.api.visual.action.SelectProvider;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.border.Border;
import org.netbeans.api.visual.border.BorderFactory;
import org.netbeans.api.visual.layout.Layout;
import org.netbeans.api.visual.model.ObjectState;
import org.openide.util.ImageUtilities;
import org.openide.util.Utilities;
import java.awt.*;
import java.awt.event.MouseEvent;
/**
* This a scroll widget similar to JScrollPane. A scrolled widget is could be manipulated with getView and setView methods.
* The scroll-bars are automatically visible when the scrolled widget is bigger than the widget.
*
* @author David Kaspar
*/
// TODO - does not cooperate with action-tools - actions from null-tool should be working all the time
// TODO - invalid calculation/checking showVertical and showHorizontal when view.preferredLocation has negative values
public class ScrollWidget extends Widget {
private static final int BAR_VERTICAL_SIZE = 16;
private static final int BAR_HORIZONTAL_SIZE = 16;
private static final Point POINT_EMPTY = new Point ();
private static final Rectangle RECTANGLE_EMPTY = new Rectangle ();
private static final Border BORDER_RAISED = BorderFactory.createBevelBorder (true);
private static final Border BORDER_LOWERED = BorderFactory.createBevelBorder (false);
private Widget viewport;
private Widget view;
private SliderWidget verticalSlider;
private SliderWidget horizontalSlider;
private Widget upArrow;
private Widget downArrow;
private Widget leftArrow;
private Widget rightArrow;
/**
* Creates a scroll widget.
* @param scene
*/
public ScrollWidget (Scene scene) {
super (scene);
setLayout (new ScrollLayout ());
setCheckClipping (true);
viewport = new Widget (scene);
viewport.setCheckClipping (true);
addChild (viewport);
addChild (upArrow = createUpArrow ());
addChild (verticalSlider = createVerticalSlider ());
addChild (downArrow = createDownArrow ());
addChild (leftArrow = createLeftArrow ());
addChild (horizontalSlider = createHorizontalSlider ());
addChild (rightArrow = createRightArrow ());
upArrow.getActions ().addAction (ActionFactory.createSelectAction (new UnitScrollProvider (0, -16)));
downArrow.getActions ().addAction (ActionFactory.createSelectAction (new UnitScrollProvider (0, 16)));
leftArrow.getActions ().addAction (ActionFactory.createSelectAction (new UnitScrollProvider (-16, 0)));
rightArrow.getActions ().addAction (ActionFactory.createSelectAction (new UnitScrollProvider (16, 0)));
horizontalSlider.getActions ().addAction (new BlockScrollAction (horizontalSlider, - 64, 0));
verticalSlider.getActions ().addAction (new BlockScrollAction (verticalSlider, 0, - 64));
horizontalSlider.getActions ().addAction (new SliderAction (horizontalSlider));
verticalSlider.getActions ().addAction (new SliderAction (verticalSlider));
}
/**
* Creates a scroll widget.
* @param scene the scene
* @param view the scrolled view
*/
public ScrollWidget (Scene scene, Widget view) {
this (scene);
setView (view);
}
private SliderWidget createVerticalSlider () {
return new SliderWidget (getScene (), true);
}
private SliderWidget createHorizontalSlider () {
return new SliderWidget (getScene (), false);
}
private Widget createUpArrow () {
return new ButtonWidget (getScene (), ImageUtilities.loadImage ("org/netbeans/modules/visual/resources/arrow-up.png")); // NOI18N
}
private Widget createDownArrow () {
return new ButtonWidget (getScene (), ImageUtilities.loadImage ("org/netbeans/modules/visual/resources/arrow-down.png")); // NOI18N
}
private Widget createLeftArrow () {
return new ButtonWidget (getScene (), ImageUtilities.loadImage ("org/netbeans/modules/visual/resources/arrow-left.png")); // NOI18N
}
private Widget createRightArrow () {
return new ButtonWidget (getScene (), ImageUtilities.loadImage ("org/netbeans/modules/visual/resources/arrow-right.png")); // NOI18N
}
/**
* Returns an inner widget.
* @return the inner widget
*/
public final Widget getView () {
return view;
}
/**
* Sets an scrolled widget.
* @param view the scrolled widget
*/
public final void setView (Widget view) {
if (this.view != null)
viewport.removeChild (this.view);
this.view = view;
if (this.view != null)
viewport.addChild (this.view);
}
/**
* Calculates a client area as from the scroll widget preferred bounds.
* @return the calculated client area
*/
protected Rectangle calculateClientArea () {
return new Rectangle (calculateSize ());
}
private Dimension calculateSize () {
if (isPreferredBoundsSet ()) {
Rectangle preferredBounds = getPreferredBounds ();
Insets insets = getBorder ().getInsets ();
return new Dimension (preferredBounds.width - insets.left - insets.right, preferredBounds.height - insets.top - insets.bottom);
} else
return view.getBounds ().getSize ();
}
private final class ScrollLayout implements Layout {
public void layout (Widget widget) {
Point scrollWidgetClientAreaLocation;
if (isPreferredBoundsSet ()) {
scrollWidgetClientAreaLocation = getPreferredBounds ().getLocation ();
Insets insets = getBorder ().getInsets ();
scrollWidgetClientAreaLocation.translate (insets.left, insets.top);
} else
scrollWidgetClientAreaLocation = new Point ();
Rectangle viewBounds = view != null ? view.getPreferredBounds () : new Rectangle ();
Rectangle viewportBounds = view != null ? new Rectangle (view.getLocation (), calculateSize ()) : new Rectangle ();
boolean showVertical = checkVertical (viewBounds, viewportBounds);
boolean showHorizontal = checkHorizontal (viewBounds, viewportBounds);
if (showVertical) {
viewportBounds.width -= BAR_HORIZONTAL_SIZE;
showHorizontal = checkHorizontal (viewBounds, viewportBounds);
}
if (showHorizontal) {
viewportBounds.height -= BAR_VERTICAL_SIZE;
if (! showVertical) {
showVertical = checkVertical (viewBounds, viewportBounds);
if (showVertical)
viewportBounds.width -= BAR_HORIZONTAL_SIZE;
}
}
viewport.resolveBounds (scrollWidgetClientAreaLocation, new Rectangle (viewportBounds.getSize ()));
int x1 = scrollWidgetClientAreaLocation.x;
int x2 = scrollWidgetClientAreaLocation.x + viewportBounds.width;
int y1 = scrollWidgetClientAreaLocation.y;
int y2 = scrollWidgetClientAreaLocation.y + viewportBounds.height;
if (showVertical) {
upArrow.resolveBounds (new Point (x2, y1), new Rectangle (BAR_HORIZONTAL_SIZE, BAR_VERTICAL_SIZE));
downArrow.resolveBounds (new Point (x2, y2 - BAR_VERTICAL_SIZE), new Rectangle (BAR_HORIZONTAL_SIZE, BAR_VERTICAL_SIZE));
verticalSlider.resolveBounds (new Point (x2, y1 + BAR_VERTICAL_SIZE), new Rectangle (BAR_HORIZONTAL_SIZE, viewportBounds.height - BAR_VERTICAL_SIZE - BAR_VERTICAL_SIZE));
} else {
upArrow.resolveBounds (POINT_EMPTY, RECTANGLE_EMPTY);
downArrow.resolveBounds (POINT_EMPTY, RECTANGLE_EMPTY);
verticalSlider.resolveBounds (POINT_EMPTY, RECTANGLE_EMPTY);
}
if (showHorizontal) {
leftArrow.resolveBounds (new Point (x1, y2), new Rectangle (BAR_HORIZONTAL_SIZE, BAR_VERTICAL_SIZE));
rightArrow.resolveBounds (new Point (x2 - BAR_HORIZONTAL_SIZE, y2), new Rectangle (BAR_HORIZONTAL_SIZE, BAR_VERTICAL_SIZE));
horizontalSlider.resolveBounds (new Point (x1 + BAR_HORIZONTAL_SIZE, y2), new Rectangle (viewportBounds.width - BAR_HORIZONTAL_SIZE - BAR_HORIZONTAL_SIZE, BAR_VERTICAL_SIZE));
} else {
leftArrow.resolveBounds (POINT_EMPTY, RECTANGLE_EMPTY);
rightArrow.resolveBounds (POINT_EMPTY, RECTANGLE_EMPTY);
horizontalSlider.resolveBounds (POINT_EMPTY, RECTANGLE_EMPTY);
}
verticalSlider.setValues (viewBounds.y + viewportBounds.y, viewBounds.y + viewportBounds.y + viewBounds.height, 0, viewportBounds.height);
horizontalSlider.setValues (viewBounds.x + viewportBounds.x, viewBounds.x + viewportBounds.x + viewBounds.width, 0, viewportBounds.width);
}
public boolean requiresJustification (Widget widget) {
return false;
}
public void justify (Widget widget) {
}
private boolean checkHorizontal (Rectangle viewBounds, Rectangle viewportBounds) {
return (viewBounds.x < viewportBounds.x || viewBounds.x + viewBounds.width > viewportBounds.x + viewportBounds.width) && viewportBounds.width > 3 * BAR_HORIZONTAL_SIZE;
}
private boolean checkVertical (Rectangle viewBounds, Rectangle viewportBounds) {
return (viewBounds.y < viewportBounds.y || viewBounds.y + viewBounds.height > viewportBounds.y + viewportBounds.height) && viewportBounds.height > 3 * BAR_VERTICAL_SIZE;
}
}
private static class ButtonWidget extends ImageWidget {
private ButtonWidget (Scene scene, Image image) {
super (scene, image);
setOpaque (true);
updateAiming (false);
}
protected void notifyStateChanged (ObjectState previousState, ObjectState state) {
if (previousState.isWidgetAimed () != state.isWidgetAimed ())
updateAiming (state.isWidgetAimed ());
}
private void updateAiming (boolean state) {
setBackground (state ? Color.GRAY : Color.LIGHT_GRAY);
setBorder (state ? BORDER_LOWERED : BORDER_RAISED);
}
}
private static class SliderWidget extends Widget {
private enum Part {
OUTSIDE, BEFORE, SLIDER, AFTER
}
private static final Color COLOR = new Color (0x5B87CE);
private static final Border BORDER = BorderFactory.createBevelBorder (true, COLOR);
private boolean vertical;
private long minimumValue = 0;
private long maximumValue = 100;
private long startValue = 0;
private long endValue = 100;
private SliderWidget (Scene scene, boolean vertical) {
super (scene);
this.vertical = vertical;
setOpaque (true);
setBackground (Color.LIGHT_GRAY);
}
public boolean isVertical () {
return vertical;
}
public long getMinimumValue () {
return minimumValue;
}
public void setValues (long minimumValue, long maximumValue, long startValue, long endValue) {
this.minimumValue = minimumValue;
this.maximumValue = maximumValue;
this.startValue = startValue;
this.endValue = endValue;
repaint ();
}
public void setMinimumValue (long minimumValue) {
this.minimumValue = minimumValue;
repaint ();
}
public long getMaximumValue () {
return maximumValue;
}
public void setMaximumValue (long maximumValue) {
this.maximumValue = maximumValue;
repaint ();
}
public long getStartValue () {
return startValue;
}
public void setStartValue (long startValue) {
this.startValue = startValue;
repaint ();
}
public long getEndValue () {
return endValue;
}
public void setEndValue (long endValue) {
this.endValue = endValue;
repaint ();
}
public Part getPartHitAt (Point localLocation) {
if (! isHitAt (localLocation))
return Part.OUTSIDE;
Rectangle clientArea = getClientArea ();
long area;
int point;
if (vertical) {
area = clientArea.height;
point = localLocation.y - clientArea.y;
} else {
area = clientArea.width;
point = localLocation.x - clientArea.x;
}
long s = Math.min (Math.max (startValue, minimumValue), maximumValue);
long e = Math.min (Math.max (endValue, minimumValue), maximumValue);
int start = (int) ((float) area * (float) (s - minimumValue) / (float) (maximumValue - minimumValue));
int end = (int) ((float) area * (float) (e - minimumValue) / (float) (maximumValue - minimumValue));
if (point < start)
return Part.BEFORE;
else if (point >= end)
return Part.AFTER;
else
return Part.SLIDER;
}
public float getPixelIncrement () {
return (float) (vertical ? getClientArea ().height : getClientArea ().width) / (float) (maximumValue - minimumValue);
}
protected void paintWidget () {
Graphics2D gr = getGraphics ();
Rectangle clientArea = getClientArea ();
long area = vertical ? clientArea.height : clientArea.width;
if (minimumValue < maximumValue && startValue < endValue) {
long s = Math.min (Math.max (startValue, minimumValue), maximumValue);
int start = (int) ((float) area * (float) (s - minimumValue) / (float) (maximumValue - minimumValue));
long e = Math.min (Math.max (endValue, minimumValue), maximumValue);
int end = (int) ((float) area * (float) (e - minimumValue) / (float) (maximumValue - minimumValue));
gr.setColor (COLOR);
if (start + 4 < end) {
if (vertical) {
gr.fillRect (clientArea.x + 2, clientArea.y + start + 2, clientArea.width - 4, end - start - 4);
BORDER.paint (gr, new Rectangle (clientArea.x, clientArea.y + start, clientArea.width, end - start));
} else {
gr.fillRect (clientArea.x + start + 2, clientArea.y + 2, end - start - 4, clientArea.height - 4);
BORDER.paint (gr, new Rectangle (clientArea.x + start, clientArea.y, end - start, clientArea.height));
}
} else {
if (vertical)
gr.fillRect (clientArea.x, clientArea.y + start, clientArea.width, end - start);
else
gr.fillRect (clientArea.x + start, clientArea.y, end - start, clientArea.height);
}
}
}
}
private void translateView (int dx, int dy) {
Widget v = getView ();
if (v == null)
return;
Point location = view.getLocation ();
location.translate (- dx, - dy);
checkViewLocationInBounds (location, view, dx, dy);
v.setPreferredLocation (location);
}
private void checkViewLocationInBounds (Point location, Widget v, int dx, int dy) {
Rectangle view = v.getBounds ();
Rectangle viewport = ScrollWidget.this.viewport.getBounds ();
if (dx < 0 && location.x > view.x)
location.x = view.x;
if (dy < 0 && location.y > view.y)
location.y = view.y;
if (dx > 0 && location.x + view.width < viewport.x + viewport.width)
location.x = viewport.x + viewport.width - view.width;
if (dy > 0 && location.y + view.height < viewport.y + viewport.height)
location.y = viewport.y + viewport.height - view.height;
}
private class UnitScrollProvider implements SelectProvider {
private int dx;
private int dy;
private UnitScrollProvider (int dx, int dy) {
this.dx = dx;
this.dy = dy;
}
public boolean isAimingAllowed (Widget widget, Point localLocation, boolean invertSelection) {
return true;
}
public boolean isSelectionAllowed (Widget widget, Point localLocation, boolean invertSelection) {
return true;
}
public void select (Widget widget, Point localLocation, boolean invertSelection) {
translateView (dx, dy);
}
}
private class BlockScrollAction extends WidgetAction.Adapter {
private SliderWidget slider;
private int dx;
private int dy;
private BlockScrollAction (SliderWidget slider, int dx, int dy) {
this.slider = slider;
this.dx = dx;
this.dy = dy;
}
public State mousePressed (Widget widget, WidgetMouseEvent event) {
if (event.getButton () == MouseEvent.BUTTON1) {
assert slider == widget;
SliderWidget.Part part = slider.getPartHitAt (event.getPoint ());
switch (part) {
case AFTER:
translateView (- dx, - dy);
return State.CHAIN_ONLY;
case BEFORE:
translateView (dx, dy);
return State.CHAIN_ONLY;
}
}
return State.REJECTED;
}
}
private class SliderAction extends WidgetAction.Adapter {
private SliderWidget slider;
private Widget movingWidget = null;
private Point dragSceneLocation = null;
private Point originalSceneLocation = null;
private SliderAction (SliderWidget slider) {
this.slider = slider;
}
protected boolean isLocked () {
return movingWidget != null;
}
public State mousePressed (Widget widget, WidgetMouseEvent event) {
if (isLocked ())
return State.createLocked (widget, this);
if (event.getButton () == MouseEvent.BUTTON1 && event.getClickCount () == 1) {
Widget view = getView ();
if (view != null && slider.getPartHitAt (event.getPoint ()) == SliderWidget.Part.SLIDER) {
movingWidget = widget;
originalSceneLocation = view.getPreferredLocation ();
if (originalSceneLocation == null)
originalSceneLocation = new Point ();
dragSceneLocation = widget.convertLocalToScene (event.getPoint ());
return State.createLocked (widget, this);
}
}
return State.REJECTED;
}
public State mouseReleased (Widget widget, WidgetMouseEvent event) {
boolean state = move (widget, event.getPoint ());
if (state)
movingWidget = null;
return state ? State.CONSUMED : State.REJECTED;
}
public State mouseDragged (Widget widget, WidgetMouseEvent event) {
return move (widget, event.getPoint ()) ? State.createLocked (widget, this) : State.REJECTED;
}
private boolean move (Widget widget, Point newLocation) {
Widget view = getView ();
if (movingWidget != widget || view == null)
return false;
newLocation = widget.convertLocalToScene (newLocation);
int dx = 0, dy = 0;
if (slider.isVertical ())
dy = - (int) ((newLocation.y - dragSceneLocation.y) / slider.getPixelIncrement ());
else
dx = - (int) ((newLocation.x - dragSceneLocation.x) / slider.getPixelIncrement ());
newLocation.x = originalSceneLocation.x + dx;
newLocation.y = originalSceneLocation.y + dy;
checkViewLocationInBounds (newLocation, view, - dx, - dy);
view.setPreferredLocation (newLocation);
return true;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy