/*******************************************************************************
* CogTool Copyright Notice and Distribution Terms
* CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* CogTool is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* CogTool is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with CogTool; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* CogTool makes use of several third-party components, with the
* following notices:
*
* Eclipse SWT version 3.448
* Eclipse GEF Draw2D version 3.2.1
*
* Unless otherwise indicated, all Content made available by the Eclipse
* Foundation is provided to you under the terms and conditions of the Eclipse
* Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this
* Content and is also available at http://www.eclipse.org/legal/epl-v10.html.
*
* CLISP version 2.38
*
* Copyright (c) Sam Steingold, Bruno Haible 2001-2006
* This software is distributed under the terms of the FSF Gnu Public License.
* See COPYRIGHT file in clisp installation folder for more information.
*
* ACT-R 6.0
*
* Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere &
* John R Anderson.
* This software is distributed under the terms of the FSF Lesser
* Gnu Public License (see LGPL.txt).
*
* Apache Jakarta Commons-Lang 2.1
*
* This product contains software developed by the Apache Software Foundation
* (http://www.apache.org/)
*
* jopt-simple version 1.0
*
* Copyright (c) 2004-2013 Paul R. Holser, Jr.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* Mozilla XULRunner 1.9.0.5
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/.
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* The J2SE(TM) Java Runtime Environment version 5.0
*
* Copyright 2009 Sun Microsystems, Inc., 4150
* Network Circle, Santa Clara, California 95054, U.S.A. All
* rights reserved. U.S.
* See the LICENSE file in the jre folder for more information.
******************************************************************************/
package edu.cmu.cs.hcii.cogtool.view;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.draw2d.LightweightSystem;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ScrollBar;
import edu.cmu.cs.hcii.cogtool.util.ClipboardUtil;
import edu.cmu.cs.hcii.cogtool.util.L10N;
import edu.cmu.cs.hcii.cogtool.util.ManagedCombo;
import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities;
import edu.cmu.cs.hcii.cogtool.util.RcvrUIException;
import edu.cmu.cs.hcii.cogtool.util.StatusDisplayable;
import edu.cmu.cs.hcii.cogtool.util.Zoomable;
// TODO: while this was originally structured to not require an Interaction, that
// may have to change. The method widgetDefaultSelected needs to interact
// with the user in some way. This might be directly via an Interaction;
// or possibly when we look further into how we really want to deal with
// exceptions it might be by throwing to some surrounding context.
/**
* Standard drawing editor is the basic method for drawing Draw2d objects.
* No interaction is needed, and it holds on to the basic components needed.
*
* Uses a properties area to hold information about the view.
*
* Handles scrolling, resizing.
*/
public abstract class StandardDrawingEditor implements StatusDisplayable
{
protected static final int MOUSE_DOWN = 0;
protected static final int MOUSE_UP = 1;
protected static final int MOUSE_DOUBLECLICK = 2;
protected static final int MOUSE_MOVE = 3;
protected static final int MOUSE_DRAG = 4;
protected static final int MOUSE_EXIT = 5;
protected static final int MOUSE_ENTER = 6;
protected static final double MINIMUM_ZOOM = 0.1;
protected static final double MAXIMUM_ZOOM = 5.0;
// The format in which a user is allowed to specify the zoom factor.
// The value should be a number, representing a percentage. It may
// optionally be floating point, but must begin with a digit. It may, but
// need not, be followed by a percent sign. Whitespace is allowed at the
// beginning, end and between the number and the percent sign.
// TODO: when we really start localizing ensure that the value from the bundle
// really can be retrieved when this class is initialized; otherwise
// do the Pattern.compile when widgetDefaultSelected is first called.
// Note that the reason this needs to be localizable is that number
// formats are locale sensitive.
protected static final Pattern ZOOM_VALUE_PATTERN =
Pattern.compile(L10N.get("SDE.zoomValuePattern",
"\\s*(\\d+\\.?\\d*)\\s*%?\\s*"));
protected class CapturedEventForwarder implements MouseListener,
MouseMoveListener
{
protected boolean mouseIsDown = true;
public void mouseDoubleClick(MouseEvent e)
{
forwardCapturedEvent(MOUSE_DOUBLECLICK,
e.button, e.x, e.y, e.stateMask);
}
public void mouseDown(MouseEvent e)
{
mouseIsDown = true;
forwardCapturedEvent(MOUSE_DOWN,
e.button, e.x, e.y, e.stateMask);
}
public void mouseUp(MouseEvent e)
{
mouseIsDown = false;
forwardCapturedEvent(MOUSE_UP,
e.button, e.x, e.y, e.stateMask);
}
public void mouseMove(MouseEvent e)
{
// forwardCapturedEvent(this.mouseIsDown ? MOUSE_DRAG : MOUSE_MOVE,
// e.button, e.x, e.y, e.stateMask);
forwardCapturedEvent(MOUSE_DRAG,
e.button, e.x, e.y, e.stateMask);
}
}
/**
* The body of the entire drawing editor. Lay this out to hold the view and
* properties.
*/
protected Composite bodyComposite;
/**
* Scroll Composite used to hold the canvas and draw 2d View.
* This object handles scrolling for the internal canvas.
*/
protected ScrolledComposite scrollComposite;
/**
* The editor Composite is the top portion of the view... it holds onto the
* scrolled composite.
*/
protected Composite editorComposite;
/**
* The area which holds the zoom information.
*/
protected Composite zoomComposite;
/**
* Class allowing controlled paste and drag-and-drop to the zoom combo
*/
protected static class ZoomCombo extends ManagedCombo
{
public ZoomCombo(Composite parent, int style)
{
super(parent, style);
}
@Override
protected void replaceSelection(String withString)
{
if (withString != null) {
// All characters must be allowed
for (int i = 0; i < withString.length(); i++) {
if (! acceptableZoomCharacter(withString.charAt(i))) {
return;
}
}
super.replaceSelection(withString);
}
}
@Override
public void paste()
{
replaceSelection(ClipboardUtil.fetchTextData());
}
}
/**
* Zoom Combo, for the standard drawing view
*/
protected Combo zoomCombo;
/**
* The composite holding onto the properties. Fill this object with property
* controls.
*/
protected Composite propertiesComposite;
/**
* Palette composite for editing frames and designs
*/
protected Palette paletteComposite;
/**
* The canvas object which holds onto the Lightweight system
*/
protected Canvas canvas;
protected boolean capturingEvents = false;
protected CapturedEventForwarder eventForwarder =
new CapturedEventForwarder();
/**
* A boolean to prevent multiple listeners to size change events from
* calling the same code.
*/
boolean resizingEditor = false;
/**
* The lightweight system is the interface layer between SWT and draw2d.
*/
protected LightweightSystem lws;
/**
* Bottom status bar for displaying status messages
*/
protected StatusBar statusBar;
protected int propertiesSize;
protected int propertiesPaneLocation;
protected Zoomable zoomable;
protected FormData scrollFormData;
protected FormData zoomFormData;
protected FormData propFormData;
/**
* Create a standard Drawing Editor with the default properties pane on the
* bottom
*
* @param window
* @param propertiesPaneSize
* @param paletteSize
*/
public StandardDrawingEditor(Composite window,
int propertiesPaneSize,
int paletteSize,
Zoomable zoom)
{
this(window, propertiesPaneSize, SWT.BOTTOM, paletteSize, zoom);
}
/**
* This constructor allows the caller to specify WHERE they want the
* properties pane to appear.
* Uses a default offset of 0, meaning nothing is left between the scroll
* area and the properties.
*
* @param window
* @param propertiesPaneSize
* @param paletteSize
* @param propertiesPaneLoc
*/
public StandardDrawingEditor(Composite window,
int propertiesPaneSize,
int propertiesPaneLoc,
int paletteSize,
Zoomable zoom)
{
this(window, propertiesPaneSize, propertiesPaneLoc, paletteSize, zoom, 0);
}
/**
* The propetiesPaneLocation can be one of
* SWT.TOP, SWT.BOTTOM, SWT.RIGHT, SWT.LEFT
*
* offsetScrollAreaBottom should be a positive number indicating the
* area to leave at the bottom of the scroll area for any additional
* "stuff"
*
* @param window
* @param propertiesPaneSize
* @param paletteSize
* @param propertiesPaneLoc
* @param offsetScrollAreaBottom
*/
public StandardDrawingEditor(Composite window,
int propertiesPaneSize,
int propertiesPaneLoc,
int paletteSize,
Zoomable zoom,
int offsetScrollAreaBottom)
{
// Check Input
switch (propertiesPaneLoc) {
case SWT.TOP:
case SWT.BOTTOM:
case SWT.LEFT:
case SWT.RIGHT:
break; // Acceptable Input
default:
throw new IllegalArgumentException("PropertiesPaneLocation must " +
"be one of SWT.TOP, SWT.BOTTOM, SWT.LEFT, SWT.RIGHT");
}
propertiesSize = propertiesPaneSize;
propertiesPaneLocation = propertiesPaneLoc;
bodyComposite = new Composite(window, SWT.NONE);
bodyComposite.setLayout(new FormLayout());
scrollComposite =
new ScrolledComposite(bodyComposite,
SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
statusBar = new StatusBar(bodyComposite);
// Dealing with showing and hiding scrollbars is tricky, so don't
scrollComposite.setAlwaysShowScrollBars(true);
editorComposite = new Composite(scrollComposite, SWT.NONE);
editorComposite.setLayout(new FillLayout());
scrollComposite.setContent(editorComposite);
propertiesComposite = new Composite(bodyComposite, SWT.NONE);
zoomComposite = new Composite(bodyComposite, SWT.NONE);
// TODO: Should consider not creating a palette if the paletteSize is 0
paletteComposite = new Palette(bodyComposite, SWT.NONE);
zoomable = zoom;
Label label = new Label(zoomComposite, SWT.RIGHT);
label.setText(L10N.get("SDE.ZOOM", "Zoom: "));
zoomCombo = new ZoomCombo(zoomComposite, SWT.DROP_DOWN)
{
@Override
protected boolean filterKey(KeyEvent evt)
{
/*
* We can allow certain types of characters here:
* Control characters (arrow keys, etc): Character.CONTROL
* Numerics: Character.DECIMAL_DIGIT_NUMBER
* (apparently, this subsumes SWT.KEYPAD_0-9)
* Decimal Point: characters '.' (keycode: 46) and
* SWT.KEYPAD_DECIMAL (keycode: 16777262)
*
* Disallow anything else
*/
if (acceptableZoomCharacter(evt.character) ||
(Character.getType(evt.character) == Character.CONTROL) ||
(evt.keyCode == SWT.KEYPAD_DECIMAL))
{
return super.filterKey(evt);
}
return false;
}
@Override
public boolean confirm(int focusRule)
{
if (focusRule == SELECTION) {
int index = getSelectionIndex();
switch (index) {
case 0:
zoomable.performZoom(0.5);
break;
case 1:
zoomable.performZoom(1.0);
break;
case 2:
zoomable.performZoom(1.5);
break;
case 3:
zoomable.performZoom(2.0);
break;
case 4:
zoomable.performZoom(zoomable.computeZoomToFit());
break;
}
}
else {
setZoomFromUserInput(getText());
}
return true;
}
};
zoomCombo.setItems(new String[] { L10N.get("SDE.50%", "50 %"),
L10N.get("SDE.100%", "100 %"),
L10N.get("SDE.150%", "150 %"),
L10N.get("SDE.200%", "200 %"),
L10N.get("SDE.ZoomToFit",
"Zoom to Fit")
});
zoomCombo.select(1);
zoomComposite.setLayout(new FormLayout());
FormData formData = new FormData();
//formData.top = new FormAttachment(100, -30);
formData.bottom = new FormAttachment(100, 0);
formData.left = new FormAttachment(0, 0);
formData.right = new FormAttachment(100, 0);
statusBar.setLayoutData(formData);
formData = new FormData();
formData.top = new FormAttachment(0, 0);
formData.right = new FormAttachment(100, -5);
zoomCombo.setLayoutData(formData);
formData = new FormData();
// use a -5 offset on the bottom so the text lines up nicely...
// TODO: works on mac's check on windows.
formData.bottom = new FormAttachment(zoomCombo, -5, SWT.BOTTOM);
formData.right = new FormAttachment(zoomCombo, -5, SWT.LEFT);
label.setLayoutData(formData);
canvas = new Canvas(editorComposite, SWT.NONE);
// Clipping is handled correctly, as long as drawing is restricted to
// when the LWS says to paint.
lws = new LightweightSystem(canvas)
{
@Override
public void paint(GC gc)
{
Rectangle bounds = scrollComposite.getClientArea();
Point origin = scrollComposite.getOrigin();
bounds.x += origin.x;
bounds.y += origin.y;
gc.setClipping(bounds);
super.paint(gc);
}
};
scrollComposite.addControlListener(new ControlAdapter()
{
/**
* If the control is resized, then update the size of the interior
* scrolled Composite.
*/
@Override
public void controlResized(ControlEvent evt)
{
// record the origin of the scrollable region. It seems to get
// reset
Point p = scrollComposite.getOrigin();
// Set the min visible area for the view.
// Allow the area to shrink
// Since SetMinVisibleArea uses scrollComposite.GetClientArea
// to always enforce a minimum, pass in -1,-1
setMinVisibleArea(-1, -1, false);
// set the origin back to recorded value.
scrollComposite.setOrigin(p);
// set scroll increments to something larger than one
setScrollIncrements();
}
});
// Set up the default values.
// Using these settings, will have the areas SUPERIMPOSED.
// set the "split" bellow in the switch
scrollFormData = new FormData();
scrollFormData.top = new FormAttachment(0, 5);
scrollFormData.left =
new FormAttachment(paletteComposite, 0, SWT.RIGHT);
scrollFormData.right = new FormAttachment(100, 0);
scrollFormData.bottom = new FormAttachment(zoomComposite, 0);
zoomFormData = new FormData();
// zoomFormData.top = new FormAttachment(100, -15);
zoomFormData.left =
new FormAttachment(scrollComposite, 0, SWT.LEFT);
zoomFormData.right =
new FormAttachment(scrollComposite, 0, SWT.RIGHT);
zoomFormData.bottom = new FormAttachment(statusBar, -5);
propFormData = new FormData();
// propFormData.top = new FormAttachment(0, 0);
propFormData.left = new FormAttachment(0, 0);
propFormData.right = new FormAttachment(100, 0);
// propFormData.bottom = new FormAttachment(statusBar, -5);
// Depending on which location the properties pane will be, set the
// form properties accordingly.
switch (propertiesPaneLocation) {
case SWT.BOTTOM:
zoomFormData.bottom =
new FormAttachment(propertiesComposite, -5);
propFormData.left = new FormAttachment(0, 0);
propFormData.right = new FormAttachment(100, 0);
propFormData.bottom =
new FormAttachment(statusBar, -5);
break;
case SWT.TOP:
scrollFormData.top =
new FormAttachment(propertiesComposite, 5);
propFormData.bottom =
new FormAttachment(0, propertiesSize);
break;
case SWT.LEFT:
scrollFormData.left =
new FormAttachment(propertiesComposite, 5);
propFormData.right =
new FormAttachment(0, propertiesSize);
propFormData.top = new FormAttachment(0, 5);
propFormData.bottom =
new FormAttachment(statusBar, -5);
break;
case SWT.RIGHT:
scrollFormData.right =
new FormAttachment(propertiesComposite, -5);
propFormData.left =
new FormAttachment(100, -1 * propertiesSize);
propFormData.top = new FormAttachment(0, 5);
propFormData.bottom =
new FormAttachment(statusBar, -5);
break;
}
addAndLayOutFields();
// Set the layout data to the important objects
scrollComposite.setLayoutData(scrollFormData);
propertiesComposite.setLayoutData(propFormData);
zoomComposite.setLayoutData(zoomFormData);
FormData paletteData = new FormData();
paletteData.left = new FormAttachment(0, 2);
paletteData.right = new FormAttachment(0, paletteSize);
paletteData.top = new FormAttachment(0, 5);
paletteData.bottom = new FormAttachment(zoomComposite, -5);
paletteComposite.setLayoutData(paletteData);
}
protected void addAndLayOutFields()
{
// Opportunity for subclasses to add UI elements and adjust layout
}
protected void setZoomFromUserInput(String userInput)
{
// TODO: this still really isn't the right UI. Instead of validating
// the value entered and then fussing at the user if it's not
// right, we should really be looking after every key press
// or paste operation and only allowing legal values to be
// entered at all.
// TODO: deal with localization here. Number formats are also locale
// specific. When we deal with that in general, it needs to be
// dealt with here. The major issue is the symbol used as a
// radix point. As of right now ZOOM_VALUE_PATTERN can be
// localized, but the call to parseDouble below is not locale-
// sensitive, and needs eventually to be replaced by something
// that is.
Matcher zoomMatcher = ZOOM_VALUE_PATTERN.matcher(userInput);
if (zoomMatcher.matches()) {
try {
double newZoom =
Double.parseDouble(zoomMatcher.group(1)) / 100.0;
if (newZoom < MINIMUM_ZOOM) {
// TODO: interact with the user, indicating that the
// value is too small (see comment at top of this
// file regarding likely need for an Interaction).
newZoom = MINIMUM_ZOOM;
}
else if (newZoom > MAXIMUM_ZOOM) {
// TODO: interact with the user, indicating that the
// value is too large (see comment at top of this
// file regarding likely need for an Interaction).
newZoom = MAXIMUM_ZOOM;
}
zoomable.performZoom(newZoom);
}
catch (NumberFormatException e) {
throw new RcvrUIException(
"Number format mismatch in zoom value.");
}
}
else {
// TODO: interact with the user, indicating that the value is
// not of the expected format; throw exception when
// exception architecture is "completed"
}
}
public abstract Rectangle getContentSize();
protected void forwardCapturedEvent(int eventType,
int button, int x, int y, int state)
{
// Subclass to override
}
public void forwardCanvasEvents()
{
canvas.addMouseListener(eventForwarder);
canvas.addMouseMoveListener(eventForwarder);
}
public void stopForwardingCanvasEvents()
{
canvas.removeMouseListener(eventForwarder);
canvas.removeMouseMoveListener(eventForwarder);
}
/**
* Allow an outside entity to set the layout data for the "whole view"
*/
public void setLayoutData(Object data)
{
bodyComposite.setLayoutData(data);
}
/**
* Allow outside people to add control listeners to the scroll area.
* @param listener
*/
public void addScrollControlListener(ControlListener listener)
{
scrollComposite.addControlListener(listener);
}
/**
* Allow outside people to add control listeners to the editor area.
* The editor area is inside the scroll area.
* @param listener
*/
public void addEditorControlListener(ControlListener listener)
{
editorComposite.addControlListener(listener);
}
/**
* Allow external entities, to get access to the Lightweight system
* @return
*/
public LightweightSystem getLWS()
{
return lws;
}
/**
* Allow external entities to have access to the canvas
* @return
*/
public Canvas getSWTEditorSubstrate()
{
return canvas;
}
public Composite getSWTBodyComposite()
{
return bodyComposite;
}
public Composite getSWTPropertiesComposite()
{
return propertiesComposite;
}
public Composite getSWTZoomComposite()
{
return zoomComposite;
}
public Composite getSWTPaletteComposite()
{
return paletteComposite;
}
public ScrolledComposite getSWTScrollComposite()
{
return scrollComposite;
}
// Status display methods
public void setStatusMessage(String message)
{
statusBar.setStatusMessage(message);
}
public void setStatusMessage(String message, int duration)
{
statusBar.setStatusMessage(message, duration);
}
/**
* Set the minimum visible area that the should be visible.
* This is based on the contents of the area.
*
* If growOnly is set true, then the visible area will not be "shrunk"
*
* @param width Desired Width
* @param height desired Height
* @param growOnly true iff the window can not be shrunk.
*/
public void setMinVisibleArea(int width,
int height,
boolean growOnly)
{
if (resizingEditor) {
return;
}
resizingEditor = true;
// use the content size
Rectangle bounds = getContentSize();
if (width < bounds.width) {
width = bounds.width;
}
if (height < bounds.height) {
height = bounds.height;
}
// Get the current visible area.
bounds = scrollComposite.getClientArea();
// Check with the desired width & height
// If the visible area is larger then that specified, set it to the
// desired size
if (width < bounds.width) {
width = bounds.width;
}
if (height < bounds.height) {
height = bounds.height;
}
// If we can shrink, check the current width & height with that of the
// current editor composite.
// This primarily here for dragging. When dragging, don't want to move
// the window around just because we are no longer at the edge.
if (growOnly) {
bounds = editorComposite.getClientArea();
if (width < bounds.width) {
width = bounds.width;
}
if (height < bounds.height) {
height = bounds.height;
}
}
// Set the new size of the editor composite.
editorComposite.setSize(width, height);
resizingEditor = false;
}
/**
* Get the visible area on the window. This is the area exposed by the
* scroll widgets.
* @return
*/
public Rectangle getVisibleBounds()
{
Point origin = scrollComposite.getOrigin();
Point extent = scrollComposite.getSize();
int verticalBarWidth =
scrollComposite.getVerticalBar().getSize().x;
int horizontalBarHeight =
scrollComposite.getHorizontalBar().getSize().y;
// Remove the height and width of the scroll bar from the visible area.
return new Rectangle(origin.x,
origin.y,
extent.x - verticalBarWidth,
extent.y - horizontalBarHeight);
}
/**
* Set the origin you want to the scroll panel to be at.
* @param x
* @param y
*/
public void setScrollOrigin(int x, int y)
{
scrollComposite.setOrigin(x, y);
}
/**
* This function takes a point and ensures that the scroll bars in the
* scrollable area are set to keep the object visible.
*/
public void ensurePointIsVisible(int deltaX, int deltaY,
int eventX, int eventY)
{
Point visibleOrigin = scrollComposite.getOrigin();
// Resize the minimum
// contents with the new size, without shrinking the area.
setMinVisibleArea(eventX + deltaX, eventY + deltaY, true);
// Scroll to make the point visible
scrollComposite.setOrigin(visibleOrigin.x + deltaX,
visibleOrigin.y + deltaY);
}
/**
* Check to see if a point is near to an edge. Get the dragDelta
* which is used to indicate how much to "grow" the window based on the
* mouse position.
*
* Returns a boolean, which dictates if the cursor was inside or outside the
* window frame. (this is done to allow mouse states to know exit and
* entered times)
*
* Also ensures that the specified X & Y are visible.
*
* @param eventX
* @param eventY
* @param dragDelta
* @return
*/
public boolean movePointNearEdge(int eventX, int eventY, Point dragDelta)
{
Rectangle clientArea = scrollComposite.getClientArea();
Point origin = scrollComposite.getOrigin();
// TODO: Switch this to base at 0. Then use an acceleration curve.
// Use the getOrigin to get the top left corner of the view.. this
// is the location that you are currently scrolled too.
// Compute the difference between the origin and the mouse cursor.
// If the difference is near the edge of the visible rectangle, then
// use set the delta to non zero. If outside, return false. if inside,
// return true, and set dragDelta to 0,0,
// Use getClientArea to find the width/height of the visible region
int relativeX = eventX - origin.x;
int relativeY = eventY - origin.y;
boolean xIsOutside = false;
boolean yIsOutside = false;
if (relativeX < 0) {
dragDelta.x = 0;
xIsOutside = true;
}
else if (relativeX < 5) {
dragDelta.x = -10;
}
else if (relativeX < 10) {
dragDelta.x = -5;
}
else if (relativeX > clientArea.width) {
dragDelta.x = 0;
xIsOutside = true;
}
else if (relativeX > clientArea.width - 5) {
dragDelta.x = 10;
}
else if (relativeX > clientArea.width - 10) {
dragDelta.x = 5;
}
else {
dragDelta.x = 0;
}
if (relativeY < 0) {
dragDelta.y = 0;
yIsOutside = true;
}
else if (relativeY < 5) {
dragDelta.y = -10;
}
else if (relativeY < 10) {
dragDelta.y = -5;
}
else if (relativeY > clientArea.height) {
dragDelta.y = 0;
yIsOutside = true;
}
else if (relativeY > clientArea.height - 5) {
dragDelta.y = 10;
}
else if (relativeY > clientArea.height - 10) {
dragDelta.y = 5;
}
else {
dragDelta.y = 0;
}
ensurePointIsVisible(dragDelta.x, dragDelta.y, eventX, eventY);
return (xIsOutside || yIsOutside);
} // movePointNearEdge
/**
* Grab all mouse events or release a previous capture.
*
* @param capture true to grab, false to release
* @author mlh
*/
public void captureMouseEvents(boolean capture)
{
if (capturingEvents != capture) {
capturingEvents = capture;
if (! capture) {
stopForwardingCanvasEvents();
}
}
}
/**
*
*/
public void dispose()
{
// Nothing to dispose
}
/**
* Set the zoom for the contents.
* TODO: refactor? doesn't seem like its weird, since nothing uses the
* standard drawing editor.. only its subclass
*
* Set the custom label to the zoom setting specified.
*
* Assumes the zoom value is in decimal. IE 1 = 100%
* @param zoom
*/
protected void setZoomSetting(double zoom)
{
zoomCombo.setText(NumberFormat.getInstance(Locale.US).format(zoom * 100)
+ " " + L10N.get("SDE.%", "%"));
}
protected void setScrollIncrements()
{
// Update the scroll bar when the internal content size changes.
org.eclipse.swt.graphics.Point scrollSize =
scrollComposite.getSize();
org.eclipse.swt.graphics.Point editorSize =
editorComposite.getSize();
if ((scrollSize.x != 0) && (scrollSize.y != 0)) {
ScrollBar horizontal = scrollComposite.getHorizontalBar();
ScrollBar vertical = scrollComposite.getVerticalBar();
// Update the scroll Step after the size info has been set
horizontal.setIncrement((editorSize.x * 10) / scrollSize.x);
vertical.setIncrement((editorSize.y * 10) / scrollSize.y);
horizontal.setPageIncrement(PrecisionUtilities.ceiling(
scrollSize.x * 0.75));
vertical.setPageIncrement(PrecisionUtilities.ceiling(
scrollSize.y * 0.75));
}
}
public FormData getPropFormData()
{
return propFormData;
}
public FormData getScrollFormData()
{
return scrollFormData;
}
public FormData getZoomFormData()
{
return zoomFormData;
}
/**
* Acceptable characters in a zoom SWT Combo widget:
* Numerics: Character.DECIMAL_DIGIT_NUMBER
* (apparently, this subsumes SWT.KEYPAD_0-9)
* Decimal Point: characters '.' (keycode: 46)
*
* @param key the character to be tested
* @return if the given character is acceptable as input to the Combo
*/
public static boolean acceptableZoomCharacter(char key)
{
return (Character.getType(key) == Character.DECIMAL_DIGIT_NUMBER) ||
(key == '.');
}
}