1 /* |
|
2 * CDDL HEADER START |
|
3 * |
|
4 * The contents of this file are subject to the terms of the |
|
5 * Common Development and Distribution License (the "License"). |
|
6 * You may not use this file except in compliance with the License. |
|
7 * |
|
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
|
9 * or http://www.opensolaris.org/os/licensing. |
|
10 * See the License for the specific language governing permissions |
|
11 * and limitations under the License. |
|
12 * |
|
13 * When distributing Covered Code, include this CDDL HEADER in each |
|
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
|
15 * If applicable, add the following below this CDDL HEADER, with the |
|
16 * fields enclosed by brackets "[]" replaced with your own identifying |
|
17 * information: Portions Copyright [yyyy] [name of copyright owner] |
|
18 * |
|
19 * CDDL HEADER END |
|
20 */ |
|
21 |
|
22 /* |
|
23 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
|
24 */ |
|
25 |
|
26 package com.oracle.solaris.vp.panel.swing.timezone; |
|
27 |
|
28 import java.awt.*; |
|
29 import java.awt.geom.Point2D; |
|
30 import java.beans.*; |
|
31 import java.text.DateFormat; |
|
32 import java.util.*; |
|
33 import java.util.List; |
|
34 import javax.swing.event.*; |
|
35 import javax.swing.Icon; |
|
36 import com.oracle.solaris.vp.panel.common.model.View; |
|
37 import com.oracle.solaris.vp.panels.time.*; |
|
38 import com.oracle.solaris.vp.util.misc.finder.Finder; |
|
39 import com.oracle.solaris.vp.util.misc.property.MutableProperty; |
|
40 import com.oracle.solaris.vp.util.swing.layout.ColumnLayoutConstraint; |
|
41 import com.oracle.solaris.vp.util.swing.SettingsPanel; |
|
42 import com.oracle.solaris.vp.util.swing.time.*; |
|
43 |
|
44 public class TimeZonePanel extends SettingsPanel |
|
45 implements View<TimeZoneModel> { |
|
46 |
|
47 // |
|
48 // Static data |
|
49 // |
|
50 |
|
51 private static TimeZoneMapper MAPPER = new TimeZoneMapper(); |
|
52 |
|
53 private static final Icon BACKGROUND_ICON = |
|
54 Finder.getIcon("images/world_map-960.png"); |
|
55 |
|
56 /** |
|
57 * Gets the {@code TimeZone} that corresponds to the given {@link |
|
58 * TimeZoneInfo}. |
|
59 * |
|
60 * @return a {@code TimeZone}, or {@code null} if no {@code |
|
61 * TimeZone} corresponds to the given {@link TimeZoneInfo} |
|
62 */ |
|
63 public static TimeZone toTimeZone(TimeZoneInfo info) { |
|
64 // Unfortunately TimeZone.getTimeZone(String id) gives no error when |
|
65 // id is not recognized, so use a different approach |
|
66 String[] ids = TimeZone.getAvailableIDs(); |
|
67 for (String id : ids) { |
|
68 if (id.equals(info.getName()) || |
|
69 id.equals(info.getAltName())) { |
|
70 return TimeZone.getTimeZone(id); |
|
71 } |
|
72 } |
|
73 |
|
74 // XXX We should never return null. TimeZoneInfo should have a |
|
75 // int getRawOffset method to ensure we can always create a |
|
76 // TimeZone. |
|
77 return null; |
|
78 } |
|
79 |
|
80 // |
|
81 // Instance data |
|
82 // |
|
83 |
|
84 private LocationPanel<TZChoice> locationPanel_; |
|
85 private MutableProperty<TZChoice> locationProp_; |
|
86 |
|
87 private Map<String, TZChoice> nameToTimeZone = |
|
88 new HashMap<String, TZChoice>(); |
|
89 private Map<String, Continent> continentDesc = |
|
90 new HashMap<String, Continent>(); |
|
91 private Map<String, Country> countryDesc = new HashMap<String, Country>(); |
|
92 private Map<Integer, Offset> offsetDesc = new HashMap<Integer, Offset>(); |
|
93 private Map<Integer, Offset> rawoffsetDesc = new HashMap<Integer, Offset>(); |
|
94 |
|
95 // |
|
96 // Inner classes |
|
97 // |
|
98 |
|
99 /* |
|
100 * Need an object to encapsulate a timezone offset. |
|
101 */ |
|
102 private class Offset { |
|
103 int offset_; |
|
104 |
|
105 public Offset(int offset) { |
|
106 offset_ = offset; |
|
107 } |
|
108 |
|
109 @Override |
|
110 public String toString() { |
|
111 boolean neg = offset_ < 0; |
|
112 int minutes = Math.abs(offset_) / (60 * 1000); |
|
113 int hours = minutes / 60; |
|
114 minutes = minutes % 60; |
|
115 return (String.format("GMT%s%d:%02d", |
|
116 neg ? "-" : "+", hours, minutes)); |
|
117 } |
|
118 } |
|
119 |
|
120 /* |
|
121 * We could use TimeZoneInfos directly, but instead we use this wrapper |
|
122 * type to cache the associations between each time zone and its various |
|
123 * attributes. |
|
124 */ |
|
125 private class TZChoice { |
|
126 TimeZoneInfo info_; |
|
127 Country country_; |
|
128 Continent continent_; |
|
129 Offset offset_; |
|
130 Offset rawoffset_; |
|
131 String label_; |
|
132 Point2D point_; |
|
133 |
|
134 public TZChoice(TimeZoneInfo info) { |
|
135 TimeZone tz = TimeZone.getTimeZone(info.getName()); |
|
136 info_ = info; |
|
137 String contname = info.getName().split("/")[0]; |
|
138 continent_ = continentDesc.get(contname); |
|
139 country_ = countryDesc.get(info.getCountryCode()); |
|
140 offset_ = makeOffset(tz.getOffset(System.currentTimeMillis()), |
|
141 offsetDesc); |
|
142 rawoffset_ = makeOffset(tz.getRawOffset(), rawoffsetDesc); |
|
143 |
|
144 String alt = info.getAltName(); |
|
145 String name = alt != null ? alt : info.getName(); |
|
146 String display = tz.getDisplayName(); |
|
147 label_ = Finder.getString("timezone.description", name, display); |
|
148 |
|
149 Coordinates coords = info.getCoordinates(); |
|
150 double east = (double)coords.getDegreesE() + |
|
151 (coords.getDegreesE() > 0 ? 1 : -1) * |
|
152 ((double)coords.getSecondsE() / 60 + |
|
153 (double)coords.getMinutesE()) / 60; |
|
154 double north = (double)coords.getDegreesN() + |
|
155 (coords.getDegreesN() > 0 ? 1 : -1) * |
|
156 ((double)coords.getSecondsN() / 60 + |
|
157 (double)coords.getMinutesN()) / 60; |
|
158 east = 180 + east; |
|
159 north = 90 - north; |
|
160 point_ = new Point2D.Double(east / 360, north / 180); |
|
161 } |
|
162 |
|
163 private Offset makeOffset(int offset, Map<Integer, Offset>map) { |
|
164 Offset result = map.get(offset); |
|
165 if (result == null) |
|
166 map.put(offset, result = new Offset(offset)); |
|
167 return (result); |
|
168 } |
|
169 |
|
170 public TimeZoneInfo getInfo() { |
|
171 return (info_); |
|
172 } |
|
173 } |
|
174 |
|
175 private static class TimeZoneMapper implements LocationMapper<TZChoice> { |
|
176 @Override |
|
177 public Point2D map(TZChoice choice) { |
|
178 return choice.point_; |
|
179 } |
|
180 |
|
181 @Override |
|
182 public String getLabel(TZChoice choice) { |
|
183 return choice.label_; |
|
184 } |
|
185 } |
|
186 |
|
187 private class TZOffsetCriteria extends LocationCriteria<TZChoice, Offset> |
|
188 implements Comparator<Offset> { |
|
189 |
|
190 public String getLabel() { |
|
191 return Finder.getString("timezone.label.offset"); |
|
192 } |
|
193 |
|
194 public String getUnselectedText() { |
|
195 return Finder.getString("timezone.offset.select"); |
|
196 } |
|
197 |
|
198 @Override |
|
199 public Comparator<Offset> getComparator() { |
|
200 return this; |
|
201 } |
|
202 |
|
203 public Offset toCriterion(TZChoice choice) { |
|
204 return choice.offset_; |
|
205 } |
|
206 |
|
207 public String toDescription(Offset criterion) { |
|
208 return criterion.toString(); |
|
209 } |
|
210 |
|
211 /* |
|
212 * Comparator methods |
|
213 */ |
|
214 |
|
215 public int compare(Offset o1, Offset o2) { |
|
216 return (o1.offset_ - o2.offset_); |
|
217 } |
|
218 } |
|
219 |
|
220 private class TZContinentCriteria |
|
221 extends LocationCriteria<TZChoice, Continent> { |
|
222 |
|
223 public String getLabel() { |
|
224 return Finder.getString("timezone.label.continent"); |
|
225 } |
|
226 |
|
227 public String getUnselectedText() { |
|
228 return Finder.getString("timezone.continent.select"); |
|
229 } |
|
230 |
|
231 public Continent toCriterion(TZChoice choice) { |
|
232 return (choice.continent_); |
|
233 } |
|
234 |
|
235 public String toDescription(Continent criterion) { |
|
236 return criterion.getDescription(); |
|
237 } |
|
238 } |
|
239 |
|
240 private class TZCountryCriteria |
|
241 extends LocationCriteria<TZChoice, Country> { |
|
242 |
|
243 public String getLabel() { |
|
244 return Finder.getString("timezone.label.country"); |
|
245 } |
|
246 |
|
247 public String getUnselectedText() { |
|
248 return Finder.getString("timezone.country.select"); |
|
249 } |
|
250 |
|
251 public Country toCriterion(TZChoice choice) { |
|
252 return (choice.country_); |
|
253 } |
|
254 |
|
255 public String toDescription(Country criterion) { |
|
256 return criterion.getDescription(); |
|
257 } |
|
258 } |
|
259 |
|
260 private class TZZoneCriteria extends LocationCriteria<TZChoice, TZChoice> { |
|
261 |
|
262 public String getLabel() { |
|
263 return Finder.getString("timezone.label.timezone"); |
|
264 } |
|
265 |
|
266 public String getUnselectedText() { |
|
267 return Finder.getString("timezone.timezone.select"); |
|
268 } |
|
269 |
|
270 public TZChoice toCriterion(TZChoice choice) { |
|
271 return choice; |
|
272 } |
|
273 |
|
274 public String toDescription(TZChoice criterion) { |
|
275 return (MAPPER.getLabel(criterion)); |
|
276 } |
|
277 } |
|
278 |
|
279 // |
|
280 // Constructors |
|
281 // |
|
282 |
|
283 public TimeZonePanel(Time bean, final SimpleTimeModel model) { |
|
284 |
|
285 getHelpField().setText(Finder.getString("timezone.desc")); |
|
286 |
|
287 // Remove the spacing between the help and content panes |
|
288 ColumnLayoutConstraint constraint = |
|
289 getLayout().getConstraint(getContentPane()); |
|
290 constraint.setGap(0); |
|
291 |
|
292 /* |
|
293 * Read metadata from Time bean. Ideally would be read from the model. |
|
294 */ |
|
295 List<TimeZoneInfo> infoSet = bean.gettimeZones(); |
|
296 List<Continent> continents = bean.getcontinents(); |
|
297 List<Country> countries = bean.getcountries(); |
|
298 |
|
299 for (Continent c : continents) |
|
300 continentDesc.put(c.getName(), c); |
|
301 |
|
302 for (Country c : countries) |
|
303 countryDesc.put(c.getCode(), c); |
|
304 |
|
305 List<TZChoice> choices = new LinkedList<TZChoice>(); |
|
306 for (TimeZoneInfo zone : infoSet) { |
|
307 TZChoice choice = new TZChoice(zone); |
|
308 if (choice.continent_ == null || choice.country_ == null) |
|
309 continue; |
|
310 choices.add(choice); |
|
311 nameToTimeZone.put(zone.getName(), choice); |
|
312 if (zone.getAltName() != null) |
|
313 nameToTimeZone.put(zone.getAltName(), choice); |
|
314 } |
|
315 |
|
316 List<LocationCriteria<TZChoice, ?>> criteria = |
|
317 new LinkedList<LocationCriteria<TZChoice, ?>>(); |
|
318 criteria.add(new TZContinentCriteria()); |
|
319 criteria.add(new TZCountryCriteria()); |
|
320 // criteria.add(new TZOffsetCriteria()); // *instead* of the other two |
|
321 |
|
322 locationPanel_ = new LocationPanel<TZChoice>(BACKGROUND_ICON, |
|
323 new Dimension(400, 200), MAPPER); |
|
324 locationProp_ = locationPanel_.getLocationProperty(); |
|
325 getChangeableAggregator().addChangeables(locationProp_); |
|
326 locationPanel_.setCriteria(criteria, new TZZoneCriteria(), choices, |
|
327 model != null ? Finder.getString("timezone.label.time") : null); |
|
328 |
|
329 if (model != null) { |
|
330 /* |
|
331 * Add listener that updates SimpleTimeModel with selected |
|
332 * time zone |
|
333 */ |
|
334 locationProp_.addChangeListener( |
|
335 new ChangeListener() { |
|
336 @Override |
|
337 public void stateChanged(ChangeEvent e) { |
|
338 TZChoice choice = locationProp_.getValue(); |
|
339 if (choice != null) { |
|
340 TimeZone zone = toTimeZone(choice.getInfo()); |
|
341 model.setTimeZone(zone); |
|
342 } |
|
343 } |
|
344 }); |
|
345 |
|
346 /* |
|
347 * And add a listener to the model that updates the auxiliary |
|
348 * field of the location panel with the current time. |
|
349 */ |
|
350 final DateFormat format = DateFormat.getDateTimeInstance( |
|
351 DateFormat.SHORT, DateFormat.SHORT); |
|
352 format.setCalendar(model.getModelCalendar()); |
|
353 |
|
354 PropertyChangeListener timeLabelListener = |
|
355 new PropertyChangeListener() { |
|
356 @Override |
|
357 public void propertyChange(PropertyChangeEvent e) { |
|
358 locationPanel_.getAuxField().setText(format.format( |
|
359 model.getCalendar().getTime())); |
|
360 } |
|
361 }; |
|
362 model.addPropertyChangeListener(TimeModel.PROPERTY_TIME, |
|
363 timeLabelListener); |
|
364 |
|
365 // Initialize |
|
366 timeLabelListener.propertyChange(null); |
|
367 } |
|
368 |
|
369 getContentPane().add(locationPanel_); |
|
370 setContent(locationPanel_, false, false); |
|
371 } |
|
372 |
|
373 // |
|
374 // View methods |
|
375 // |
|
376 |
|
377 public void modelToView(TimeZoneModel model) { |
|
378 // Sanity check -- the UI should be updated only on the event thread |
|
379 assert EventQueue.isDispatchThread(); |
|
380 |
|
381 String name = model.getTimeZone(); |
|
382 TZChoice timeZone = nameToTimeZone.get(name); |
|
383 locationProp_.update(timeZone, false); |
|
384 } |
|
385 |
|
386 public void viewToModel(TimeZoneModel model) { |
|
387 TZChoice tz = locationProp_.getValue(); |
|
388 model.setTimeZone(tz != null ? tz.getInfo().getName() : null); |
|
389 } |
|
390 } |
|