Changeset 2662 in josm for trunk/src/org


Ignore:
Timestamp:
2009-12-19T18:56:45+01:00 (15 years ago)
Author:
bastiK
Message:

geoimage: reworked image correlation dialog. Might still have some quirks here and there. New: displays and updates the number of matched images in the status bar while you type.

Location:
trunk/src/org/openstreetmap/josm/gui
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/gui/ExtendedDialog.java

    r2627 r2662  
    192192    }
    193193
    194     protected void setupDialog() {
     194    private boolean setupDone = false;
     195   
     196    /**
     197     * This is called by showDialog().
     198     * Only invoke from outside if you need to modify the contentPane
     199     */
     200    public void setupDialog() {
     201        if (setupDone)
     202            return;
     203        setupDone = true;
     204
    195205        setupEscListener();
    196206
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/CorrelateGpxWithImages.java

    r2649 r2662  
    1616import java.awt.event.ActionEvent;
    1717import java.awt.event.ActionListener;
     18import java.awt.event.ItemEvent;
     19import java.awt.event.ItemListener;
     20import java.awt.event.WindowAdapter;
     21import java.awt.event.WindowEvent;
    1822import java.io.File;
    1923import java.io.FileInputStream;
     
    3539
    3640import javax.swing.AbstractListModel;
     41import javax.swing.BorderFactory;
    3742import javax.swing.ButtonGroup;
    3843import javax.swing.JButton;
     
    4651import javax.swing.JRadioButton;
    4752import javax.swing.JScrollPane;
     53import javax.swing.JSeparator;
    4854import javax.swing.JSlider;
    4955import javax.swing.JTextField;
    5056import javax.swing.ListSelectionModel;
     57import javax.swing.SwingConstants;
    5158import javax.swing.event.ChangeEvent;
    5259import javax.swing.event.ChangeListener;
     60import javax.swing.event.DocumentEvent;
     61import javax.swing.event.DocumentListener;
    5362import javax.swing.event.ListSelectionEvent;
    5463import javax.swing.event.ListSelectionListener;
     
    6372import org.openstreetmap.josm.gui.layer.GpxLayer;
    6473import org.openstreetmap.josm.gui.layer.Layer;
    65 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;
    6674import org.openstreetmap.josm.io.GpxReader;
    6775import org.openstreetmap.josm.tools.ExifReader;
     
    7987    private static List<GpxData> loadedGpxData = new ArrayList<GpxData>();
    8088
    81     public static class CorrelateParameters {
    82         GpxData gpxData;
    83         float timezone;
    84         long offset;
    85     }
    86 
    8789    GeoImageLayer yLayer = null;
     90    double timezone;
     91    long delta;
     92   
     93    public CorrelateGpxWithImages(GeoImageLayer layer) {
     94        this.yLayer = layer;
     95    }
    8896
    8997    private static class GpxDataWrapper {
     
    104112    }
    105113
     114    ExtendedDialog syncDialog;
    106115    Vector<GpxDataWrapper> gpxLst = new Vector<GpxDataWrapper>();
    107     JPanel panel = null;
    108     JComboBox cbGpx = null;
    109     JTextField tfTimezone = null;
    110     JTextField tfOffset = null;
    111     JRadioButton rbAllImg = null;
    112     JRadioButton rbUntaggedImg = null;
    113     JRadioButton rbNoExifImg = null;
     116    JPanel outerPanel;
     117    JComboBox cbGpx;
     118    JTextField tfTimezone;
     119    JTextField tfOffset;
     120    JCheckBox cbExifImg;
     121    JCheckBox cbTaggedImg;
     122    JCheckBox cbShowThumbs;
     123    JLabel statusBarText;
     124    StatusBarListener statusBarListener;
     125   
     126    // remember the last number of matched photos
     127    int lastNumMatched = 0;
    114128
    115129    /** This class is called when the user doesn't find the GPX file he needs in the files that have
     
    140154
    141155            try {
    142                 panel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
     156                outerPanel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    143157
    144158                Main.pref.put("lastDirectory", sel.getPath());
     
    197211                cbGpx.setSelectedIndex(cbGpx.getItemCount() - 1);
    198212            } finally {
    199                 panel.setCursor(Cursor.getDefaultCursor());
     213                outerPanel.setCursor(Cursor.getDefaultCursor());
    200214            }
    201215        }
     
    257271            panelTf.add(new JLabel(tr("Gps time (read from the above photo): ")), gc);
    258272
    259             tfGpsTime = new JTextField();
     273            tfGpsTime = new JTextField(12);
    260274            tfGpsTime.setEnabled(false);
    261             tfGpsTime.setMinimumSize(new Dimension(150, tfGpsTime.getMinimumSize().height));
     275            tfGpsTime.setMinimumSize(new Dimension(155, tfGpsTime.getMinimumSize().height));
    262276            gc.gridx = 1;
    263277            gc.weightx = 1.0;
     
    334348                    if (date != null) {
    335349                        lbExifTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date));
    336                         tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy ").format(date));
     350                        tfGpsTime.setText(new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(date));
    337351                        tfGpsTime.setCaretPosition(tfGpsTime.getText().length());
    338352                        tfGpsTime.setEnabled(true);
     353                        tfGpsTime.requestFocus();
    339354                    } else {
    340355                        lbExifTime.setText(tr("No date"));
     
    418433
    419434            }
    420 
    421         }
    422     }
    423 
    424     public CorrelateGpxWithImages(GeoImageLayer layer) {
    425         this.yLayer = layer;
     435            statusBarListener.updateStatusBar();
     436            yLayer.updateBufferAndRepaint();
     437        }
    426438    }
    427439
     
    454466
    455467        JPanel panelCb = new JPanel();
    456         panelCb.setLayout(new FlowLayout());
    457468
    458469        panelCb.add(new JLabel(tr("GPX track: ")));
     
    465476
    466477        JButton buttonOpen = new JButton(tr("Open another GPX trace"));
    467         buttonOpen.setIcon(ImageProvider.get("dialogs/geoimage/geoimage-open"));
    468478        buttonOpen.addActionListener(new LoadGpxDataActionListener());
    469 
    470479        panelCb.add(buttonOpen);
    471480
     
    473482        panelTf.setLayout(new GridBagLayout());
    474483
    475         GridBagConstraints gc = new GridBagConstraints();
    476         gc.anchor = GridBagConstraints.WEST;
    477 
    478         gc.gridx = gc.gridy = 0;
    479         gc.gridwidth = gc.gridheight = 1;
    480         gc.fill = GridBagConstraints.NONE;
    481         gc.weightx = gc.weighty = 0.0;
    482         panelTf.add(new JLabel(tr("Timezone: ")), gc);
    483 
    484         float gpstimezone = Float.parseFloat(Main.pref.get("geoimage.doublegpstimezone", "0.0"));
    485         if (gpstimezone == 0.0) {
    486             gpstimezone = - Long.parseLong(Main.pref.get("geoimage.gpstimezone", "0"));
    487         }
    488         tfTimezone = new JTextField();
    489         tfTimezone.setText(formatTimezone(gpstimezone));
    490 
    491         gc.gridx = 1;
    492         gc.gridy = 0;
    493         gc.gridwidth = gc.gridheight = 1;
    494         gc.fill = GridBagConstraints.HORIZONTAL;
    495         gc.weightx = 1.0;
    496         gc.weighty = 0.0;
    497         panelTf.add(tfTimezone, gc);
    498 
    499         gc.gridx = 0;
    500         gc.gridy = 1;
    501         gc.gridwidth = gc.gridheight = 1;
    502         gc.fill = GridBagConstraints.NONE;
    503         gc.weightx = gc.weighty = 0.0;
    504         panelTf.add(new JLabel(tr("Offset:")), gc);
    505 
    506         long delta = Long.parseLong(Main.pref.get("geoimage.delta", "0")) / 1000;
    507         tfOffset = new JTextField();
     484        String prefTimezone = Main.pref.get("geoimage.timezone", "0:00");
     485        if (prefTimezone == null) {
     486            prefTimezone = "0:00";
     487        }
     488        try {
     489            timezone = parseTimezone(prefTimezone);
     490        } catch (ParseException e) {
     491            timezone = 0;
     492        }
     493       
     494        tfTimezone = new JTextField(10);
     495        tfTimezone.setText(formatTimezone(timezone));
     496
     497        try {
     498        delta = parseOffset(Main.pref.get("geoimage.delta", "0"));
     499        } catch (ParseException e) {
     500            delta = 0;
     501        }
     502        delta = delta / 1000;
     503       
     504        tfOffset = new JTextField(10);
    508505        tfOffset.setText(Long.toString(delta));
    509         gc.gridx = gc.gridy = 1;
    510         gc.gridwidth = gc.gridheight = 1;
    511         gc.fill = GridBagConstraints.HORIZONTAL;
    512         gc.weightx = 1.0;
    513         gc.weighty = 0.0;
    514         panelTf.add(tfOffset, gc);
    515 
    516         JButton buttonViewGpsPhoto = new JButton(tr("<html>I can take a picture of my GPS receiver.<br>"
    517                 + "Can this help?</html>"));
     506       
     507        JPanel panelBtn = new JPanel();
     508       
     509        JButton buttonViewGpsPhoto = new JButton(tr("<html>Use photo of an accurate clock,<br>"
     510                + "e.g. GPS receiver display</html>"));
     511        buttonViewGpsPhoto.setIcon(ImageProvider.get("clock"));
    518512        buttonViewGpsPhoto.addActionListener(new SetOffsetActionListener());
    519         gc.gridx = 2;
    520         gc.gridy = 0;
    521         gc.gridwidth = 1;
    522         gc.gridheight = 2;
    523         gc.fill = GridBagConstraints.BOTH;
    524         gc.weightx = 0.5;
    525         gc.weighty = 1.0;
    526         panelTf.add(buttonViewGpsPhoto, gc);
    527 
    528         gc.gridx = 0;
    529         gc.gridy = 2;
    530         gc.gridwidth = gc.gridheight = 1;
    531         gc.fill = GridBagConstraints.NONE;
    532         gc.weightx = gc.weighty = 0.0;
    533         panelTf.add(new JLabel(tr("Update position for: ")), gc);
    534 
    535         gc.gridx = 1;
    536         gc.gridy = 2;
    537         gc.gridwidth = 2;
    538         gc.gridheight = 1;
    539         gc.fill = GridBagConstraints.HORIZONTAL;
    540         gc.weightx = 1.0;
    541         gc.weighty = 0.0;
    542         rbAllImg = new JRadioButton(tr("All images"));
    543         panelTf.add(rbAllImg, gc);
    544 
    545         gc.gridx = 1;
    546         gc.gridy = 3;
    547         gc.gridwidth = 2;
    548         gc.gridheight = 1;
    549         gc.fill = GridBagConstraints.HORIZONTAL;
    550         gc.weightx = 1.0;
    551         gc.weighty = 0.0;
    552         rbNoExifImg = new JRadioButton(tr("Images with no exif position"));
    553         panelTf.add(rbNoExifImg, gc);
    554 
    555         gc.gridx = 1;
    556         gc.gridy = 4;
    557         gc.gridwidth = 2;
    558         gc.gridheight = 1;
    559         gc.fill = GridBagConstraints.HORIZONTAL;
    560         gc.weightx = 1.0;
    561         gc.weighty = 0.0;
    562         rbUntaggedImg = new JRadioButton(tr("Not yet tagged images"));
    563         panelTf.add(rbUntaggedImg, gc);
    564 
    565         gc.gridx = 0;
    566         gc.gridy = 5;
    567         gc.gridwidth = 2;
    568         gc.gridheight = 1;
    569         gc.fill = GridBagConstraints.NONE;
    570         gc.weightx = gc.weighty = 0.0;
    571         yLayer.useThumbs = Main.pref.getBoolean("geoimage.showThumbs", false);
    572         JCheckBox cbShowThumbs = new JCheckBox(tr("Show Thumbnail images on the map"), yLayer.useThumbs);
    573         panelTf.add(cbShowThumbs, gc);
    574 
    575         ButtonGroup group = new ButtonGroup();
    576         group.add(rbAllImg);
    577         group.add(rbNoExifImg);
    578         group.add(rbUntaggedImg);
    579 
    580         rbUntaggedImg.setSelected(true);
    581 
    582         panel = new JPanel();
    583         panel.setLayout(new BorderLayout());
    584 
    585         panel.add(panelCb, BorderLayout.PAGE_START);
    586         panel.add(panelTf, BorderLayout.CENTER);
    587 
    588         boolean isOk = false;
    589         GpxDataWrapper selectedGpx = null;
    590         while (! isOk) {
    591             ExtendedDialog dialog = new ExtendedDialog(
    592                     Main.parent,
    593                     tr("Correlate images with GPX track"),
    594                     new String[] { tr("Correlate"), tr("Auto-Guess"), tr("Cancel") }
    595             );
    596 
    597             dialog.setContent(panel);
    598             dialog.setButtonIcons(new String[] { "ok.png", "dialogs/geoimage/gpx2imgManual.png", "cancel.png" });
    599             dialog.showDialog();
    600             int answer = dialog.getValue();
    601             if(answer != 1 && answer != 2)
     513
     514        JButton buttonAutoGuess = new JButton(tr("Auto-Guess"));
     515        buttonAutoGuess.addActionListener(new AutoGuessActionListener());
     516
     517        JButton buttonAdjust = new JButton(tr("Manual adjust"));
     518        buttonAdjust.addActionListener(new AdjustActionListener());
     519
     520        JLabel labelPosition = new JLabel(tr("Override position for: "));
     521 
     522        int numAll = getSortedImgList(true, true).size();
     523        int numExif = numAll - getSortedImgList(false, true).size();
     524        int numTagged = numAll - getSortedImgList(true, false).size();
     525
     526        cbExifImg = new JCheckBox(tr("Images with geo location in exif data ({0}/{1})", numExif, numAll));
     527        cbExifImg.setEnabled(numExif != 0);
     528
     529        cbTaggedImg = new JCheckBox(tr("Images that are already tagged ({0}/{1})", numTagged, numAll), true);
     530        cbTaggedImg.setEnabled(numTagged != 0);
     531       
     532        labelPosition.setEnabled(cbExifImg.isEnabled() || cbTaggedImg.isEnabled());
     533
     534        boolean ticked = yLayer.thumbsLoaded || Main.pref.getBoolean("geoimage.showThumbs", false);
     535        cbShowThumbs = new JCheckBox(tr("Show Thumbnail images on the map"), ticked);
     536        cbShowThumbs.setEnabled(!yLayer.thumbsLoaded);
     537        /*cbShowThumbs.addItemListener(new ItemListener() {
     538            public void itemStateChanged(ItemEvent e) {
     539                if (e.getStateChange() == ItemEvent.SELECTED) {
     540                    yLayer.loadThumbs();
     541                } else {
     542                }       
     543            }
     544        });*/
     545
     546        int y=0;
     547        GBC gbc = GBC.eol();
     548        gbc.gridx = 0;
     549        gbc.gridy = y++;
     550        panelTf.add(panelCb, gbc);
     551       
     552       
     553        gbc = GBC.eol().fill(GBC.HORIZONTAL).insets(0,0,0,12);
     554        gbc.gridx = 0;
     555        gbc.gridy = y++;
     556        panelTf.add(new JSeparator(SwingConstants.HORIZONTAL), gbc);
     557
     558
     559        gbc = GBC.std();
     560        gbc.gridx = 0;
     561        gbc.gridy = y;
     562        panelTf.add(new JLabel(tr("Timezone: ")), gbc);
     563
     564        gbc = GBC.std().fill(GBC.HORIZONTAL);
     565        gbc.gridx = 1;
     566        gbc.gridy = y++;
     567        gbc.weightx = 1.;
     568        panelTf.add(tfTimezone, gbc);
     569
     570        gbc = GBC.std();
     571        gbc.gridx = 0;
     572        gbc.gridy = y;
     573        panelTf.add(new JLabel(tr("Offset:")), gbc);
     574
     575        gbc = GBC.std().fill(GBC.HORIZONTAL);
     576        gbc.gridx = 1;
     577        gbc.gridy = y++;
     578        gbc.weightx = 1.;
     579        panelTf.add(tfOffset, gbc);
     580       
     581        gbc = GBC.std().insets(5,5,5,5);
     582        gbc.gridx = 2;
     583        gbc.gridy = y-2;
     584        gbc.gridheight = 2;
     585        gbc.gridwidth = 2;
     586        gbc.fill = GridBagConstraints.BOTH;
     587        gbc.weightx = 0.5;
     588        panelTf.add(buttonViewGpsPhoto, gbc);
     589
     590        gbc = GBC.std().fill(GBC.BOTH).insets(5,5,5,5);
     591        gbc.gridx = 2;
     592        gbc.gridy = y++;
     593        gbc.weightx = 0.5;
     594        panelTf.add(buttonAutoGuess, gbc);
     595       
     596        gbc.gridx = 3;
     597        panelTf.add(buttonAdjust, gbc);
     598
     599        gbc = GBC.eol().fill(GBC.HORIZONTAL).insets(0,12,0,0);
     600        gbc.gridx = 0;
     601        gbc.gridy = y++;
     602        panelTf.add(new JSeparator(SwingConstants.HORIZONTAL), gbc);
     603
     604        gbc = GBC.eol();
     605        gbc.gridx = 0;
     606        gbc.gridy = y++;
     607        panelTf.add(labelPosition, gbc);
     608
     609        gbc = GBC.eol();
     610        gbc.gridx = 1;
     611        gbc.gridy = y++;
     612        panelTf.add(cbExifImg, gbc);
     613
     614        gbc = GBC.eol();
     615        gbc.gridx = 1;
     616        gbc.gridy = y++;
     617        panelTf.add(cbTaggedImg, gbc);
     618
     619        gbc = GBC.eol();
     620        gbc.gridx = 0;
     621        gbc.gridy = y++;
     622        panelTf.add(cbShowThumbs, gbc);
     623
     624        final JPanel statusBar = new JPanel();
     625        statusBar.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
     626        statusBar.setBorder(BorderFactory.createLoweredBevelBorder());
     627        statusBarText = new JLabel(" ");
     628        statusBarText.setFont(statusBarText.getFont().deriveFont(8));
     629        statusBar.add(statusBarText);
     630       
     631        statusBarListener = new StatusBarListener() {
     632            @Override
     633            public void updateStatusBar() {
     634                statusBarText.setText(statusText());
     635            }
     636            private String statusText() {
     637                try {
     638                    timezone = parseTimezone(tfTimezone.getText().trim());
     639                    delta = parseOffset(tfOffset.getText().trim());
     640                } catch (ParseException e) {
     641                     return e.getMessage();
     642                }
     643               
     644                // Construct a list of images that have a date, and sort them on the date.
     645                ArrayList<ImageEntry> dateImgLst = getSortedImgList();
     646                for (ImageEntry ie : dateImgLst) {
     647                    ie.cleanTmp();
     648                }
     649               
     650                GpxDataWrapper selGpx = selectedGPX(false);
     651                if (selGpx == null)
     652                    return tr("No gpx selected");
     653                   
     654                lastNumMatched = matchGpxTrack(dateImgLst, selGpx.data, (long) (timezone * 3600) + delta);
     655
     656                return tr("<html>Matched <b>{0}</b> of <b>{1}</b> photos to GPX track.", lastNumMatched, dateImgLst.size());
     657            }
     658        };
     659       
     660        tfTimezone.getDocument().addDocumentListener(statusBarListener);
     661        tfOffset.getDocument().addDocumentListener(statusBarListener);
     662        cbExifImg.addItemListener(statusBarListener);
     663        cbTaggedImg.addItemListener(statusBarListener);
     664       
     665        statusBarListener.updateStatusBar();
     666
     667        outerPanel = new JPanel();
     668        outerPanel.setLayout(new BorderLayout());
     669        outerPanel.add(statusBar, BorderLayout.PAGE_END);
     670
     671
     672        syncDialog = new ExtendedDialog(
     673                Main.parent,
     674                tr("Correlate images with GPX track"),
     675                new String[] { tr("Correlate"), tr("Cancel") },
     676                false
     677        );
     678        syncDialog.setContent(panelTf, false);
     679        syncDialog.setButtonIcons(new String[] { "ok.png", "cancel.png" });
     680        syncDialog.setupDialog();
     681        outerPanel.add(syncDialog.getContentPane(), BorderLayout.PAGE_START);
     682        syncDialog.setContentPane(outerPanel);
     683        syncDialog.pack();
     684        syncDialog.addWindowListener(new WindowAdapter() {
     685            final int CANCEL = -1;
     686            final int DONE = 0;
     687            final int AGAIN = 1;
     688            final int NOTHING = 2;
     689            private int checkAndSave() {
     690                if (syncDialog.isVisible()) {
     691                    // nothing happened: JOSM was minimized or similar
     692                    return NOTHING;             
     693                }
     694                int answer = syncDialog.getValue();
     695                if(answer != 1)
     696                    return CANCEL;
     697
     698                // Parse values again, to display an error if the format is not recognized
     699                try {
     700                    timezone = parseTimezone(tfTimezone.getText().trim());
     701                } catch (ParseException e) {
     702                    JOptionPane.showMessageDialog(Main.parent, e.getMessage(),
     703                            tr("Invalid timezone"), JOptionPane.ERROR_MESSAGE);
     704                    return AGAIN;
     705                }
     706               
     707                try {
     708                    delta = parseOffset(tfOffset.getText().trim());
     709                } catch (ParseException e) {
     710                        JOptionPane.showMessageDialog(Main.parent, e.getMessage(),
     711                                tr("Invalid offset"), JOptionPane.ERROR_MESSAGE);
     712                    return AGAIN;
     713                }
     714               
     715                if (lastNumMatched == 0) {
     716                    if (new ExtendedDialog(
     717                            Main.parent,
     718                            tr("Correlate images with GPX track"),
     719                            new String[] { tr("OK"), tr("Try Again") }).
     720                        setContent(tr("No images could be matched!")).
     721                        setButtonIcons(new String[] { "ok.png", "dialogs/refresh.png"}).
     722                        showDialog().getValue() == 2)
     723                            return AGAIN;
     724                }
     725                return DONE;
     726            }
     727           
     728            public void windowDeactivated(WindowEvent e) {
     729                int result = checkAndSave();
     730                switch (result) {
     731                    case NOTHING:
     732                        break;
     733                    case CANCEL:
     734                    {
     735                        for (ImageEntry ie : yLayer.data) {
     736                            ie.tmp = null;
     737                        }
     738                        yLayer.updateBufferAndRepaint();
     739                        break;
     740                    }
     741                    case AGAIN:
     742                        actionPerformed(null);
     743                        break;
     744                    case DONE:
     745                    {
     746                        Main.pref.put("geoimage.timezone", formatTimezone(timezone));
     747                        Main.pref.put("geoimage.delta", Long.toString(delta * 1000));
     748                        Main.pref.put("geoimage.showThumbs", yLayer.useThumbs);
     749
     750                        yLayer.useThumbs = cbShowThumbs.isSelected();//FIXME
     751                        yLayer.loadThumbs();
     752                       
     753                        // Search whether an other layer has yet defined some bounding box.
     754                        // If none, we'll zoom to the bounding box of the layer with the photos.
     755                        boolean boundingBoxedLayerFound = false;
     756                        for (Layer l: Main.map.mapView.getAllLayers()) {
     757                            if (l != yLayer) {
     758                                BoundingXYVisitor bbox = new BoundingXYVisitor();
     759                                l.visitBoundingBox(bbox);
     760                                if (bbox.getBounds() != null) {
     761                                    boundingBoxedLayerFound = true;
     762                                    break;
     763                                }
     764                            }
     765                        }
     766                        if (! boundingBoxedLayerFound) {
     767                            BoundingXYVisitor bbox = new BoundingXYVisitor();
     768                            yLayer.visitBoundingBox(bbox);
     769                            Main.map.mapView.recalculateCenterScale(bbox);
     770                        }
     771
     772
     773                        for (ImageEntry ie : yLayer.data) {
     774                            ie.applyTmp();
     775                        }
     776
     777                        yLayer.updateBufferAndRepaint();
     778
     779
     780                        break;
     781                    }
     782                    default:
     783                        throw new IllegalStateException();
     784                }
     785            }
     786        });
     787        syncDialog.showDialog();
     788    }
     789
     790    private static abstract class StatusBarListener implements  DocumentListener, ItemListener {
     791        public void insertUpdate(DocumentEvent ev) {
     792            updateStatusBar();
     793        }
     794        public void removeUpdate(DocumentEvent ev) {
     795            updateStatusBar();
     796        }
     797        public void changedUpdate(DocumentEvent ev) {
     798        }
     799        public void itemStateChanged(ItemEvent e) {
     800            updateStatusBar();
     801        }
     802        abstract public void updateStatusBar();
     803    }
     804
     805    /**
     806     * Presents dialog with sliders for manual adjust.
     807     */
     808    private class AdjustActionListener implements ActionListener {
     809   
     810        public void actionPerformed(ActionEvent arg0) {
     811
     812            long diff = delta + Math.round(timezone*60*60);
     813           
     814            double diffInH = (double)diff/(60*60);    // hours
     815
     816            // Find day difference
     817            final int dayOffset = (int)Math.round(diffInH / 24); // days
     818            double tmz = diff - dayOffset*24*60*60;  // seconds
     819
     820            // In hours, rounded to two decimal places
     821            tmz = (double)Math.round(tmz*100/(60*60)) / 100;
     822
     823            // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
     824            // -2 minutes offset. This determines the real timezone and finds offset.
     825            double fixTimezone = (double)Math.round(tmz * 2)/2; // hours, rounded to one decimal place
     826            int offset = (int)Math.round(diff - fixTimezone*60*60) - dayOffset*24*60*60; // seconds
     827
     828            // Info Labels
     829            final JLabel lblMatches = new JLabel();
     830
     831            // Timezone Slider
     832            // The slider allows to switch timezon from -12:00 to 12:00 in 30 minutes
     833            // steps. Therefore the range is -24 to 24.
     834            final JLabel lblTimezone = new JLabel();
     835            final JSlider sldTimezone = new JSlider(-24, 24, 0);
     836            sldTimezone.setPaintLabels(true);
     837            Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer, JLabel>();
     838            labelTable.put(-24, new JLabel("-12:00"));
     839            labelTable.put(-12, new JLabel( "-6:00"));
     840            labelTable.put(  0, new JLabel(  "0:00"));
     841            labelTable.put( 12, new JLabel(  "6:00"));
     842            labelTable.put( 24, new JLabel( "12:00"));
     843            sldTimezone.setLabelTable(labelTable);
     844
     845            // Minutes Slider
     846            final JLabel lblMinutes = new JLabel();
     847            final JSlider sldMinutes = new JSlider(-15, 15, 0);
     848            sldMinutes.setPaintLabels(true);
     849            sldMinutes.setMajorTickSpacing(5);
     850
     851            // Seconds slider
     852            final JLabel lblSeconds = new JLabel();
     853            final JSlider sldSeconds = new JSlider(-60, 60, 0);
     854            sldSeconds.setPaintLabels(true);
     855            sldSeconds.setMajorTickSpacing(30);
     856
     857            // This is called whenever one of the sliders is moved.
     858            // It updates the labels and also calls the "match photos" code
     859            class sliderListener implements ChangeListener {
     860                public void stateChanged(ChangeEvent e) {
     861                    // parse slider position into real timezone
     862                    double tz = Math.abs(sldTimezone.getValue());
     863                    String zone = tz % 2 == 0
     864                    ? (int)Math.floor(tz/2) + ":00"
     865                            : (int)Math.floor(tz/2) + ":30";
     866                    if(sldTimezone.getValue() < 0) {
     867                        zone = "-" + zone;
     868                    }
     869
     870                    lblTimezone.setText(tr("Timezone: {0}", zone));
     871                    lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue()));
     872                    lblSeconds.setText(tr("Seconds: {0}", sldSeconds.getValue()));
     873
     874                    try {
     875                        timezone = parseTimezone(zone);
     876                    } catch (ParseException pe) {
     877                        throw new RuntimeException();
     878                    }
     879                    delta = sldMinutes.getValue()*60 + sldSeconds.getValue();
     880
     881                    tfTimezone.getDocument().removeDocumentListener(statusBarListener);
     882                    tfOffset.getDocument().removeDocumentListener(statusBarListener);
     883                   
     884                    tfTimezone.setText(formatTimezone(timezone));
     885                    tfOffset.setText(Long.toString(delta + dayOffset*24*60*60));    // add the day offset to the offset field
     886
     887                    tfTimezone.getDocument().addDocumentListener(statusBarListener);
     888                    tfOffset.getDocument().addDocumentListener(statusBarListener);
     889
     890
     891
     892                    lblMatches.setText(statusBarText.getText() + tr("<br>(Time difference of {0} days)", Math.abs(dayOffset)));
     893
     894                    statusBarListener.updateStatusBar();
     895                    yLayer.updateBufferAndRepaint();
     896                }
     897            }
     898
     899            // Put everything together
     900            JPanel p = new JPanel(new GridBagLayout());
     901            p.setPreferredSize(new Dimension(400, 230));
     902            p.add(lblMatches, GBC.eol().fill());
     903            p.add(lblTimezone, GBC.eol().fill());
     904            p.add(sldTimezone, GBC.eol().fill().insets(0, 0, 0, 10));
     905            p.add(lblMinutes, GBC.eol().fill());
     906            p.add(sldMinutes, GBC.eol().fill().insets(0, 0, 0, 10));
     907            p.add(lblSeconds, GBC.eol().fill());
     908            p.add(sldSeconds, GBC.eol().fill());
     909
     910            // If there's an error in the calculation the found values
     911            // will be off range for the sliders. Catch this error
     912            // and inform the user about it.
     913            try {
     914                sldTimezone.setValue((int)(fixTimezone*2));
     915                sldMinutes.setValue(offset/60);
     916                sldSeconds.setValue(offset%60);
     917            } catch(Exception e) {
     918                JOptionPane.showMessageDialog(Main.parent,
     919                        tr("An error occurred while trying to match the photos to the GPX track."
     920                                +" You can adjust the sliders to manually match the photos."),
     921                                tr("Matching photos to track failed"),
     922                                JOptionPane.WARNING_MESSAGE);
     923            }
     924
     925            // Call the sliderListener once manually so labels get adjusted
     926            new sliderListener().stateChanged(null);
     927            // Listeners added here, otherwise it tries to match three times
     928            // (when setting the default values)
     929            sldTimezone.addChangeListener(new sliderListener());
     930            sldMinutes.addChangeListener(new sliderListener());
     931            sldSeconds.addChangeListener(new sliderListener());
     932
     933            // There is no way to cancel this dialog, all changes get applied
     934            // immediately. Therefore "Close" is marked with an "OK" icon.
     935            // Settings are only saved temporarily to the layer.
     936            new ExtendedDialog(Main.parent,
     937                    tr("Adjust timezone and offset"),
     938                    new String[] { tr("Close")}).
     939                setContent(p).setButtonIcons(new String[] {"ok.png"}).showDialog();
     940        }
     941    }
     942
     943    private class AutoGuessActionListener implements ActionListener {
     944   
     945        public void actionPerformed(ActionEvent arg0) {
     946            GpxDataWrapper gpxW = selectedGPX(true);
     947            if (gpxW == null)
    602948                return;
    603 
    604             // Check the selected values
    605             Object item = cbGpx.getSelectedItem();
    606 
    607             if (item == null || ! (item instanceof GpxDataWrapper)) {
    608                 JOptionPane.showMessageDialog(Main.parent, tr("You should select a GPX track"),
    609                         tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE );
    610                 continue;
    611             }
    612             selectedGpx = ((GpxDataWrapper) item);
    613 
    614             if (answer == 2) {
    615                 autoGuess(selectedGpx.data);
     949            GpxData gpx = gpxW.data;
     950           
     951            ArrayList<ImageEntry> imgs = getSortedImgList();
     952            PrimaryDateParser dateParser = new PrimaryDateParser();
     953
     954            // no images found, exit
     955            if(imgs.size() <= 0) {
     956                JOptionPane.showMessageDialog(Main.parent,
     957                        tr("The selected photos don't contain time information."),
     958                        tr("Photos don't contain time information"), JOptionPane.WARNING_MESSAGE);
    616959                return;
    617960            }
    618961
    619             Float timezoneValue = parseTimezone(tfTimezone.getText().trim());
    620             if (timezoneValue == null) {
    621                 JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM"),
    622                         tr("Invalid timezone"), JOptionPane.ERROR_MESSAGE);
    623                 continue;
    624             }
    625             gpstimezone = timezoneValue.floatValue();
    626 
    627             String deltaText = tfOffset.getText().trim();
    628             if (deltaText.length() > 0) {
    629                 try {
    630                     if(deltaText.startsWith("+")) {
    631                         deltaText = deltaText.substring(1);
     962            // Init variables
     963            long firstExifDate = imgs.get(0).time.getTime()/1000;
     964
     965            long firstGPXDate = -1;
     966            // Finds first GPX point
     967            outer: for (GpxTrack trk : gpx.tracks) {
     968                for (Collection<WayPoint> segment : trk.trackSegs) {
     969                    for (WayPoint curWp : segment) {
     970                        String curDateWpStr = (String) curWp.attr.get("time");
     971                        if (curDateWpStr == null) {
     972                            continue;
     973                        }
     974
     975                        try {
     976                            firstGPXDate = dateParser.parse(curDateWpStr).getTime()/1000;
     977                            break outer;
     978                        } catch(Exception e) {}
    632979                    }
    633                     delta = Long.parseLong(deltaText);
    634                 } catch(NumberFormatException nfe) {
    635                     JOptionPane.showMessageDialog(Main.parent, tr("Error while parsing offset.\nExpected format: {0}", "number"),
    636                             tr("Invalid offset"), JOptionPane.ERROR_MESSAGE);
    637                     continue;
    638                 }
    639             } else {
    640                 delta = 0;
    641             }
    642 
    643             yLayer.useThumbs = cbShowThumbs.isSelected();
    644 
    645             Main.pref.put("geoimage.doublegpstimezone", Double.toString(gpstimezone));
    646             Main.pref.put("geoimage.gpstimezone", Long.toString(- ((long) gpstimezone)));
    647             Main.pref.put("geoimage.delta", Long.toString(delta * 1000));
    648             Main.pref.put("geoimage.showThumbs", yLayer.useThumbs);
    649             isOk = true;
    650 
    651             if (yLayer.useThumbs) {
    652                 yLayer.thumbsloader = new ThumbsLoader(yLayer);
    653                 Thread t = new Thread(yLayer.thumbsloader);
    654                 t.setPriority(Thread.MIN_PRIORITY);
    655                 t.start();
    656             }
    657 
    658         }
    659 
    660         // Construct a list of images that have a date, and sort them on the date.
    661         ArrayList<ImageEntry> dateImgLst = getSortedImgList(rbAllImg.isSelected(), rbNoExifImg.isSelected());
    662 
    663         int matched = matchGpxTrack(dateImgLst, selectedGpx.data, (long) (gpstimezone * 3600) + delta);
    664 
    665         // Search whether an other layer has yet defined some bounding box.
    666         // If none, we'll zoom to the bounding box of the layer with the photos.
    667         boolean boundingBoxedLayerFound = false;
    668         for (Layer l: Main.map.mapView.getAllLayers()) {
    669             if (l != yLayer) {
    670                 BoundingXYVisitor bbox = new BoundingXYVisitor();
    671                 l.visitBoundingBox(bbox);
    672                 if (bbox.getBounds() != null) {
    673                     boundingBoxedLayerFound = true;
    674                     break;
    675                 }
    676             }
    677         }
    678         if (! boundingBoxedLayerFound) {
    679             BoundingXYVisitor bbox = new BoundingXYVisitor();
    680             yLayer.visitBoundingBox(bbox);
    681             Main.map.mapView.recalculateCenterScale(bbox);
    682         }
    683 
    684         Main.map.repaint();
    685 
    686         JOptionPane.showMessageDialog(Main.parent, tr("Found {0} matches of {1} in GPX track {2}", matched, dateImgLst.size(), selectedGpx.name),
    687                 tr("GPX Track loaded"),
    688                 ((dateImgLst.size() > 0 && matched == 0) ? JOptionPane.WARNING_MESSAGE
    689                         : JOptionPane.INFORMATION_MESSAGE));
    690 
    691     }
    692 
    693     // These variables all belong to "auto guess" but need to be accessible
    694     // from the slider change listener
    695     private int dayOffset;
    696     private JLabel lblMatches;
    697     private JLabel lblOffset;
    698     private JLabel lblTimezone;
    699     private JLabel lblMinutes;
    700     private JLabel lblSeconds;
    701     private JSlider sldTimezone;
    702     private JSlider sldMinutes;
    703     private JSlider sldSeconds;
    704     private GpxData autoGpx;
    705     private ArrayList<ImageEntry> autoImgs;
    706     private long firstGPXDate = -1;
    707     private long firstExifDate = -1;
    708 
    709     /**
    710      * Tries to automatically match opened photos to a given GPX track. Changes are applied
    711      * immediately. Presents dialog with sliders for manual adjust.
    712      * @param GpxData The GPX track to match against
    713      */
    714     private void autoGuess(GpxData gpx) {
    715         autoGpx = gpx;
    716         autoImgs = getSortedImgList(true, false);
    717         PrimaryDateParser dateParser = new PrimaryDateParser();
    718 
    719         // no images found, exit
    720         if(autoImgs.size() <= 0) {
    721             JOptionPane.showMessageDialog(Main.parent,
    722                     tr("The selected photos don't contain time information."),
    723                     tr("Photos don't contain time information"), JOptionPane.WARNING_MESSAGE);
    724             return;
    725         }
    726 
    727         ImageViewerDialog dialog = ImageViewerDialog.getInstance();
    728         dialog.showDialog();
    729         // Will show first photo if none is selected yet
    730         if(!dialog.hasImage()) {
    731             yLayer.showNextPhoto();
    732             // FIXME: If the dialog is minimized it will not be maximized. ToggleDialog is
    733             // in need of a complete re-write to allow this in a reasonable way.
    734         }
    735 
    736         // Init variables
    737         firstExifDate = autoImgs.get(0).time.getTime()/1000;
    738 
    739 
    740         // Finds first GPX point
    741         outer: for (GpxTrack trk : gpx.tracks) {
    742             for (Collection<WayPoint> segment : trk.trackSegs) {
    743                 for (WayPoint curWp : segment) {
    744                     String curDateWpStr = (String) curWp.attr.get("time");
    745                     if (curDateWpStr == null) {
    746                         continue;
    747                     }
    748 
    749                     try {
    750                         firstGPXDate = dateParser.parse(curDateWpStr).getTime()/1000;
    751                         break outer;
    752                     } catch(Exception e) {}
    753                 }
    754             }
    755         }
    756 
    757         // No GPX timestamps found, exit
    758         if(firstGPXDate < 0) {
    759             JOptionPane.showMessageDialog(Main.parent,
    760                     tr("The selected GPX track doesn't contain timestamps. Please select another one."),
    761                     tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE);
    762             return;
    763         }
    764 
    765         // seconds
    766         long diff = (yLayer.hasTimeoffset)
    767         ? yLayer.timeoffset
    768                 : firstExifDate - firstGPXDate;
    769         yLayer.timeoffset = diff;
    770         yLayer.hasTimeoffset = true;
    771 
    772         double diffInH = (double)diff/(60*60);    // hours
    773 
    774         // Find day difference
    775         dayOffset = (int)Math.round(diffInH / 24); // days
    776         double timezone = diff - dayOffset*24*60*60;  // seconds
    777 
    778         // In hours, rounded to two decimal places
    779         timezone = (double)Math.round(timezone*100/(60*60)) / 100;
    780 
    781         // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
    782         // -2 minutes offset. This determines the real timezone and finds offset.
    783         double fixTimezone = (double)Math.round(timezone * 2)/2; // hours, rounded to one decimal place
    784         int offset = (int)Math.round(diff - fixTimezone*60*60) - dayOffset*24*60*60; // seconds
    785 
    786         /*System.out.println("phto " + firstExifDate);
    787         System.out.println("gpx  " + firstGPXDate);
    788         System.out.println("diff " + diff);
    789         System.out.println("difh " + diffInH);
    790         System.out.println("days " + dayOffset);
    791         System.out.println("time " + timezone);
    792         System.out.println("fix  " + fixTimezone);
    793         System.out.println("offt " + offset);*/
    794 
    795         // This is called whenever one of the sliders is moved.
    796         // It updates the labels and also calls the "match photos" code
    797         class sliderListener implements ChangeListener {
    798             public void stateChanged(ChangeEvent e) {
    799                 // parse slider position into real timezone
    800                 double tz = Math.abs(sldTimezone.getValue());
    801                 String zone = tz % 2 == 0
    802                 ? (int)Math.floor(tz/2) + ":00"
    803                         : (int)Math.floor(tz/2) + ":30";
    804                 if(sldTimezone.getValue() < 0) {
    805                     zone = "-" + zone;
    806                 }
    807 
    808                 lblTimezone.setText(tr("Timezone: {0}", zone));
    809                 lblMinutes.setText(tr("Minutes: {0}", sldMinutes.getValue()));
    810                 lblSeconds.setText(tr("Seconds: {0}", sldSeconds.getValue()));
    811 
    812                 float gpstimezone = parseTimezone(zone).floatValue();
    813 
    814                 // Reset previous position
    815                 for(ImageEntry x : autoImgs) {
    816                     x.pos = null;
    817                 }
    818 
    819                 long timediff = (long) (gpstimezone * 3600)
    820                 + dayOffset*24*60*60
    821                 + sldMinutes.getValue()*60
    822                 + sldSeconds.getValue();
    823 
    824                 int matched = matchGpxTrack(autoImgs, autoGpx, timediff);
    825 
    826                 lblMatches.setText(
    827                         tr("Matched {0} of {1} photos to GPX track.", matched, autoImgs.size())
    828                         + ((Math.abs(dayOffset) == 0)
    829                                 ? ""
    830                                         : " " + tr("(Time difference of {0} days)", Math.abs(dayOffset))
    831                         )
    832                 );
    833 
    834                 int offset = (int)(firstGPXDate+timediff-firstExifDate);
    835                 int o = Math.abs(offset);
    836                 lblOffset.setText(
    837                         tr("Offset between track and photos: {0}m {1}s",
    838                                 (offset < 0 ? "-" : "") + Long.toString(o/60),
    839                                 Long.toString(o%60)
    840                         )
    841                 );
    842 
    843                 yLayer.timeoffset = timediff;
    844                 Main.main.map.repaint();
    845             }
    846         }
    847 
    848         // Info Labels
    849         lblMatches = new JLabel();
    850         lblOffset = new JLabel();
    851 
    852         // Timezone Slider
    853         // The slider allows to switch timezon from -12:00 to 12:00 in 30 minutes
    854         // steps. Therefore the range is -24 to 24.
    855         lblTimezone = new JLabel();
    856         sldTimezone = new JSlider(-24, 24, 0);
    857         sldTimezone.setPaintLabels(true);
    858         Hashtable<Integer,JLabel> labelTable = new Hashtable<Integer, JLabel>();
    859         labelTable.put(-24, new JLabel("-12:00"));
    860         labelTable.put(-12, new JLabel( "-6:00"));
    861         labelTable.put(  0, new JLabel(  "0:00"));
    862         labelTable.put( 12, new JLabel(  "6:00"));
    863         labelTable.put( 24, new JLabel( "12:00"));
    864         sldTimezone.setLabelTable(labelTable);
    865 
    866         // Minutes Slider
    867         lblMinutes = new JLabel();
    868         sldMinutes = new JSlider(-15, 15, 0);
    869         sldMinutes.setPaintLabels(true);
    870         sldMinutes.setMajorTickSpacing(5);
    871 
    872         // Seconds slider
    873         lblSeconds = new JLabel();
    874         sldSeconds = new JSlider(-60, 60, 0);
    875         sldSeconds.setPaintLabels(true);
    876         sldSeconds.setMajorTickSpacing(30);
    877 
    878         // Put everything together
    879         JPanel p = new JPanel(new GridBagLayout());
    880         p.setPreferredSize(new Dimension(400, 230));
    881         p.add(lblMatches, GBC.eol().fill());
    882         p.add(lblOffset, GBC.eol().fill().insets(0, 0, 0, 10));
    883         p.add(lblTimezone, GBC.eol().fill());
    884         p.add(sldTimezone, GBC.eol().fill().insets(0, 0, 0, 10));
    885         p.add(lblMinutes, GBC.eol().fill());
    886         p.add(sldMinutes, GBC.eol().fill().insets(0, 0, 0, 10));
    887         p.add(lblSeconds, GBC.eol().fill());
    888         p.add(sldSeconds, GBC.eol().fill());
    889 
    890         // If there's an error in the calculation the found values
    891         // will be off range for the sliders. Catch this error
    892         // and inform the user about it.
    893         try {
    894             sldTimezone.setValue((int)(fixTimezone*2));
    895             sldMinutes.setValue(offset/60);
    896             sldSeconds.setValue(offset%60);
    897         } catch(Exception e) {
    898             JOptionPane.showMessageDialog(Main.parent,
    899                     tr("An error occurred while trying to match the photos to the GPX track."
    900                             +" You can adjust the sliders to manually match the photos."),
    901                             tr("Matching photos to track failed"),
    902                             JOptionPane.WARNING_MESSAGE);
    903         }
    904 
    905         // Call the sliderListener once manually so labels get adjusted
    906         new sliderListener().stateChanged(null);
    907         // Listeners added here, otherwise it tries to match three times
    908         // (when setting the default values)
    909         sldTimezone.addChangeListener(new sliderListener());
    910         sldMinutes.addChangeListener(new sliderListener());
    911         sldSeconds.addChangeListener(new sliderListener());
    912 
    913         // There is no way to cancel this dialog, all changes get applied
    914         // immediately. Therefore "Close" is marked with an "OK" icon.
    915         // Settings are only saved temporarily to the layer.
    916         ExtendedDialog d = new ExtendedDialog(Main.parent,
    917                 tr("Adjust timezone and offset"),
    918                 new String[] { tr("Close"),  tr("Default Values") }
    919         );
    920 
    921         d.setContent(p);
    922         d.setButtonIcons(new String[] { "ok.png", "dialogs/refresh.png"});
    923         d.showDialog();
    924         int answer = d.getValue();
    925         // User wants default values; discard old result and re-open dialog
    926         if(answer == 2) {
    927             yLayer.hasTimeoffset = false;
    928             autoGuess(gpx);
    929         }
    930     }
    931 
     980                }
     981            }
     982
     983            // No GPX timestamps found, exit
     984            if(firstGPXDate < 0) {
     985                JOptionPane.showMessageDialog(Main.parent,
     986                        tr("The selected GPX track doesn't contain timestamps. Please select another one."),
     987                        tr("GPX Track has no time information"), JOptionPane.WARNING_MESSAGE);
     988                return;
     989            }
     990
     991            // seconds
     992            long diff = firstExifDate - firstGPXDate;
     993
     994            double diffInH = (double)diff/(60*60);    // hours
     995
     996            // Find day difference
     997            int dayOffset = (int)Math.round(diffInH / 24); // days
     998            double tz = diff - dayOffset*24*60*60;  // seconds
     999
     1000            // In hours, rounded to two decimal places
     1001            tz = (double)Math.round(tz*100/(60*60)) / 100;
     1002
     1003            // Due to imprecise clocks we might get a "+3:28" timezone, which should obviously be 3:30 with
     1004            // -2 minutes offset. This determines the real timezone and finds offset.
     1005            timezone = (double)Math.round(tz * 2)/2; // hours, rounded to one decimal place
     1006            delta = (long)Math.round(diff - timezone*60*60); // seconds
     1007
     1008            /*System.out.println("phto " + firstExifDate);
     1009            System.out.println("gpx  " + firstGPXDate);
     1010            System.out.println("diff " + diff);
     1011            System.out.println("difh " + diffInH);
     1012            System.out.println("days " + dayOffset);
     1013            System.out.println("time " + tz);
     1014            System.out.println("fix  " + timezone);
     1015            System.out.println("offt " + delta);*/
     1016
     1017            tfTimezone.getDocument().removeDocumentListener(statusBarListener);
     1018            tfOffset.getDocument().removeDocumentListener(statusBarListener);
     1019           
     1020            tfTimezone.setText(formatTimezone(timezone));
     1021            tfOffset.setText(Long.toString(delta));
     1022            tfOffset.requestFocus();
     1023
     1024            tfTimezone.getDocument().addDocumentListener(statusBarListener);
     1025            tfOffset.getDocument().addDocumentListener(statusBarListener);
     1026           
     1027            statusBarListener.updateStatusBar();
     1028            yLayer.updateBufferAndRepaint();
     1029        }
     1030    }
     1031
     1032    private ArrayList<ImageEntry>  getSortedImgList() {
     1033        return getSortedImgList(cbExifImg.isSelected(), cbTaggedImg.isSelected());
     1034    }
     1035   
    9321036    /**
    9331037     * Returns a list of images that fulfill the given criteria.
    9341038     * Default setting is to return untagged images, but may be overwritten.
    9351039     * @param boolean all -- returns all available images
    936      * @param boolean noexif -- returns untagged images without EXIF-GPS coords
     1040     * @param boolean noexif -- returns untagged images without EXIF-GPS coords
     1041     *                          this parameter is irrelevant if <code>all</code> is true
     1042     * @param boolean exif -- also returns images with exif-gps info
     1043     * @param boolean tagged -- also returns tagged images
    9371044     * @return ArrayList<ImageEntry> matching images
    9381045     */
    939     private ArrayList<ImageEntry> getSortedImgList(boolean all, boolean noexif) {
     1046    private ArrayList<ImageEntry> getSortedImgList(boolean exif, boolean tagged) {
    9401047        ArrayList<ImageEntry> dateImgLst = new ArrayList<ImageEntry>(yLayer.data.size());
    941         if (all) {
    942             for (ImageEntry e : yLayer.data) {
    943                 if (e.time != null) {
    944                     // Reset previous position
    945                     e.pos = null;
    946                     dateImgLst.add(e);
    947                 }
    948             }
    949 
    950         } else if (noexif) {
    951             for (ImageEntry e : yLayer.data) {
    952                 if (e.time != null && e.exifCoor == null) {
    953                     dateImgLst.add(e);
    954                 }
    955             }
    956 
    957         } else {
    958             for (ImageEntry e : yLayer.data) {
    959                 if (e.time != null && e.pos == null) {
    960                     dateImgLst.add(e);
    961                 }
    962             }
    963         }
    964 
     1048        for (ImageEntry e : yLayer.data) {
     1049            if (e.time == null)
     1050                continue;
     1051               
     1052            if (e.exifCoor != null) {
     1053                if (!exif)
     1054                    continue;
     1055            }
     1056               
     1057            if (e.isTagged() && e.exifCoor == null) {
     1058                if (!tagged)
     1059                    continue;
     1060            }
     1061               
     1062            dateImgLst.add(e);
     1063        }
     1064       
    9651065        Collections.sort(dateImgLst, new Comparator<ImageEntry>() {
    9661066            public int compare(ImageEntry arg0, ImageEntry arg1) {
     
    9701070
    9711071        return dateImgLst;
     1072    }
     1073
     1074    private GpxDataWrapper selectedGPX(boolean complain) {
     1075        Object item = cbGpx.getSelectedItem();
     1076
     1077        if (item == null || ! (item instanceof GpxDataWrapper)) {
     1078            if (complain) {
     1079                JOptionPane.showMessageDialog(Main.parent, tr("You should select a GPX track"),
     1080                        tr("No selected GPX track"), JOptionPane.ERROR_MESSAGE );
     1081            }
     1082            return null;
     1083        }
     1084        return (GpxDataWrapper) item;
    9721085    }
    9731086
     
    10491162            while(i >= 0 && (dateImgLst.get(i).time.getTime()/1000) <= curDateWp
    10501163                    && (dateImgLst.get(i).time.getTime()/1000) >= (curDateWp - interval)) {
    1051                 if(dateImgLst.get(i).pos == null) {
    1052                     dateImgLst.get(i).setCoor(curWp.getCoor());
    1053                     dateImgLst.get(i).speed = speed;
    1054                     dateImgLst.get(i).elevation = curElevation;
     1164                if(dateImgLst.get(i).tmp.getPos() == null) {
     1165                    dateImgLst.get(i).tmp.setCoor(curWp.getCoor());
     1166                    dateImgLst.get(i).tmp.setSpeed(speed);
     1167                    dateImgLst.get(i).tmp.setElevation(curElevation);
    10551168                    ret++;
    10561169                }
     
    10651178        while(i >= 0 && (imgDate = dateImgLst.get(i).time.getTime()/1000) >= prevDateWp) {
    10661179
    1067             if(dateImgLst.get(i).pos == null) {
     1180            if(dateImgLst.get(i).tmp.getPos() == null) {
    10681181                // The values of timeDiff are between 0 and 1, it is not seconds but a dimensionless
    10691182                // variable
    10701183                double timeDiff = (double)(imgDate - prevDateWp) / interval;
    1071                 dateImgLst.get(i).setCoor(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
    1072                 dateImgLst.get(i).speed = speed;
     1184                dateImgLst.get(i).tmp.setCoor(prevWp.getCoor().interpolate(curWp.getCoor(), timeDiff));
     1185                dateImgLst.get(i).tmp.setSpeed(speed);
    10731186
    10741187                if (curElevation != null && prevElevation != null) {
    1075                     dateImgLst.get(i).elevation = prevElevation + (curElevation - prevElevation) * timeDiff;
     1188                    dateImgLst.get(i).setElevation(prevElevation + (curElevation - prevElevation) * timeDiff);
    10761189                }
    10771190
     
    10991212        int endIndex= lstSize-1;
    11001213        while (endIndex - startIndex > 1) {
    1101             curIndex= (int) Math.round((double)(endIndex + startIndex)/2);
     1214            curIndex= (endIndex + startIndex) / 2;
    11021215            if (searchedDate > dateImgLst.get(curIndex).time.getTime()/1000) {
    11031216                startIndex= curIndex;
     
    11371250    }
    11381251
    1139     private Float parseTimezone(String timezone) {
     1252    private double parseTimezone(String timezone) throws ParseException {
     1253 
     1254        String error = tr("Error while parsing timezone.\nExpected format: {0}", "+H:MM");
     1255 
     1256 
    11401257        if (timezone.length() == 0)
    1141             return new Float(0);
     1258            return 0;
    11421259
    11431260        char sgnTimezone = '+';
     
    11501267            case ' ' :
    11511268                if (state != 2 || hTimezone.length() != 0)
    1152                     return null;
     1269                    throw new ParseException(error,0);
    11531270                break;
    11541271            case '+' :
     
    11581275                    state = 2;
    11591276                } else
    1160                     return null;
     1277                    throw new ParseException(error,0);
    11611278                break;
    11621279            case ':' :
     
    11651282                    state = 3;
    11661283                } else
    1167                     return null;
     1284                    throw new ParseException(error,0);
    11681285                break;
    11691286            case '0' : case '1' : case '2' : case '3' : case '4' :
     
    11791296                    break;
    11801297                default :
    1181                     return null;
     1298                    throw new ParseException(error,0);
    11821299                }
    11831300                break;
    11841301            default :
    1185                 return null;
     1302                throw new ParseException(error,0);
    11861303            }
    11871304        }
     
    11961313        } catch (NumberFormatException nfe) {
    11971314            // Invalid timezone
    1198             return null;
     1315            throw new ParseException(error,0);
    11991316        }
    12001317
    12011318        if (h > 12 || m > 59 )
    1202             return null;
     1319            throw new ParseException(error,0);
    12031320        else
    1204             return new Float((h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1));
     1321            return (h + m / 60.0) * (sgnTimezone == '-' ? -1 : 1);
     1322    }
     1323
     1324    private long parseOffset(String offset) throws ParseException {
     1325        String error = tr("Error while parsing offset.\nExpected format: {0}", "number");
     1326   
     1327        if (offset.length() > 0) {
     1328            try {
     1329                if(offset.startsWith("+")) {
     1330                    offset = offset.substring(1);
     1331                }
     1332                return Long.parseLong(offset);
     1333            } catch(NumberFormatException nfe) {
     1334                throw new ParseException(error,0);
     1335            }
     1336        } else {
     1337            return 0;
     1338        }
    12051339    }
    12061340}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java

    r2646 r2662  
    7979    private int currentPhoto = -1;
    8080
    81     // These are used by the auto-guess function to store the result,
    82     // so when the dialig is re-opened the users modifications don't
    83     // get overwritten
    84     public boolean hasTimeoffset = false;
    85     public long timeoffset = 0;
    86 
    8781    boolean useThumbs = false;
    8882    ThumbsLoader thumbsloader;
     83    boolean thumbsLoaded = false;
    8984    private BufferedImage offscreenBuffer;
    9085    boolean updateOffscreenBuffer = true;
    91 
    92     /*
    93      * Stores info about each image
    94      */
    95 
    96     static final class ImageEntry implements Comparable<ImageEntry> {
    97         File file;
    98         Date time;
    99         LatLon exifCoor;
    100         CachedLatLon pos;
    101         Image thumbnail;
    102         /** Speed in kilometer per second */
    103         Double speed;
    104         /** Elevation (altitude) in meters */
    105         Double elevation;
    106 
    107         public void setCoor(LatLon latlon)
    108         {
    109             pos = new CachedLatLon(latlon);
    110         }
    111         public int compareTo(ImageEntry image) {
    112             if (time != null && image.time != null)
    113                 return time.compareTo(image.time);
    114             else if (time == null && image.time == null)
    115                 return 0;
    116             else if (time == null)
    117                 return -1;
    118             else
    119                 return 1;
    120         }
    121     }
    12286
    12387    /** Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
     
    277241                    boolean noGeotagFound = true;
    278242                    for (ImageEntry e : layer.data) {
    279                         if (e.pos != null) {
     243                        if (e.getPos() != null) {
    280244                            noGeotagFound = false;
    281245                        }
     
    344308        int i = 0;
    345309        for (ImageEntry e : data)
    346             if (e.pos != null) {
     310            if (e.getPos() != null) {
    347311                i++;
    348312            }
     
    397361    private Dimension scaledDimension(Image thumb) {
    398362        final double d = Main.map.mapView.getDist100Pixel();
    399         final double size = 40 /*meter*/;     /* size of the photo on the map */
     363        final double size = 10 /*meter*/;     /* size of the photo on the map */
    400364        double s = size * 100 /*px*/ / d;
    401365
     
    441405
    442406                for (ImageEntry e : data) {
    443                     if (e.pos == null) {
     407                    if (e.getPos() == null) {
    444408                        continue;
    445409                    }
    446                     Point p = mv.getPoint(e.pos);
     410                    Point p = mv.getPoint(e.getPos());
    447411                    if (e.thumbnail != null) {
    448412                        Dimension d = scaledDimension(e.thumbnail);
     
    464428        else {
    465429            for (ImageEntry e : data) {
    466                 if (e.pos == null) {
     430                if (e.getPos() == null) {
    467431                    continue;
    468432                }
    469                 Point p = mv.getPoint(e.pos);
     433                Point p = mv.getPoint(e.getPos());
    470434                icon.paintIcon(mv, g,
    471435                        p.x - icon.getIconWidth() / 2,
     
    477441            ImageEntry e = data.get(currentPhoto);
    478442
    479             if (e.pos != null) {
    480                 Point p = mv.getPoint(e.pos);
     443            if (e.getPos() != null) {
     444                Point p = mv.getPoint(e.getPos());
    481445
    482446                if (e.thumbnail != null) {
     
    496460    public void visitBoundingBox(BoundingXYVisitor v) {
    497461        for (ImageEntry e : data) {
    498             v.visit(e.pos);
     462            v.visit(e.getPos());
    499463        }
    500464    }
     
    549513
    550514            e.setCoor(new LatLon(lat, lon));
    551             e.exifCoor = e.pos;
     515            e.exifCoor = e.getPos();
    552516
    553517        } catch (Exception p) {
    554             e.pos = null;
     518            e.exifCoor = null;
     519            e.setPos(null);
    555520        }
    556521    }
     
    614579                    new String[] {tr("Cancel"), tr("Delete")})
    615580                .setButtonIcons(new String[] {"cancel.png", "dialogs/delete.png"})
    616                 .setContent(new JLabel(tr("<html><h3>Delete the file {0}  from the disk?<p>The image file will be permanently lost!"
     581                .setContent(new JLabel(tr("<html><h3>Delete the file {0}  from disk?<p>The image file will be permanently lost!"
    617582                    ,toDelete.file.getName()), ImageProvider.get("dialogs/geoimage/deletefromdisk"),SwingConstants.LEFT))
    618583                .toggleEnable("geoimage.deleteimagefromdisk")
     
    674639                for (int i = data.size() - 1; i >= 0; --i) {
    675640                    ImageEntry e = data.get(i);
    676                     if (e.pos == null) {
     641                    if (e.getPos() == null) {
    677642                        continue;
    678643                    }
    679                     Point p = Main.map.mapView.getPoint(e.pos);
     644                    Point p = Main.map.mapView.getPoint(e.getPos());
    680645                    Rectangle r;
    681646                    if (e.thumbnail != null) {
     
    744709        }
    745710    }
     711   
     712    public void loadThumbs() {   
     713        if (useThumbs && !thumbsLoaded) {
     714            thumbsLoaded = true;
     715            thumbsloader = new ThumbsLoader(this);
     716            Thread t = new Thread(thumbsloader);
     717            t.setPriority(Thread.MIN_PRIORITY);
     718            t.start();
     719        }
     720    }
     721   
     722    public void updateBufferAndRepaint() {
     723        updateOffscreenBuffer = true;
     724        Main.map.mapView.repaint();
     725    }
    746726}
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImageViewerDialog.java

    r2627 r2662  
    2929import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
    3030import org.openstreetmap.josm.gui.dialogs.DialogsPanel.Action;
    31 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;
    3231import org.openstreetmap.josm.tools.ImageProvider;
    3332import org.openstreetmap.josm.tools.Shortcut;
     
    187186            } else if (COMMAND_CENTERVIEW.equals(action)) {
    188187                centerView = ((JToggleButton) e.getSource()).isSelected();
    189                 if (centerView && currentEntry != null && currentEntry.pos != null) {
    190                     Main.map.mapView.zoomTo(currentEntry.pos);
     188                if (centerView && currentEntry != null && currentEntry.getPos() != null) {
     189                    Main.map.mapView.zoomTo(currentEntry.getPos());
    191190                }
    192191
     
    231230//            }                     TODO: pop up image dialog but don't load image again
    232231
    233             if (centerView && Main.map != null && entry != null && entry.pos != null) {
    234                 Main.map.mapView.zoomTo(entry.pos);
     232            if (centerView && Main.map != null && entry != null && entry.getPos() != null) {
     233                Main.map.mapView.zoomTo(entry.getPos());
    235234            }
    236235
     
    243242            titleBar.setTitle("Geotagged Images" + (entry.file != null ? " - " + entry.file.getName() : ""));
    244243            StringBuffer osd = new StringBuffer(entry.file != null ? entry.file.getName() : "");
    245             if (entry.elevation != null) {
    246                 osd.append(tr("\nAltitude: {0} m", entry.elevation.longValue()));
    247             }
    248             if (entry.speed != null) {
    249                 osd.append(tr("\n{0} km/h", Math.round(entry.speed)));
     244            if (entry.getElevation() != null) {
     245                osd.append(tr("\nAltitude: {0} m", entry.getElevation().longValue()));
     246            }
     247            if (entry.getSpeed() != null) {
     248                osd.append(tr("\n{0} km/h", Math.round(entry.getSpeed())));
    250249            }
    251250            imgDisplay.setOsdText(osd.toString());
  • trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ThumbsLoader.java

    r2617 r2662  
    1717import org.openstreetmap.josm.io.CacheFiles;
    1818import org.openstreetmap.josm.Main;
    19 import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.ImageEntry;
    2019
    2120public class ThumbsLoader implements Runnable {
Note: See TracChangeset for help on using the changeset viewer.