/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed 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 au.gov.ga.earthsci.application.parts.globe; import java.text.DecimalFormat; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.ui.services.IServiceConstants; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Scale; import org.eclipse.swt.widgets.Shell; import au.gov.ga.earthsci.worldwind.common.exaggeration.VerticalExaggerationListener; import au.gov.ga.earthsci.worldwind.common.exaggeration.VerticalExaggerationService; /** * Tool control used to change globe exaggeration. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class GlobeExaggerationToolControl implements VerticalExaggerationListener { private final static double SCALE_MIN = 0.1; //must be a power of 10 private final static double SCALE_MAX = 100; //must be a power of 10 private final static double SCALE_LOG_MIN = Math.log10(SCALE_MIN); private final static double SCALE_LOG_MAX = Math.log10(SCALE_MAX); private final static int INCREMENTS_PER_POWER = 1000; private final static int SCALE_WIDTH = 120; //width of the scale control private final static int SCALE_HEIGHT = 21; //height of the scale control (to fit in the toolbar) private final static int TICK_HEIGHT = 3; //height of the ticks private final static int SCALE_MARGIN = 14; //margin on the left/right of the scale private final static int GRAB_WIDTH = 8; //width of the scale position indicator private final static int KEY_DELAY = 1500; //ms private StyledText scaleText; private Scale scale; private Color tickForeground; private String keyString = ""; //$NON-NLS-1$ private long lastKeyTime; @PostConstruct public void createControls(Composite parent, IEclipseContext context) { VerticalExaggerationService.INSTANCE.addListener(this); parent.setBackgroundMode(SWT.INHERIT_FORCE); RowLayout layout = new RowLayout(SWT.HORIZONTAL); layout.wrap = false; layout.spacing = layout.marginBottom = layout.marginTop = layout.marginLeft = layout.marginRight = 0; parent.setLayout(layout); Composite labelParent = new Composite(parent, SWT.NONE); GridLayout gridLayout = new GridLayout(); gridLayout.marginWidth = 0; gridLayout.marginHeight = 4; labelParent.setLayout(gridLayout); labelParent.setSize(labelParent.computeSize(SWT.DEFAULT, SCALE_HEIGHT)); scaleText = new StyledText(labelParent, SWT.NONE); scaleText.setText("1.000x"); ScaleEditListener clickListener = new ScaleEditListener(); scaleText.addFocusListener(clickListener); scaleText.addKeyListener(clickListener); scaleText.addVerifyKeyListener(clickListener); scaleText.setSize(50, scaleText.computeSize(SCALE_WIDTH, SWT.DEFAULT).y); Composite child = new Composite(parent, SWT.NONE); child.setSize(child.computeSize(SCALE_WIDTH, SWT.DEFAULT)); scale = new Scale(child, SWT.HORIZONTAL); Point size = scale.computeSize(SCALE_WIDTH, SWT.DEFAULT); scale.setSize(size); scale.setMinimum(exaggerationToScale(0)); scale.setMaximum(exaggerationToScale(SCALE_MAX)); scale.setLocation(0, (SCALE_HEIGHT - size.y) / 2); scale.setToolTipText(Messages.GlobeExaggerationToolControl_ToolTip0); scale.setIncrement(INCREMENTS_PER_POWER / 100); scale.setSelection(exaggerationToScale(VerticalExaggerationService.INSTANCE.get())); updateSelection(false); tickForeground = Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW); scale.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { GlobeExaggerationToolControl.this.paintControl(e); } }); scale.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateSelection(true); } }); scale.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { handleKey(e); } }); } @PreDestroy public void preDestroy() { VerticalExaggerationService.INSTANCE.removeListener(this); } private double scaleToExaggeration(int value) { if (value <= 0) { return 0; } double exponent = (value / (double) INCREMENTS_PER_POWER) + SCALE_LOG_MIN; return Math.pow(10, exponent); } private int exaggerationToScale(double value) { if (value <= 0) { return 0; } double log10 = Math.max(SCALE_LOG_MIN, Math.min(SCALE_LOG_MAX, Math.log10(value))); return (int) Math.round((log10 - SCALE_LOG_MIN) * INCREMENTS_PER_POWER); } private void paintControl(PaintEvent e) { GC gc = e.gc; Color foreground = gc.getForeground(); Color background = gc.getBackground(); try { gc.setForeground(tickForeground); Point size = scale.getSize(); float scalePercent = (scale.getSelection() - scale.getMinimum()) / (float) (scale.getMaximum() - scale.getMinimum()); int grabCenter = Math.round(scalePercent * (size.x - SCALE_MARGIN * 2)) + SCALE_MARGIN; int y = (size.y - SCALE_HEIGHT) / 2; double value = SCALE_MIN; while (value <= SCALE_MAX) { double log10 = Math.log10(value); double percent = (log10 - SCALE_LOG_MIN) / (SCALE_LOG_MAX - SCALE_LOG_MIN); double increment = Math.pow(10, Math.floor(log10 + Double.MIN_VALUE)); long count = Math.round(value / increment); int x = (int) Math.round(percent * (size.x + 1 - SCALE_MARGIN * 2) + SCALE_MARGIN - 1); if (x < grabCenter - GRAB_WIDTH / 2 || x > grabCenter + GRAB_WIDTH / 2) { gc.drawLine(x, y, x, y + TICK_HEIGHT); gc.drawLine(x, y + SCALE_HEIGHT, x, y + SCALE_HEIGHT - TICK_HEIGHT); } value = (count + 1) * increment; } } finally { gc.setForeground(foreground); gc.setBackground(background); } } private void updateSelection(boolean setService) { double exaggeration = scaleToExaggeration(scale.getSelection()); if (setService) { VerticalExaggerationService.INSTANCE.set(exaggeration); } int decimalPlaces = 2 - (exaggeration <= 0 ? 0 : (int) Math.log10(exaggeration)); DecimalFormat format = new DecimalFormat(); format.setMinimumFractionDigits(decimalPlaces); format.setMaximumFractionDigits(decimalPlaces); scaleText.setText(format.format(exaggeration) + "x"); //$NON-NLS-1$ } private void handleKey(KeyEvent e) { long thisKeyTime = System.currentTimeMillis(); if (thisKeyTime - lastKeyTime > KEY_DELAY) { keyString = ""; //$NON-NLS-1$ } if (e.character == SWT.CR || e.character == SWT.KEYPAD_CR || e.character == SWT.LF) { lastKeyTime = 0; } else if (e.character == SWT.DEL) { keyString = ""; //$NON-NLS-1$ } else if (e.character == SWT.BS) { if (keyString.length() > 0) { keyString = keyString.substring(0, keyString.length() - 1); } lastKeyTime = thisKeyTime; } else { String newString = keyString + e.character; Double value = null; try { value = Double.parseDouble(newString); } catch (NumberFormatException nfe) { } if (value != null) { scale.setSelection(exaggerationToScale(value)); updateSelection(true); keyString = newString; lastKeyTime = thisKeyTime; } } } @Override public void verticalExaggerationChanged(double oldValue, final double newValue) { if (scale != null && !scale.isDisposed()) { scale.getDisplay().asyncExec(new Runnable() { @Override public void run() { scale.setSelection(exaggerationToScale(newValue)); updateSelection(false); } }); } } private class ScaleEditListener extends MouseAdapter implements FocusListener, KeyListener, VerifyKeyListener { @Inject @Named(IServiceConstants.ACTIVE_SHELL) private Shell shell; IInputValidator validator; /** * */ public ScaleEditListener() { validator = new IInputValidator() { @Override public String isValid(String inputString) { Pattern matingPattern = Pattern.compile("\\d+(\\.\\d+)?x?"); // \d+\.\d+ //$NON-NLS-1$ Matcher matcher = matingPattern.matcher(inputString); if (!matcher.matches()) { return Messages.GlobeExaggerationSet_PatternNotMatched; } try { double value = Double.parseDouble(inputString.replace("x", "")); //TODO: Specifies a minimum at the top of the file, yet to see it enforced. //if (value < SCALE_MIN) //{ // return "Must be at least 0.1"; //} if (value > 100) { return Messages.GlobeExaggerationSet_RangeExceeded; } } catch (NumberFormatException ex) { return ex.getLocalizedMessage(); } return null; } }; } String originalValue; @Override public void focusGained(FocusEvent arg0) { if (validator.isValid(scaleText.getText()) == null) { originalValue = scaleText.getText(); } } @Override public void focusLost(FocusEvent arg0) { scaleText.setMarginColor(null); if (validator.isValid(scaleText.getText()) == null) { double value = Double.parseDouble(scaleText.getText().replace("x", "")); scale.setSelection(exaggerationToScale(value)); updateSelection(true); } else { scaleText.setText(originalValue); } showValidNess(validator.isValid(scaleText.getText())); } @Override public void keyPressed(KeyEvent event) { if (event.keyCode == SWT.CR || event.keyCode == SWT.LF) { event.doit = false; } } @Override public void keyReleased(KeyEvent event) { scaleText.setMarginColor(null); showValidNess(validator.isValid(scaleText.getText())); if (event.keyCode == SWT.CR || event.keyCode == SWT.LF) { event.doit = false; focusLost(null); } } private void showValidNess(String message) { Color background = message == null ? null : Display.getDefault().getSystemColor(SWT.COLOR_RED); scaleText.setForeground(background); scaleText.setToolTipText(message); // scaleError } @Override public void verifyKey(VerifyEvent event) { if (event.keyCode == SWT.CR || event.keyCode == SWT.LF) { // The user pressed Enter event.doit = false; } } } }