1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ddmuilib.location; 18 19 import org.eclipse.swt.SWT; 20 import org.eclipse.swt.events.ModifyEvent; 21 import org.eclipse.swt.events.ModifyListener; 22 import org.eclipse.swt.graphics.Point; 23 import org.eclipse.swt.layout.GridData; 24 import org.eclipse.swt.layout.GridLayout; 25 import org.eclipse.swt.widgets.Composite; 26 import org.eclipse.swt.widgets.Text; 27 28 import java.text.DecimalFormat; 29 import java.text.ParseException; 30 31 /** 32 * Encapsulation of controls handling a location coordinate in decimal and sexagesimal. 33 * <p/>This handle the conversion between both modes automatically by using a {@link ModifyListener} 34 * on all the {@link Text} widgets. 35 * <p/>To get/set the coordinate, use {@link #setValue(double)} and {@link #getValue()} (preceded by 36 * a call to {@link #isValueValid()}) 37 */ 38 public final class CoordinateControls { 39 private double mValue; 40 private boolean mValueValidity = false; 41 private Text mDecimalText; 42 private Text mSexagesimalDegreeText; 43 private Text mSexagesimalMinuteText; 44 private Text mSexagesimalSecondText; 45 private final DecimalFormat mDecimalFormat = new DecimalFormat(); 46 47 /** Internal flag to prevent {@link ModifyEvent} to be sent when {@link Text#setText(String)} 48 * is called. This is an int instead of a boolean to act as a counter. */ 49 private int mManualTextChange = 0; 50 51 /** 52 * ModifyListener for the 3 {@link Text} controls of the sexagesimal mode. 53 */ 54 private ModifyListener mSexagesimalListener = new ModifyListener() { 55 public void modifyText(ModifyEvent event) { 56 if (mManualTextChange > 0) { 57 return; 58 } 59 try { 60 mValue = getValueFromSexagesimalControls(); 61 setValueIntoDecimalControl(mValue); 62 mValueValidity = true; 63 } catch (ParseException e) { 64 // wrong format empty the decimal controls. 65 mValueValidity = false; 66 resetDecimalControls(); 67 } 68 } 69 }; 70 71 /** 72 * Creates the {@link Text} control for the decimal display of the coordinate. 73 * <p/>The control is expected to be placed in a Composite using a {@link GridLayout}. 74 * @param parent The {@link Composite} parent of the control. 75 */ 76 public void createDecimalText(Composite parent) { 77 mDecimalText = createTextControl(parent, "-199.999999", new ModifyListener() { 78 public void modifyText(ModifyEvent event) { 79 if (mManualTextChange > 0) { 80 return; 81 } 82 try { 83 mValue = mDecimalFormat.parse(mDecimalText.getText()).doubleValue(); 84 setValueIntoSexagesimalControl(mValue); 85 mValueValidity = true; 86 } catch (ParseException e) { 87 // wrong format empty the sexagesimal controls. 88 mValueValidity = false; 89 resetSexagesimalControls(); 90 } 91 } 92 }); 93 } 94 95 /** 96 * Creates the {@link Text} control for the "degree" display of the coordinate in sexagesimal 97 * mode. 98 * <p/>The control is expected to be placed in a Composite using a {@link GridLayout}. 99 * @param parent The {@link Composite} parent of the control. 100 */ 101 public void createSexagesimalDegreeText(Composite parent) { 102 mSexagesimalDegreeText = createTextControl(parent, "-199", mSexagesimalListener); //$NON-NLS-1$ 103 } 104 105 /** 106 * Creates the {@link Text} control for the "minute" display of the coordinate in sexagesimal 107 * mode. 108 * <p/>The control is expected to be placed in a Composite using a {@link GridLayout}. 109 * @param parent The {@link Composite} parent of the control. 110 */ 111 public void createSexagesimalMinuteText(Composite parent) { 112 mSexagesimalMinuteText = createTextControl(parent, "99", mSexagesimalListener); //$NON-NLS-1$ 113 } 114 115 /** 116 * Creates the {@link Text} control for the "second" display of the coordinate in sexagesimal 117 * mode. 118 * <p/>The control is expected to be placed in a Composite using a {@link GridLayout}. 119 * @param parent The {@link Composite} parent of the control. 120 */ 121 public void createSexagesimalSecondText(Composite parent) { 122 mSexagesimalSecondText = createTextControl(parent, "99.999", mSexagesimalListener); //$NON-NLS-1$ 123 } 124 125 /** 126 * Sets the coordinate into the {@link Text} controls. 127 * @param value the coordinate value to set. 128 */ 129 public void setValue(double value) { 130 mValue = value; 131 mValueValidity = true; 132 setValueIntoDecimalControl(value); 133 setValueIntoSexagesimalControl(value); 134 } 135 136 /** 137 * Returns whether the value in the control(s) is valid. 138 */ 139 public boolean isValueValid() { 140 return mValueValidity; 141 } 142 143 /** 144 * Returns the current value set in the control(s). 145 * <p/>This value can be erroneous, and a check with {@link #isValueValid()} should be performed 146 * before any call to this method. 147 */ 148 public double getValue() { 149 return mValue; 150 } 151 152 /** 153 * Enables or disables all the {@link Text} controls. 154 * @param enabled the enabled state. 155 */ 156 public void setEnabled(boolean enabled) { 157 mDecimalText.setEnabled(enabled); 158 mSexagesimalDegreeText.setEnabled(enabled); 159 mSexagesimalMinuteText.setEnabled(enabled); 160 mSexagesimalSecondText.setEnabled(enabled); 161 } 162 163 private void resetDecimalControls() { 164 mManualTextChange++; 165 mDecimalText.setText(""); //$NON-NLS-1$ 166 mManualTextChange--; 167 } 168 169 private void resetSexagesimalControls() { 170 mManualTextChange++; 171 mSexagesimalDegreeText.setText(""); //$NON-NLS-1$ 172 mSexagesimalMinuteText.setText(""); //$NON-NLS-1$ 173 mSexagesimalSecondText.setText(""); //$NON-NLS-1$ 174 mManualTextChange--; 175 } 176 177 /** 178 * Creates a {@link Text} with a given parent, default string and a {@link ModifyListener} 179 * @param parent the parent {@link Composite}. 180 * @param defaultString the default string to be used to compute the {@link Text} control 181 * size hint. 182 * @param listener the {@link ModifyListener} to be called when the {@link Text} control is 183 * modified. 184 */ 185 private Text createTextControl(Composite parent, String defaultString, 186 ModifyListener listener) { 187 // create the control 188 Text text = new Text(parent, SWT.BORDER | SWT.LEFT | SWT.SINGLE); 189 190 // add the standard listener to it. 191 text.addModifyListener(listener); 192 193 // compute its size/ 194 mManualTextChange++; 195 text.setText(defaultString); 196 text.pack(); 197 Point size = text.computeSize(SWT.DEFAULT, SWT.DEFAULT); 198 text.setText(""); //$NON-NLS-1$ 199 mManualTextChange--; 200 201 GridData gridData = new GridData(); 202 gridData.widthHint = size.x; 203 text.setLayoutData(gridData); 204 205 return text; 206 } 207 208 private double getValueFromSexagesimalControls() throws ParseException { 209 double degrees = mDecimalFormat.parse(mSexagesimalDegreeText.getText()).doubleValue(); 210 double minutes = mDecimalFormat.parse(mSexagesimalMinuteText.getText()).doubleValue(); 211 double seconds = mDecimalFormat.parse(mSexagesimalSecondText.getText()).doubleValue(); 212 213 boolean isPositive = (degrees >= 0.); 214 degrees = Math.abs(degrees); 215 216 double value = degrees + minutes / 60. + seconds / 3600.; 217 return isPositive ? value : - value; 218 } 219 220 private void setValueIntoDecimalControl(double value) { 221 mManualTextChange++; 222 mDecimalText.setText(String.format("%.6f", value)); 223 mManualTextChange--; 224 } 225 226 private void setValueIntoSexagesimalControl(double value) { 227 // get the sign and make the number positive no matter what. 228 boolean isPositive = (value >= 0.); 229 value = Math.abs(value); 230 231 // get the degree 232 double degrees = Math.floor(value); 233 234 // get the minutes 235 double minutes = Math.floor((value - degrees) * 60.); 236 237 // get the seconds. 238 double seconds = (value - degrees) * 3600. - minutes * 60.; 239 240 mManualTextChange++; 241 mSexagesimalDegreeText.setText( 242 Integer.toString(isPositive ? (int)degrees : (int)- degrees)); 243 mSexagesimalMinuteText.setText(Integer.toString((int)minutes)); 244 mSexagesimalSecondText.setText(String.format("%.3f", seconds)); //$NON-NLS-1$ 245 mManualTextChange--; 246 } 247 } 248