PAMGuard/src/pamScrollSystem/ViewerScrollerManager.java

897 lines
28 KiB
Java
Raw Normal View History

package pamScrollSystem;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SwingWorker;
import PamController.AWTScheduler;
import PamController.PamControlledUnitSettings;
import PamController.PamController;
import PamController.PamControllerInterface;
import PamController.PamGUIManager;
import PamController.PamSettingManager;
import PamController.PamSettings;
import PamguardMVC.PamDataBlock;
import PamguardMVC.dataOffline.OfflineDataLoadInfo;
import PamguardMVC.superdet.SuperDetDataBlock;
import clickDetector.offlineFuncs.OfflineEventDataBlock;
import dataMap.DataMapControl;
import dataMap.OfflineDataMap;
import detectiongrouplocaliser.DetectionGroupDataBlock;
import generalDatabase.SQLLogging;
import generalDatabase.SuperDetLogging;
import offlineProcessing.superdet.OfflineSuperDetFilter;
import pamViewFX.pamTask.PamTaskUpdate;
public class ViewerScrollerManager extends AbstractScrollManager implements PamSettings {
private boolean initialisationComplete;
private boolean intialiseLoadDone;
private StoredScrollerData oldScrollerData = new StoredScrollerData();
private boolean currentScrollInitialisation;
public ViewerScrollerManager() {
PamSettingManager.getInstance().registerSettings(this);
}
/* (non-Javadoc)
* @see pamScrollSystem.AbstractScrollManager#addPamScroller(pamScrollSystem.AbstractPamScroller)
*/
@Override
public void addPamScroller(AbstractPamScroller pamScroller) {
super.addPamScroller(pamScroller);
if (oldScrollerData != null && initialisationComplete) {
PamScrollerData oldData = oldScrollerData.findScrollerData(pamScroller);
if (oldData != null) {
pamScroller.scrollerData = oldData.clone();
}
}
}
@Override
public void moveInnerScroller(AbstractPamScroller scroller, long newValue) {
//Debug.out.println("Move inner scroller " + newValue);
AbstractPamScroller aScroller;
if (oldScrollerData.coupleAllScrollers) {
followCoupledScroller(scroller);
}
for (int i = 0; i < pamScrollers.size(); i++) {
aScroller = pamScrollers.get(i);
if (aScroller == scroller) {
continue;
}
aScroller.anotherScrollerMovedInner(newValue);
}
}
@Override
public void moveOuterScroller(AbstractPamScroller scroller, long newMin,
long newMax) {
//Debug.out.println("Move outer scroller " + newMin + " " + newMax);
AbstractPamScroller aScroller;
for (int i = 0; i < pamScrollers.size(); i++) {
aScroller = pamScrollers.get(i);
if (aScroller == scroller) {
continue;
}
aScroller.anotherScrollerMovedOuter(newMin, newMax);
}
if (currentScrollInitialisation == false) {
loadData(false);
}
}
private volatile boolean loaderRunning = false;
private DataLoader dataLoader;
@Override
public void reLoad() {
loadData(false);
}
/**
* Wait for the data loader to complete.
*
* @param timeOut maximum time to wait for in milliseconds. Enter 0 to wait forever.
* @return true if data loader is not running or stops, false if
* a timeout occurs.
*/
// public boolean waitForLoader(long timeOut) {
// long now = System.currentTimeMillis();
// try {
// while (loaderRunning) {
// Thread.sleep(10);
// if (timeOut > 0) {
// if (System.currentTimeMillis() > now+timeOut) {
// break;
// }
// }
// }
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// return (!loaderRunning);
// }
/**
* loads data after a scroller has changed.
* @param immediateLoad
*/
public synchronized void loadData(boolean immediateLoad) {
//Debug.out.println("Viewer scroll manager: " + immediateLoad);
/**
* will need to stop this getting called multiple times
* when several scroll bars move.
*/
if (loaderRunning) {
// return;
// if (dataLoader != null) {
// return;
// }
// dataLoader.cancel(true);
}
// checks what everyone wants and needs, then loads the
// appropriate data.
/*
* first loop through data blocks, then for
* every data block loop through all scrollers and
* for srollers which use that data block work out
* the min and max times for data in that block then
* load the data.
*/
ArrayList<PamDataBlock> dataBlocks = PamController.getInstance().getDataBlocks();
ArrayList<DataLoadQueData> dataLoadQueue = new ArrayList<DataLoadQueData>();
// dataLoadQueu.clear();
for (int i = 0; i < dataBlocks.size(); i++) {
checkLoadLimits(dataLoadQueue, dataBlocks.get(i));
}
if (dataLoadQueue.size() > 0) {
loaderRunning = true;
if (immediateLoad) {
loadDataQueue(dataLoadQueue);
}
else {
scheduleDataQueue(dataLoadQueue);
}
}
}
/**
* Load the data queue immediately in the current thread.
* @param dataLoadQueue
*/
private void loadDataQueue(ArrayList<DataLoadQueData> dataLoadQueue) {
int n = dataLoadQueue.size();
for (int i = 0; i < n; i++) {
loadDataQueueItem(dataLoadQueue.get(i), i, null);
}
loadSubformData(dataLoadQueue, null);
loadDone();
}
/**
* Load the data from a single object in the queue.
* Generally called from within the worker thread.
* Note that any data units with a starting load time of <=0 are simply not loaded. This is to prevent potential issues
* later with algorithms not expecting a negative date; however, this also means that PAMGuard cannot process pre-1970 data.
* @param dataLoadQueData
* @param i
*/
private void loadDataQueueItem(DataLoadQueData dataLoadQueData, int queuePosition, ViewLoadObserver loadObserver) {
PamDataBlock dataBlock = dataLoadQueData.getPamDataBlock();
if (dataBlock instanceof OfflineEventDataBlock) {
System.out.println(dataBlock);
}
// 2019-11-12 add a check of the counts to this 'if' statement as well. If the data start time is negative
// (which can indicate that this is a 'special' data block and we should be loading all of it rather than just
// what is within the scroller time range) and we haven't loaded anything yet, go ahead and load. But if there
// are data units, it means the info has already been loaded so don't bother loading again.
// 2020-01-28 this fixed the problem with DetectionGroupLocaliser data not getting loaded, but Streamer data is still broken.
// Correct this by changing the default min time from Long.MIN_VALUE to 1 in AbstractScrollManager.getSpecialLoadTimes. This
// affects DetectionGroupLocaliser, StreamerData, AcquisitionProcess and IMUProcess (anything that uses the AbstractScrollManager.SpecialDataBlockInfo
// class without explicitly specifying time limits
if ((dataLoadQueData.getDataStart() <= 0 || dataLoadQueData.getDataStart() == Long.MAX_VALUE)
&& dataBlock.getUnitsCount() > 0) {
return;
}
dataLoadQueData.getPamDataBlock().loadViewerData(new OfflineDataLoadInfo(dataLoadQueData.getDataStart(),
dataLoadQueData.getDataEnd()), loadObserver);
}
/**
* Schedule the data queue to be loaded in a separate thread.
* @param dataLoadQueue
*/
private void scheduleDataQueue(ArrayList<DataLoadQueData> dataLoadQueue) {
AWTScheduler.getInstance().scheduleTask(dataLoader = new DataLoader(dataLoadQueue));
}
/**
* Go through the list of loaded data blocks and see if there are any that have a subform
*
* @param dataLoadQueue
* @param dataLoader
*/
private void loadSubformData(ArrayList<DataLoadQueData> dataLoadQueue, DataLoader dataLoader) {
for (int i=0; i<dataLoadQueue.size(); i++) {
PamDataBlock dataBlock = dataLoadQueue.get(i).getPamDataBlock();
if (dataBlock instanceof SuperDetDataBlock == false) {
continue;
}
// System.out.println("Load subform data for datablock " + i + ": " + dataBlock.getLongDataName());
if (dataLoader != null) {
LoadQueueProgressData lpd = new LoadQueueProgressData("Database",
dataBlock.getDataName(),
dataLoadQueue.size(), i, LoadQueueProgressData.STATE_LINKINGSUBTABLE, 0, 0, 0, 0);
dataLoader.pubPublish(lpd);
}
((SuperDetDataBlock) dataBlock).reattachSubdetections(dataLoader);
// System.out.println("Completed subform data for datablock " + i + ": " + dataBlock.getLongDataName());
}
}
private void loadDone() {
for (int i = 0; i < pamScrollers.size(); i++) {
pamScrollers.get(i).notifyRangeChange();
}
PamController.getInstance().notifyModelChanged(PamControllerInterface.OFFLINE_DATA_LOADED);
}
/**
* Check the required load limits for a data block which
* may be being used my multiple scrollers.
*
* @param pamDataBlock data block to check
*/
private void checkLoadLimits(ArrayList<DataLoadQueData> dataLoadQueue, PamDataBlock pamDataBlock) {
long minTime = Long.MAX_VALUE;
long maxTime = Long.MIN_VALUE;
AbstractPamScroller aScroller;
boolean used = false;
for (int i = 0; i < this.pamScrollers.size(); i++) {
aScroller = pamScrollers.get(i);
if (aScroller.isDataBlockUsed(pamDataBlock) || aScroller.isInSpecialList(pamDataBlock)) {
long[] loadTimes = aScroller.getSpecialLoadTimes(pamDataBlock, aScroller.getMinimumMillis(), aScroller.getMaximumMillis());
/*
* Needed to change the logic here, since was going wrong if one scroller had a shorter load time.
* Now a straight forward 3-way maximum.
*/
if (loadTimes != null) {
minTime = Math.min(minTime, loadTimes[0]);
maxTime = Math.max(maxTime, loadTimes[1]);
}
minTime = Math.min(minTime, aScroller.getMinimumMillis());
maxTime = Math.max(maxTime, aScroller.getMaximumMillis());
used = true;
}
}
if (pamDataBlock.getDataName().contains("Gemini Target") ) {
System.out.println("Gemini Tracks");
}
if (used) {
// dataLoadQueue.add(new DataLoadQueData(pamDataBlock, minTime, maxTime, hasSubFormData(pamDataBlock)));
addToDataQueue(dataLoadQueue, pamDataBlock, minTime, maxTime, hasSubFormData(pamDataBlock));
/*
* Also need to do this for any super detection datablock(s), which may or may not already
* be in the dataloadQueue, and may or may not be associated with this scroller. Also note that a superblock
* may already be in the load que, in which case AND the times.
*/
ArrayList<SuperDetDataBlock> superBlocks = OfflineSuperDetFilter.findPossibleSuperDetections(pamDataBlock);
for (PamDataBlock aBlock : superBlocks) {
addToDataQueue(dataLoadQueue, pamDataBlock, minTime, maxTime, true);
// DataLoadQueData exItem = findLoadQueueData(dataLoadQueue, aBlock);
// if (exItem != null) {
// // expand an existing item as necessary
// exItem.setDataStart(Math.min(exItem.getDataStart(), minTime));
// exItem.setDataEnd(Math.max(exItem.getDataEnd(), maxTime));
// }
// else {
// // or add a new item.
// dataLoadQueue.add(new DataLoadQueData(aBlock, minTime, maxTime, true));
// }
}
}
}
/**
* Add to data queue, being aware that the block may already be in the queue if it was included from a sub detections super
* detection list.
* @param dataLoadQueue
* @param pamDataBlock
* @param minTime
* @param maxTime
* @param hasSubData
*/
private void addToDataQueue(ArrayList<DataLoadQueData> dataLoadQueue, PamDataBlock pamDataBlock, long minTime, long maxTime, boolean hasSubData) {
DataLoadQueData exItem = findLoadQueueData(dataLoadQueue, pamDataBlock);
if (exItem != null) {
// expand an existing item as necessary
exItem.setDataStart(Math.min(exItem.getDataStart(), minTime));
exItem.setDataEnd(Math.max(exItem.getDataEnd(), maxTime));
exItem.setHasSubTable(true);
}
else {
// or add a new item.
dataLoadQueue.add(new DataLoadQueData(pamDataBlock, minTime, maxTime, true));
}
}
/**
* Find an existing entry for a data block in the load queue
* @param loadQueue
* @param dataBlock
* @return
*/
private DataLoadQueData findLoadQueueData(ArrayList<DataLoadQueData> loadQueue, PamDataBlock dataBlock) {
for (DataLoadQueData dlqd : loadQueue) {
if (dlqd.getPamDataBlock() == dataBlock) {
return dlqd;
}
}
return null;
}
/**
*
* @param dataBlock
* @return true if a datablock has subform data.
*/
private boolean hasSubFormData(PamDataBlock dataBlock) {
SQLLogging logging = dataBlock.getLogging();
if (logging == null) {
return false;
}
return (logging instanceof SuperDetLogging);
}
class DataLoader extends SwingWorker<Integer, LoadQueueProgressData> implements ViewLoadObserver{
private ArrayList<DataLoadQueData> dataLoadQueue;
private LoadingDataDialog loadingDataDialog;
private volatile boolean emergencyStop = false;
private volatile String currentDataName;
private volatile String storeName;
public DataLoader(ArrayList<DataLoadQueData> dataLoadQueue) {
super();
// System.out.printf("Makeing dataloader with %d items, inicomplete = %s\n",
// dataLoadQueue.size(),
// new Boolean(PamController.getInstance().isInitializationComplete()));
this.dataLoadQueue = dataLoadQueue;
}
@Override
protected Integer doInBackground() throws Exception {
try {
int nDone = 0;
/**
* First load all the data which has subtable detections
*/
for (int i = 0; i < dataLoadQueue.size(); i++) {
DataLoadQueData queueItem = dataLoadQueue.get(i);
if (queueItem.isHasSubTable() == false) {
continue;
}
OfflineDataMap priMap = dataLoadQueue.get(i).getPamDataBlock().getPrimaryDataMap();
if (priMap != null) {
storeName = priMap.getDataMapName();
}
else {
storeName = null;
}
currentDataName = dataLoadQueue.get(i).getPamDataBlock().getDataName();
LoadQueueProgressData lpd = new LoadQueueProgressData(storeName,
currentDataName,
dataLoadQueue.size(), nDone++, 0, 0, 0, 0, 0);
publish(lpd);
loadDataQueueItem(queueItem, i, this);
/**
* Now go through every item which was loaded so far and
* then check to see that any required sub detections are
* about to be loaded.
*/
// ArrayList subTableData = queueItem.getPamDataBlock().getSubtableData();
/*
* Need to
* 1. Get a list of datablock,
* 2. Find if they are in the rest of the queueItem list
* 3. If so, then check load limits are the same
* 4. If not, then add to the list before proceeding below.
* All very complicated !!!!!!!!
*/
}
/**
*
* Then load all the data that doesn't have sub table detections.
*/
for (int i = 0; i < dataLoadQueue.size(); i++) {
DataLoadQueData queueItem = dataLoadQueue.get(i);
if (queueItem.isHasSubTable() == true) {
continue;
}
LoadQueueProgressData lpd = new LoadQueueProgressData(storeName,
dataLoadQueue.get(i).getPamDataBlock().getDataName(),
dataLoadQueue.size(), nDone++, 0, 0, 0, 0, 0);
publish(lpd);
loadDataQueueItem(queueItem, i, this);
}
loadSubformData(dataLoadQueue, this);
// need to set this here so that waitForLoader() can execute in the AWT thread.
loaderRunning = false;
}
catch (Exception e){
System.out.println("Error in Viewer Scroller data loader " + e.getMessage());
e.printStackTrace();
}
LoadQueueProgressData lpd = new LoadQueueProgressData(storeName,
"Data Load Complete, updating displays",
dataLoadQueue.size(), dataLoadQueue.size(), 0, 0, 0, 0, 0);
publish(lpd);
PamController.getInstance().notifyModelChanged(PamControllerInterface.DATA_LOAD_COMPLETE);
return null;
}
public void pubPublish(LoadQueueProgressData loadQueueProgressData) {
publish(loadQueueProgressData);
}
@Override
protected void done() {
super.done();
loaderRunning = false;
if (PamGUIManager.isSwing()) {
if (loadingDataDialog != null) {
loadingDataDialog.closeLater();
// loadingDataDialog.setVisible(false);
}
}
loadDone();
PamController.getInstance().notifyTaskProgress(new LoadQueueProgressData(PamTaskUpdate.STATUS_DONE));
}
@Override
protected void process(List<LoadQueueProgressData> chunks) {
if (PamGUIManager.isSwing()) {
if (loadingDataDialog == null) {
loadingDataDialog = LoadingDataDialog.showDialog(PamController.getMainFrame());
}
if (loadingDataDialog != null) {
for (LoadQueueProgressData lpd:chunks) {
loadingDataDialog.setData(lpd);
}
emergencyStop = loadingDataDialog.shouldStop();
}
}
else {
for (LoadQueueProgressData lpd:chunks) {
PamController.getInstance().notifyTaskProgress(lpd);
}
//TODO- how to return an emergency stop form seperate GUI?
}
}
@Override
public void sayProgress(int state, long loadStart, long loadEnd,
long lastTime, int nLoaded) {
LoadQueueProgressData lpd = new LoadQueueProgressData(storeName,
currentDataName, 0, 0, state, loadStart, loadEnd, lastTime, nLoaded);
publish(lpd);
}
/* (non-Javadoc)
* @see pamScrollSystem.ViewLoadObserver#cancelLoad()
*/
@Override
public boolean cancelLoad() {
return emergencyStop ;
}
}
private DataMapControl findDataMapControl() {
return DataMapControl.getDataMapControl();
}
@Override
public long checkMaximumTime(long requestedTime) {
DataMapControl dmc = findDataMapControl();
if (dmc == null) {
return 0;
}
return Math.min(dmc.getLastTime(), requestedTime);
}
@Override
public long checkMinimumTime(long requestedTime) {
DataMapControl dmc = findDataMapControl();
if (dmc == null) {
return 0;
}
return Math.max(dmc.getFirstTime(), requestedTime);
}
@Override
public void notifyModelChanged(int changeType) {
switch (changeType) {
case PamControllerInterface.INITIALIZATION_COMPLETE:
initialisationComplete = true;
break;
case PamControllerInterface.INITIALIZE_LOADDATA:
intialiseLoadDone = true;
case PamControllerInterface.CHANGED_OFFLINE_DATASTORE:
case PamControllerInterface.ADD_CONTROLLEDUNIT:
case PamControllerInterface.REMOVE_CONTROLLEDUNIT:
if (initialisationComplete && intialiseLoadDone) {
initialiseScrollers();
}
break;
}
}
/**
* Called once at the start, and possibly after
* any changes to the database or binary store.
* Initialises scroll bars and calls for a data load.
*/
private void initialiseScrollers() {
DataMapControl dmc = findDataMapControl();
if (dmc == null) {
return;
}
currentScrollInitialisation = true;
AbstractPamScroller aScroller;
PamScrollerData oldData;
for (int i = 0; i < pamScrollers.size(); i++) {
aScroller = pamScrollers.get(i);
if (oldScrollerData != null) {
oldData = oldScrollerData.findScrollerData(aScroller);
}
else {
oldData = null;
}
if (oldData == null) {
aScroller.setRangeMillis(dmc.getFirstTime(),
dmc.getFirstTime() + aScroller.getDefaultLoadtime(), true);
}
else {
aScroller.scrollerData = oldData.clone();
aScroller.rangesChanged(0);
}
}
currentScrollInitialisation = false;
loadData(false);
}
@Override
public Serializable getSettingsReference() {
StoredScrollerData sd = new StoredScrollerData();
for (int i = 0; i < pamScrollers.size(); i++) {
sd.addScrollerData(pamScrollers.get(i));
}
return sd;
}
@Override
public long getSettingsVersion() {
return StoredScrollerData.serialVersionUID;
}
@Override
public String getUnitName() {
return "Viewer Scroll Manager";
}
@Override
public String getUnitType() {
return "Viewer Scroll Manager";
}
@Override
public boolean restoreSettings(
PamControlledUnitSettings pamControlledUnitSettings) {
oldScrollerData = (StoredScrollerData) pamControlledUnitSettings.getSettings();
return true;
}
@Override
public void centreDataAt(PamDataBlock dataBlock, long menuMouseTime) {
// centre all scroll bars as close to the above as is possible.
AbstractPamScroller aScroller;
long scrollRange, newMax, newMin;
for (int i = 0; i < pamScrollers.size(); i++) {
aScroller = pamScrollers.get(i);
scrollRange = aScroller.getMaximumMillis() - aScroller.getMinimumMillis();
newMin = checkMinimumTime(menuMouseTime - scrollRange / 2);
newMax = checkMaximumTime(newMin + scrollRange);
newMin = newMax-scrollRange;
newMin = checkGapPos(dataBlock, newMin, newMax);
newMax = newMin + scrollRange;
aScroller.setRangeMillis(newMin, newMax, false);
// aScroller.setValueMillis(menuMouseTime - scrollRange/2);
}
loadData(false);
}
@Override
public void startDataAt(PamDataBlock dataBlock, long menuMouseTime, boolean immediateLoad) {
AbstractPamScroller aScroller;
long scrollRange, newMax, newMin;
for (int i = 0; i < pamScrollers.size(); i++) {
aScroller = pamScrollers.get(i);
scrollRange = aScroller.getMaximumMillis() - aScroller.getMinimumMillis();
newMin = checkMinimumTime(menuMouseTime);
newMax = checkMaximumTime(newMin + scrollRange);
newMin = newMax-scrollRange;
newMin = checkGapPos(dataBlock, newMin, newMax);
newMax = newMin + scrollRange;
aScroller.setRangeMillis(newMin, newMax, false);
}
loadData(immediateLoad);
}
@Override
public JPopupMenu getStandardOptionsMenu(AbstractPamScroller pamScroller) {
JPopupMenu menu = new JPopupMenu();
JCheckBoxMenuItem cbItem = new JCheckBoxMenuItem("Couple all scrollers");
cbItem.setSelected(oldScrollerData.coupleAllScrollers);
cbItem.addActionListener(new CoupleScrollersMenuItem(pamScroller, oldScrollerData.coupleAllScrollers));
menu.add(cbItem);
return menu;
}
class CoupleScrollersMenuItem implements ActionListener {
private boolean currentState;
private AbstractPamScroller pamScroller;
public CoupleScrollersMenuItem(AbstractPamScroller pamScroller, boolean currentState) {
super();
this.currentState = currentState;
this.pamScroller = pamScroller;
}
@Override
public void actionPerformed(ActionEvent arg0) {
setCoupleAllScrollers(pamScroller, !currentState);
}
}
private void setCoupleAllScrollers(AbstractPamScroller pamScroller, boolean newState) {
oldScrollerData.coupleAllScrollers = newState;
if (newState) {
followCoupledScroller(pamScroller);
}
}
private void followCoupledScroller(AbstractPamScroller pamScroller) {
AbstractPamScroller aScroller;
long value = pamScroller.getValueMillis();
for (int i = 0; i < pamScrollers.size(); i++) {
aScroller = pamScrollers.get(i);
if (aScroller == pamScroller) {
continue;
}
/*
* Can end up with some horrible feedback here if two
* coupled scrollers bring in rounding errors and start
* to oscillate each other.
*/
long currentVal = aScroller.getValueMillis();
long change = Math.abs(value-currentVal);
long range = aScroller.getMaximumMillis() - aScroller.getMinimumMillis();
if (range == 0) {
aScroller.setValueMillis(aScroller.getMinimumMillis());
continue;
}
double fracChange = (double) change / (double) range;
if (fracChange <= .0001) {
continue;
}
aScroller.setValueMillis(value);
}
}
@Override
public long checkGapPos(AbstractPamScroller scroller,
long oldMin, long oldMax, long newMin, long newMax, int direction) {
int nDataBlocks = scroller.getNumUsedDataBlocks();
if (nDataBlocks == 0) {
return newMin;
}
int oldMinGap = 0, oldMaxGap = 0, newMinGap = 0, newMaxGap = 0;
PamDataBlock dataBlock;
long range = oldMax-oldMin;
/*
* Get a series of flags for all four positions indicating
* exactly where the data are / want to be relative to
* gaps in the datamap.
* These are defined in OfflineDataMap
* <p>NO_DATA
* <p>BEFORE_FIRST
* <p>AFTER_LAST
* <p>POINT_START
* <p>POINT_END
* <P>IN_GAP
*/
for (int i = 0; i < nDataBlocks; i++) {
dataBlock = scroller.getUsedDataBlock(i);
oldMinGap |= isInGap(dataBlock, oldMin);
oldMaxGap |= isInGap(dataBlock, oldMax);
newMinGap |= isInGap(dataBlock, newMin);
newMaxGap |= isInGap(dataBlock, newMax);
}
/**
* Can now think about what to do based on gap status
* of the four times.
*/
if ((newMinGap & OfflineDataMap.IN_DATA) > 0 && (newMaxGap & OfflineDataMap.IN_DATA) > 0) {
return newMin;
}
long newStart;
if (direction > 0) { // going forward.
/*
* if the old start was in a gap, move so the new start is at the edge of
* the next data.
*/
if ((oldMinGap == OfflineDataMap.IN_GAP)) {
newStart = getNextDataStart(scroller, oldMin);
if (newStart != Long.MAX_VALUE) {
return newStart;
}
}
/*
* If the old end was on the end of a point, then jump so that the new start
* is at the start of the next map point AFTER the old end.
*/
if ((oldMaxGap & OfflineDataMap.POINT_END) != 0) {
newStart = getNextDataStart(scroller, oldMax);
if (newStart != Long.MAX_VALUE) {
return newStart;
}
}
/*
* If the old end wasn't in gap and the new end is in a gap, then align the
* data so that the end is on the end of the data.
*/
if ((oldMaxGap & OfflineDataMap.IN_DATA) != 0 &&
(newMaxGap == OfflineDataMap.IN_GAP)) {
newStart = getPrevDataEnd(scroller, newMax);
if (newStart != Long.MAX_VALUE) {
return newStart - range;
}
}
}
else if (direction < 0) { // going backwards.
/*
* if the old end was in a gap, move so the new end is at the edge of
* the previous data.
*/
if ((oldMaxGap == OfflineDataMap.IN_GAP)) {
newStart = getPrevDataEnd(scroller, oldMax);
if (newStart != Long.MIN_VALUE) {
return newStart-range;
}
}
/*
* If the old start was on the start of a point, then jump so that the new end
* is at the end of the previous map point BEFORE the old start.
*/
if ((oldMinGap & OfflineDataMap.POINT_START) != 0) {
newStart = getPrevDataEnd(scroller, oldMin);
if (newStart != Long.MIN_VALUE) {
return newStart-range;
}
}
/*
* If the old start wasn't in gap and the new start is in a gap, then align the
* data so that the start is on the start of the data.
*/
if ((oldMinGap & OfflineDataMap.IN_DATA) != 0 &&
(newMinGap == OfflineDataMap.IN_GAP)) {
newStart = getNextDataStart(scroller, newMin);
if (newStart != Long.MAX_VALUE) {
return newStart;
}
}
}
return newMin;
}
/**
* Very similar to the function checking gap pos for a whole scroller,
* but only does it for one datablock.
* @param dataBlock data block
* @param newMin new min time
* @param newMax new max time
* @return adjusted new min time
*/
private long checkGapPos(PamDataBlock dataBlock, long newMin, long newMax) {
int newMinGap = 0, newMaxGap = 0;
newMinGap |= isInGap(dataBlock, newMin);
newMaxGap |= isInGap(dataBlock, newMax);
long newT;
if ((newMinGap & OfflineDataMap.IN_DATA) > 0 && (newMaxGap & OfflineDataMap.IN_DATA) > 0) {
return newMin;
}
else if (newMinGap == OfflineDataMap.IN_GAP) {
/*
* Move forward
*/
newT = dataBlock.getNextDataStart(newMin);
if (newT > 0xFF) {
return newT;
}
}
else if (newMinGap == OfflineDataMap.IN_DATA && newMaxGap == OfflineDataMap.IN_GAP) {
/**
* Move back a bit, so end of data aligns.
*/
newT = dataBlock.getPrevDataEnd(newMax);
if (newT > 0xFF) {
return newT - (newMax-newMin);
}
}
return newMin;
}
public long getNextDataStart(AbstractPamScroller scroller, long timeMillis) {
long time = Long.MAX_VALUE;
long t;
for (int i = 0; i < scroller.getNumUsedDataBlocks(); i++) {
t = scroller.getUsedDataBlock(i).getNextDataStart(timeMillis);
if (t > 0xFF) {
time = Math.min(time, t);
}
}
return time;
}
public long getPrevDataEnd(AbstractPamScroller scroller, long timeMillis) {
long time = Long.MIN_VALUE;
long t;
for (int i = 0; i < scroller.getNumUsedDataBlocks(); i++) {
t = scroller.getUsedDataBlock(i).getPrevDataEnd(timeMillis);
if (t > 0xFF) {
time = Math.max(time, t);
}
}
return time;
}
}