1 package tim.prune.correlate;
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.event.ActionEvent;
8 import java.awt.event.ActionListener;
9 import java.util.Calendar;
10 import java.util.Iterator;
11 import java.util.TreeSet;
13 import javax.swing.BorderFactory;
14 import javax.swing.BoxLayout;
15 import javax.swing.ButtonGroup;
16 import javax.swing.JButton;
17 import javax.swing.JComboBox;
18 import javax.swing.JDialog;
19 import javax.swing.JLabel;
20 import javax.swing.JOptionPane;
21 import javax.swing.JPanel;
22 import javax.swing.JRadioButton;
23 import javax.swing.JScrollPane;
24 import javax.swing.JTable;
25 import javax.swing.JTextField;
28 import tim.prune.GenericFunction;
29 import tim.prune.I18nManager;
30 import tim.prune.data.DataPoint;
31 import tim.prune.data.Distance;
32 import tim.prune.data.Field;
33 import tim.prune.data.MediaObject;
34 import tim.prune.data.MediaList;
35 import tim.prune.data.TimeDifference;
36 import tim.prune.data.Timestamp;
37 import tim.prune.data.Track;
38 import tim.prune.data.Unit;
39 import tim.prune.data.UnitSetLibrary;
40 import tim.prune.tips.TipManager;
43 * Abstract superclass of the two correlator functions
45 public abstract class Correlator extends GenericFunction
47 protected JDialog _dialog;
48 private CardStack _cards = null;
49 private JTable _selectionTable = null;
50 protected JTable _previewTable = null;
51 private boolean _previewEnabled = false; // flag required to enable preview function on final panel
52 private boolean[] _cardEnabled = null; // flag for each card
53 private JTextField _offsetHourBox = null, _offsetMinBox = null, _offsetSecBox = null;
54 private JRadioButton _mediaLaterOption = null, _pointLaterOption = null;
55 private JRadioButton _timeLimitRadio = null, _distLimitRadio = null;
56 private JTextField _limitMinBox = null, _limitSecBox = null;
57 private JTextField _limitDistBox = null;
58 private JComboBox<String> _distUnitsDropdown = null;
59 private JButton _nextButton = null, _backButton = null;
60 protected JButton _okButton = null;
64 * @param inApp App object to report actions to
66 public Correlator(App inApp) {
71 * @return type key eg photo, audio
73 protected abstract String getMediaTypeKey();
78 protected abstract MediaList getMediaList();
81 * Begin the function by initialising and showing the dialog
85 // Check whether track has timestamps, exit if not
86 if (!_app.getTrackInfo().getTrack().hasData(Field.TIMESTAMP))
88 JOptionPane.showMessageDialog(_parentFrame, I18nManager.getText("dialog.correlate.notimestamps"),
89 I18nManager.getText(getNameKey()), JOptionPane.INFORMATION_MESSAGE);
92 // Show warning if no uncorrelated audios
93 if (!getMediaList().hasUncorrelatedMedia())
95 Object[] buttonTexts = {I18nManager.getText("button.continue"), I18nManager.getText("button.cancel")};
96 if (JOptionPane.showOptionDialog(_parentFrame,
97 I18nManager.getText("dialog.correlate.nouncorrelated" + getMediaTypeKey() + "s"),
98 I18nManager.getText(getNameKey()), JOptionPane.YES_NO_OPTION,
99 JOptionPane.WARNING_MESSAGE, null, buttonTexts, buttonTexts[1])
100 == JOptionPane.NO_OPTION)
105 // Create dialog if necessary
108 _dialog = new JDialog(_parentFrame, I18nManager.getText(getNameKey()), true);
109 _dialog.setLocationRelativeTo(_parentFrame);
110 _dialog.getContentPane().add(makeDialogContents());
113 // Go to first available card
116 while (!isCardEnabled(card)) {card++;}
117 _cards.showCard(card);
118 showCard(0); // does set up and next/prev enabling
119 _okButton.setEnabled(false);
120 if (!isCardEnabled(1)) {
121 _app.showTip(TipManager.Tip_ManuallyCorrelateOne);
123 _dialog.setVisible(true);
127 * Make contents of correlate dialog
128 * @return JPanel containing gui elements
130 private JPanel makeDialogContents()
132 JPanel mainPanel = new JPanel();
133 mainPanel.setLayout(new BorderLayout());
135 // Card panel in the middle
136 _cards = new CardStack();
138 // First panel (not required by photo correlator)
139 JPanel card1 = makeFirstPanel();
140 if (card1 == null) {card1 = new JPanel();}
141 _cards.addCard(card1);
143 // Second panel for selection of linked media
144 _cards.addCard(makeSecondPanel());
146 // Third panel for options and preview
147 _cards.addCard(makeThirdPanel());
148 mainPanel.add(_cards, BorderLayout.CENTER);
150 // Button panel at the bottom
151 JPanel buttonPanel = new JPanel();
152 _backButton = new JButton(I18nManager.getText("button.back"));
153 _backButton.addActionListener(new ActionListener() {
154 public void actionPerformed(ActionEvent e) {
158 _backButton.setEnabled(false);
159 buttonPanel.add(_backButton);
160 _nextButton = new JButton(I18nManager.getText("button.next"));
161 _nextButton.addActionListener(new ActionListener() {
162 public void actionPerformed(ActionEvent e) {
166 buttonPanel.add(_nextButton);
167 _okButton = new JButton(I18nManager.getText("button.ok"));
168 _okButton.addActionListener(new ActionListener()
170 public void actionPerformed(ActionEvent e)
176 _okButton.setEnabled(false);
177 buttonPanel.add(_okButton);
178 JButton cancelButton = new JButton(I18nManager.getText("button.cancel"));
179 cancelButton.addActionListener(new ActionListener() {
180 public void actionPerformed(ActionEvent e) {
184 buttonPanel.add(cancelButton);
185 mainPanel.add(buttonPanel, BorderLayout.SOUTH);
190 * Construct a table model for the photo / audio selection table
191 * @return table model
193 protected MediaSelectionTableModel makeSelectionTableModel()
195 MediaList mediaList = getMediaList();
196 MediaSelectionTableModel model = new MediaSelectionTableModel(
197 "dialog.correlate.select." + getMediaTypeKey() + "name",
198 "dialog.correlate.select." + getMediaTypeKey() + "later");
199 int numMedia = mediaList.getNumMedia();
200 for (int i=0; i<numMedia; i++)
202 MediaObject media = mediaList.getMedia(i);
203 // For working out time differences, can't use media which already had point information
204 if (media.getDataPoint() != null && media.getDataPoint().hasTimestamp()
205 && media.getOriginalStatus() == MediaObject.Status.NOT_CONNECTED)
207 // Calculate time difference, add to table model
208 long timeDiff = getMediaTimestamp(media).getSecondsSince(media.getDataPoint().getTimestamp());
209 model.addMedia(media, timeDiff);
216 * Group the two radio buttons together with a ButtonGroup
217 * @param inButton1 first radio button
218 * @param inButton2 second radio button
220 protected static void groupRadioButtons(JRadioButton inButton1, JRadioButton inButton2)
222 ButtonGroup buttonGroup = new ButtonGroup();
223 buttonGroup.add(inButton1);
224 buttonGroup.add(inButton2);
225 inButton1.setSelected(true);
230 * Try to parse the given string
231 * @param inText String to parse
232 * @return value if parseable, 0 otherwise
234 protected static int getValue(String inText)
238 value = Integer.parseInt(inText);
240 catch (NumberFormatException nfe) {}
246 * @param inFirstTimestamp timestamp of first photo / audio object, or null if not available
247 * @return time difference of local time zone from UTC when the first photo was taken
249 private static TimeDifference getTimezoneOffset(Timestamp inFirstTimestamp)
252 // Use first timestamp if available
253 if (inFirstTimestamp != null) {
254 cal = inFirstTimestamp.getCalendar();
257 // No photo or no timestamp, just use current time
258 cal = Calendar.getInstance();
260 // Both time zone offset and dst offset are based on milliseconds, so convert to seconds
261 TimeDifference timeDiff = new TimeDifference((cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / 1000);
267 * Calculate the median index to select from the table
268 * @param inModel table model
269 * @return index of entry to select from table
271 protected static int getMedianIndex(MediaSelectionTableModel inModel)
273 // make sortable list
274 TreeSet<TimeIndexPair> set = new TreeSet<TimeIndexPair>();
275 // loop through rows of table adding to list
276 int numRows = inModel.getRowCount();
278 for (i=0; i<numRows; i++)
280 MediaSelectionTableRow row = inModel.getRow(i);
281 set.add(new TimeIndexPair(row.getTimeDiff().getTotalSeconds(), i));
283 // pull out middle entry and return index
284 TimeIndexPair pair = null;
285 Iterator<TimeIndexPair> iterator = set.iterator();
286 for (i=0; i<(numRows+1)/2; i++)
288 pair = iterator.next();
290 return pair.getIndex();
295 * Disable the ok button
297 public void disableOkButton()
299 if (_okButton != null) {
300 _okButton.setEnabled(false);
305 * @return gui components for first panel, or null if empty
307 protected JPanel makeFirstPanel() {
312 * Make the second panel for the selection screen
313 * @return JPanel object containing gui elements
315 private JPanel makeSecondPanel()
317 JPanel card = new JPanel();
318 card.setLayout(new BorderLayout(10, 10));
319 JLabel introLabel = new JLabel(I18nManager.getText(
320 "dialog.correlate." + getMediaTypeKey() + "select.intro"));
321 introLabel.setBorder(BorderFactory.createEmptyBorder(6, 6, 6, 6));
322 card.add(introLabel, BorderLayout.NORTH);
323 // table doesn't have model yet - that will be attached later
324 _selectionTable = new JTable();
325 JScrollPane photoScrollPane = new JScrollPane(_selectionTable);
326 photoScrollPane.setPreferredSize(new Dimension(400, 100));
327 card.add(photoScrollPane, BorderLayout.CENTER);
333 * Make contents of third panel including options and preview
334 * @return JPanel containing gui elements
336 private JPanel makeThirdPanel()
338 OptionsChangedListener optionsChangedListener = new OptionsChangedListener(this);
339 // Second panel for options
340 JPanel card2 = new JPanel();
341 card2.setLayout(new BorderLayout());
342 JPanel card2Top = new JPanel();
343 card2Top.setLayout(new BoxLayout(card2Top, BoxLayout.Y_AXIS));
344 JLabel introLabel = new JLabel(I18nManager.getText("dialog.correlate.options.intro"));
345 introLabel.setBorder(BorderFactory.createEmptyBorder(8, 6, 5, 6));
346 card2Top.add(introLabel);
347 // time offset section
348 JPanel offsetPanel = new JPanel();
349 offsetPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.correlate.options.offsetpanel")));
350 offsetPanel.setLayout(new BoxLayout(offsetPanel, BoxLayout.Y_AXIS));
351 JPanel offsetPanelTop = new JPanel();
352 offsetPanelTop.setLayout(new FlowLayout());
353 offsetPanelTop.setBorder(null);
354 offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset") + ": "));
355 _offsetHourBox = new JTextField(3);
356 _offsetHourBox.addKeyListener(optionsChangedListener);
357 offsetPanelTop.add(_offsetHourBox);
358 offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.hours")));
359 _offsetMinBox = new JTextField(3);
360 _offsetMinBox.addKeyListener(optionsChangedListener);
361 offsetPanelTop.add(_offsetMinBox);
362 offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.minutes")));
363 _offsetSecBox = new JTextField(3);
364 _offsetSecBox.addKeyListener(optionsChangedListener);
365 offsetPanelTop.add(_offsetSecBox);
366 offsetPanelTop.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.seconds")));
367 offsetPanel.add(offsetPanelTop);
369 // radio buttons for photo / point later
370 JPanel offsetPanelBot = new JPanel();
371 offsetPanelBot.setLayout(new FlowLayout());
372 offsetPanelBot.setBorder(null);
373 _mediaLaterOption = new JRadioButton(I18nManager.getText("dialog.correlate.options." + getMediaTypeKey() + "later"));
374 _pointLaterOption = new JRadioButton(I18nManager.getText("dialog.correlate.options.pointlater" + getMediaTypeKey()));
375 _mediaLaterOption.addItemListener(optionsChangedListener);
376 _pointLaterOption.addItemListener(optionsChangedListener);
377 ButtonGroup laterGroup = new ButtonGroup();
378 laterGroup.add(_mediaLaterOption);
379 laterGroup.add(_pointLaterOption);
380 offsetPanelBot.add(_mediaLaterOption);
381 offsetPanelBot.add(_pointLaterOption);
382 offsetPanel.add(offsetPanelBot);
383 offsetPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
384 card2Top.add(offsetPanel);
386 // listener for radio buttons
387 ActionListener radioListener = new ActionListener() {
388 public void actionPerformed(ActionEvent e) {
392 // time limits section
393 JPanel limitsPanel = new JPanel();
394 limitsPanel.setBorder(BorderFactory.createTitledBorder(I18nManager.getText("dialog.correlate.options.limitspanel")));
395 limitsPanel.setLayout(new BoxLayout(limitsPanel, BoxLayout.Y_AXIS));
396 JPanel timeLimitPanel = new JPanel();
397 timeLimitPanel.setLayout(new FlowLayout());
398 JRadioButton noTimeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.notimelimit"));
399 noTimeLimitRadio.addItemListener(optionsChangedListener);
400 noTimeLimitRadio.addActionListener(radioListener);
401 timeLimitPanel.add(noTimeLimitRadio);
402 _timeLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.timelimit") + ": ");
403 _timeLimitRadio.addItemListener(optionsChangedListener);
404 _timeLimitRadio.addActionListener(radioListener);
405 timeLimitPanel.add(_timeLimitRadio);
406 groupRadioButtons(noTimeLimitRadio, _timeLimitRadio);
407 _limitMinBox = new JTextField(3);
408 _limitMinBox.addKeyListener(optionsChangedListener);
409 timeLimitPanel.add(_limitMinBox);
410 timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.minutes")));
411 _limitSecBox = new JTextField(3);
412 _limitSecBox.addKeyListener(optionsChangedListener);
413 timeLimitPanel.add(_limitSecBox);
414 timeLimitPanel.add(new JLabel(I18nManager.getText("dialog.correlate.options.offset.seconds")));
415 limitsPanel.add(timeLimitPanel);
417 JPanel distLimitPanel = new JPanel();
418 distLimitPanel.setLayout(new FlowLayout());
419 JRadioButton noDistLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.nodistancelimit"));
420 noDistLimitRadio.addItemListener(optionsChangedListener);
421 noDistLimitRadio.addActionListener(radioListener);
422 distLimitPanel.add(noDistLimitRadio);
423 _distLimitRadio = new JRadioButton(I18nManager.getText("dialog.correlate.options.distancelimit") + ": ");
424 _distLimitRadio.addItemListener(optionsChangedListener);
425 _distLimitRadio.addActionListener(radioListener);
426 distLimitPanel.add(_distLimitRadio);
427 groupRadioButtons(noDistLimitRadio, _distLimitRadio);
428 _limitDistBox = new JTextField(4);
429 _limitDistBox.addKeyListener(optionsChangedListener);
430 distLimitPanel.add(_limitDistBox);
431 String[] distUnitsOptions = {I18nManager.getText("units.kilometres"), I18nManager.getText("units.metres"),
432 I18nManager.getText("units.miles")};
433 _distUnitsDropdown = new JComboBox<String>(distUnitsOptions);
434 _distUnitsDropdown.addItemListener(optionsChangedListener);
435 distLimitPanel.add(_distUnitsDropdown);
436 limitsPanel.add(distLimitPanel);
437 limitsPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
438 card2Top.add(limitsPanel);
441 JButton previewButton = new JButton(I18nManager.getText("button.preview"));
442 previewButton.addActionListener(new ActionListener() {
443 public void actionPerformed(ActionEvent e) {
447 card2Top.add(previewButton);
448 card2.add(card2Top, BorderLayout.NORTH);
450 _previewTable = new JTable(new MediaPreviewTableModel("dialog.correlate.select." + getMediaTypeKey() + "name"));
451 JScrollPane previewScrollPane = new JScrollPane(_previewTable);
452 previewScrollPane.setPreferredSize(new Dimension(300, 100));
453 card2.add(previewScrollPane, BorderLayout.CENTER);
459 * Go to the next or previous card in the stack
460 * @param increment 1 for next, -1 for previous card
462 private void showCard(int increment)
464 int currCard = _cards.getCurrentCardIndex();
465 int next = currCard + increment;
466 if (!isCardEnabled(next)) {
470 _backButton.setEnabled(next > 0 && (isCardEnabled(next-1) || isCardEnabled(next-2)));
471 _nextButton.setEnabled(next < (_cards.getNumCards()-1));
472 _cards.showCard(next);
476 * @param inCardNum index of card
477 * @return true if specified card is enabled
479 private boolean isCardEnabled(int inCardNum)
481 if (_cardEnabled == null) {_cardEnabled = getCardEnabledFlags();}
482 return (inCardNum >= 0 && inCardNum < _cardEnabled.length && _cardEnabled[inCardNum]);
486 * @return array of boolean flags denoting availability of cards
488 protected boolean[] getCardEnabledFlags()
490 // by default first is off and third is always on; second depends on selection table
491 return new boolean[] {false, makeSelectionTableModel().getRowCount() > 0, true};
495 * Set up the specified card
496 * @param inCardNum index of card
498 protected void setupCard(int inCardNum)
500 _previewEnabled = false;
503 // set up photo selection card
504 MediaSelectionTableModel model = makeSelectionTableModel();
505 _selectionTable.setModel(model);
506 for (int i=0; i<model.getColumnCount(); i++) {
507 _selectionTable.getColumnModel().getColumn(i).setPreferredWidth(i==3?50:150);
509 // Calculate median time difference, select corresponding row of table
510 int preselectedIndex = model.getRowCount() < 3 ? 0 : getMedianIndex(model);
511 _selectionTable.getSelectionModel().setSelectionInterval(preselectedIndex, preselectedIndex);
512 _nextButton.requestFocus();
514 else if (inCardNum == 2)
516 // set up the options/preview card - first check for given time difference
517 TimeDifference timeDiff = null;
518 if (isCardEnabled(1))
520 int rowNum = _selectionTable.getSelectedRow();
521 if (rowNum < 0) {rowNum = 0;}
522 MediaSelectionTableRow selectedRow =
523 ((MediaSelectionTableModel) _selectionTable.getModel()).getRow(rowNum);
524 timeDiff = selectedRow.getTimeDiff();
526 setupPreviewCard(timeDiff, getMediaList().getMedia(0));
528 // enable ok button if any photos have been selected
529 _okButton.setEnabled(inCardNum == 2 && ((MediaPreviewTableModel) _previewTable.getModel()).hasAnySelected());
533 * Enable or disable the edit boxes according to the radio button selections
535 private void enableEditBoxes()
537 // enable/disable text field for distance input
538 _limitDistBox.setEnabled(_distLimitRadio.isSelected());
539 // and for time limits
540 _limitMinBox.setEnabled(_timeLimitRadio.isSelected());
541 _limitSecBox.setEnabled(_timeLimitRadio.isSelected());
545 * Parse the time limit values entered and validate them
546 * @return TimeDifference object describing limit
548 protected TimeDifference parseTimeLimit()
550 if (!_timeLimitRadio.isSelected()) {return null;}
551 int mins = getValue(_limitMinBox.getText());
552 _limitMinBox.setText("" + mins);
553 int secs = getValue(_limitSecBox.getText());
554 _limitSecBox.setText("" + secs);
555 if (mins <= 0 && secs <= 0) {return null;}
556 return new TimeDifference(0, mins, secs, true);
560 * Parse the distance limit value entered and validate
561 * @return angular distance in radians
563 protected double parseDistanceLimit()
566 if (_distLimitRadio.isSelected())
569 value = Double.parseDouble(_limitDistBox.getText());
571 catch (NumberFormatException nfe) {}
574 _limitDistBox.setText("0");
577 _limitDistBox.setText("" + value);
578 return Distance.convertDistanceToRadians(value, getSelectedDistanceUnits());
583 * @return the selected distance units from the dropdown
585 protected Unit getSelectedDistanceUnits()
587 final Unit[] distUnits = {UnitSetLibrary.UNITS_KILOMETRES, UnitSetLibrary.UNITS_METRES, UnitSetLibrary.UNITS_MILES};
588 return distUnits[_distUnitsDropdown.getSelectedIndex()];
592 * Create a preview of the correlate action using the selected time difference
593 * @param inFromButton true if triggered from button press, false if automatic
595 public void createPreview(boolean inFromButton)
597 // Exit if still on first panel
598 if (!_previewEnabled) {return;}
599 // Create a TimeDifference based on the edit boxes
600 int numHours = getValue(_offsetHourBox.getText());
601 int numMins = getValue(_offsetMinBox.getText());
602 int numSecs = getValue(_offsetSecBox.getText());
603 boolean isPos = _mediaLaterOption.isSelected();
604 createPreview(new TimeDifference(numHours, numMins, numSecs, isPos), inFromButton);
608 * Set up the final card using the given time difference and show it
609 * @param inTimeDiff time difference to use for time offsets
610 * @param inFirstMedia first media object to use for calculating timezone
612 protected void setupPreviewCard(TimeDifference inTimeDiff, MediaObject inFirstMedia)
614 _previewEnabled = false;
615 TimeDifference timeDiff = inTimeDiff;
616 if (timeDiff == null)
618 // No time difference available, so calculate based on computer's time zone
619 Timestamp tstamp = null;
620 if (inFirstMedia != null) {
621 tstamp = inFirstMedia.getTimestamp();
623 timeDiff = getTimezoneOffset(tstamp);
625 // Use time difference to set edit boxes
626 _offsetHourBox.setText("" + timeDiff.getNumHours());
627 _offsetMinBox.setText("" + timeDiff.getNumMinutes());
628 _offsetSecBox.setText("" + timeDiff.getNumSeconds());
629 _mediaLaterOption.setSelected(timeDiff.getIsPositive());
630 _pointLaterOption.setSelected(!timeDiff.getIsPositive());
631 _previewEnabled = true;
633 createPreview(timeDiff, true);
637 * Create a preview of the correlate action using the selected time difference
638 * @param inTimeDiff TimeDifference to use for preview
639 * @param inShowWarning true to show warning if all points out of range
641 protected abstract void createPreview(TimeDifference inTimeDiff, boolean inShowWarning);
645 * Get the timestamp of the given media
646 * @param inMedia media object
647 * @return normally just returns the media timestamp, overridden by audio correlator
649 protected Timestamp getMediaTimestamp(MediaObject inMedia)
651 return inMedia.getTimestamp();
655 * Get the point pair surrounding the given media item
656 * @param inTrack track object
657 * @param inMedia media object
658 * @param inOffset time offset to apply
659 * @return point pair resulting from correlation
661 protected PointMediaPair getPointPairForMedia(Track inTrack, MediaObject inMedia, TimeDifference inOffset)
663 PointMediaPair pair = new PointMediaPair(inMedia);
664 if (inMedia.hasTimestamp())
666 // Add/subtract offset to media timestamp
667 Timestamp mediaStamp = getMediaTimestamp(inMedia).createMinusOffset(inOffset);
668 int numPoints = inTrack.getNumPoints();
669 for (int i=0; i<numPoints; i++)
671 DataPoint point = inTrack.getPoint(i);
672 if (point.getPhoto() == null && point.getAudio() == null)
674 Timestamp pointStamp = point.getTimestamp();
675 if (pointStamp != null && pointStamp.isValid())
677 long numSeconds = pointStamp.getSecondsSince(mediaStamp);
678 pair.addPoint(point, numSeconds);
688 * Finish the correlation
690 protected abstract void finishCorrelation();
693 * Construct an array of the point pairs to use for correlation
694 * @return array of PointMediaPair objects
696 protected PointMediaPair[] getPointPairs()
698 MediaPreviewTableModel model = (MediaPreviewTableModel) _previewTable.getModel();
699 int numMedia = model.getRowCount();
700 PointMediaPair[] pairs = new PointMediaPair[numMedia];
701 // Loop over items in preview table model
702 for (int i=0; i<numMedia; i++)
704 MediaPreviewTableRow row = model.getRow(i);
705 // add all selected pairs to array (other elements remain null)
706 if (row.getCorrelateFlag().booleanValue()) {
707 pairs[i] = row.getPointPair();