package jfconfig;
/** Time zone selector
*
* Created : Oct 21, 2014
*
* @author pquiring
*/
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import javax.swing.*;
import javaforce.*;
public class TimeZonePanel extends javax.swing.JPanel implements MouseListener, MouseMotionListener {
/**
* Creates new form TimeZone
*/
public TimeZonePanel() {
initComponents();
initMap();
loadZones();
calcMapCoords();
repaintMap();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
map = new javax.swing.JPanel();
jLabel2 = new javax.swing.JLabel();
timezone = new javax.swing.JComboBox();
map.setLayout(new java.awt.GridLayout(1, 0));
jLabel2.setText("Timezone:");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(map, javax.swing.GroupLayout.PREFERRED_SIZE, 480, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel2)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(timezone, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addContainerGap(24, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addContainerGap()
.addComponent(map, javax.swing.GroupLayout.PREFERRED_SIZE, 240, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel2)
.addComponent(timezone, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel jLabel2;
private javax.swing.JPanel map;
private javax.swing.JComboBox timezone;
// End of variables declaration//GEN-END:variables
private class Zone {
double lng, lat;
int x, y; //translated screen coords
String tz;
}
private JFImage imgorg, img;
private ArrayList<Zone> zones = new ArrayList<Zone>();
private int selIdx = -1;
private void initMap() {
imgorg = new JFImage();
imgorg.load(getClass().getResourceAsStream("/worldmap.png"));
img = new JFImage();
img.setImageSize(imgorg.getWidth(), imgorg.getHeight());
img.putJFImage(imgorg, 0, 0);
img.addMouseMotionListener(this);
img.addMouseListener(this);
img.setResizeOperation(JFImage.ResizeOperation.CHOP);
map.add(img);
map.revalidate();
}
private float decodeDegMinSec(String dms) {
boolean neg = dms.charAt(0) == '-';
String d,m,s;
switch (dms.length()) {
default:
return 0;
case 5://-DDMM
d = dms.substring(1,2+1);
m = dms.substring(3,4+1);
s = "0";
break;
case 6://-DDDMM
d = dms.substring(1,3+1);
m = dms.substring(4,5+1);
s = "0";
break;
case 7://-DDMMSS
d = dms.substring(1,2+1);
m = dms.substring(3,4+1);
s = dms.substring(5,6+1);
break;
case 8://-DDDMMSS
d = dms.substring(1,3+1);
m = dms.substring(4,5+1);
s = dms.substring(6,7+1);
break;
}
float deg = Integer.valueOf(d);
float min = ((float)Integer.valueOf(m)) / 60.0f;
float sec = ((float)Integer.valueOf(s)) / 3600.0f;
float com = deg + min + sec;
if (neg) return -com; else return com;
}
private void loadZones() {
try {
File tab = new File("/usr/share/zoneinfo/zone.tab");
BufferedReader br = new BufferedReader(new FileReader(tab));
while (true) {
String ln = br.readLine();
if (ln == null) break;
if (ln.length() == 0) continue;
if (ln.charAt(0) == '#') continue;
String f[] = ln.split("\t"); //tab delimited
//CC coords dir/file [desc]
float lat, lng;
if (f[1].length() == 11) {
//-DDMM-DDDMM
lat = decodeDegMinSec(f[1].substring(0, 1+4));
lng = decodeDegMinSec(f[1].substring(5));
} else {
//-DDMMSS-DDDMMSS
lat = decodeDegMinSec(f[1].substring(0, 1+6));
lng = decodeDegMinSec(f[1].substring(7));
}
Zone zone = new Zone();
zone.lat = lat;
zone.lng = lng;
zone.tz = f[2];
zones.add(zone);
timezone.addItem(f[2] + " (" + lat + " " + lng + ")");
}
} catch (Exception e) {
JFLog.log(e);
}
}
//see http://en.wikipedia.org/wiki/Miller_cylindrical_projection
//image and equations taken from Ubuntu ubiquity
private int lngToX(double lng) {
double width = img.getWidth();
double x = (lng * (Math.PI / 180.0)) + Math.PI;
x = x / (2.0 * Math.PI);
x = x * width;
return (int)x;
}
private int latToY(double lat) {
double height = img.getHeight();
double y = 1.25 * Math.log(Math.tan(
(0.25 * Math.PI) + (0.4 * (lat * (Math.PI / 180.0)))));
//100 % = 4.6068250867599998
y /= 2.30341254338;
y *= 1.3; //scale adjust
y = y * -height/2.0 + height/2.0;
return (int)y;
}
private void calcMapCoords() {
for(int a=0;a<zones.size();a++) {
Zone zone = zones.get(a);
zone.x = lngToX(zone.lng);
zone.y = latToY(zone.lat);
}
}
private void repaintMap() {
img.putJFImage(imgorg, 0, 0);
Graphics2D g = img.getGraphics2D();
for(int a=0;a<zones.size();a++) {
Zone z = zones.get(a);
if (a == selIdx)
g.setColor(new Color(0x00ff00)); //green dot (selected)
else
g.setColor(new Color(0xff0000)); //red dot (not selected)
int rad = 4; //TODO : change size based on bandwidth
g.fillOval(z.x-rad/2, z.y-rad/2, rad, rad);
}
img.repaint();
}
private void updateSelection() {
if (selIdx == -1) return;
timezone.setSelectedIndex(selIdx);
}
public void setTimeZone(String tz) {
for(int a=0;a<zones.size();a++) {
Zone z = zones.get(a);
if (tz.equalsIgnoreCase(z.tz)) {
selIdx = a;
updateSelection();
repaintMap();
return;
}
}
JFLog.log("Error:timezone unknown:" + tz);
}
public String getTimeZone() {
if (selIdx == -1) return null;
Zone z = zones.get(selIdx);
return z.tz;
}
public boolean applyTimeZone() {
if (selIdx == -1) return false;
Zone z = zones.get(selIdx);
ShellProcess sp;
File file = new File("/tmp/settimezone");
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write("#/bin/bash\n".getBytes());
fos.write("rm /etc/localtime\n".getBytes());
fos.write(("ln -s /usr/share/zoneinfo/" + z.tz + " /etc/localtime\n").getBytes());
fos.write(("echo " + z.tz + ">/etc/timezone").getBytes());
fos.close();
file.setExecutable(true);
sp = new ShellProcess();
sp.run(new String[]{
"sudo", "/tmp/settimezone"
}, false);
if (sp.getErrorLevel() != 0) throw new Exception("failed");
file.delete();
return true;
} catch (Exception e) {
file.delete();
JF.showError("Error", "Unable to set timezone");
JFLog.log(e);
return false;
}
}
/** Loads current zone from /etc/timezone */
public void loadCurrentZone() {
File file = new File("/etc/timezone");
if (!file.exists()) {
JFLog.log("Error:/etc/timezone does not exist");
return;
}
try {
BufferedReader br = new BufferedReader(new FileReader(file));
String tz = br.readLine();
br.close();
JFLog.log("current timezone=" + tz);
setTimeZone(tz);
} catch (Exception e) {
JFLog.log(e);
}
}
public void mouseClicked(MouseEvent e) {
//find closest to point
int x = e.getX();
int y = e.getY();
int bd = 0xffffff;
int bi = -1;
Zone z;
for(int a=0;a<zones.size();a++) {
z = zones.get(a);
int d = (int)Math.sqrt(Math.pow(z.x - x, 2) + Math.pow(z.y - y, 2));
if (d < bd) {
bi = a;
bd = d;
}
}
if (bi == -1) return;
selIdx = bi;
updateSelection();
repaintMap();
}
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
public void mousePressed(MouseEvent e) { }
public void mouseReleased(MouseEvent e) { }
public void mouseDragged(MouseEvent e) { }
public void mouseMoved(MouseEvent e) { }
}