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) { } }