package com.dreikraft.axbo.controller;
import apple.dts.samplecode.osxadapter.OSXAdapter;
import com.dreikraft.events.ApplicationEventDispatcher;
import com.dreikraft.events.ApplicationEventEnabled;
import com.dreikraft.events.ApplicationExit;
import com.dreikraft.events.ApplicationInitialize;
import com.dreikraft.events.ApplicationInitialized;
import com.dreikraft.events.ApplicationMessageEvent;
import com.dreikraft.axbo.Axbo;
import com.dreikraft.axbo.OS;
import com.dreikraft.axbo.data.AxboInfo;
import com.dreikraft.axbo.data.DeviceContext;
import com.dreikraft.axbo.data.SleepData;
import com.dreikraft.axbo.data.SleepDataComparator;
import com.dreikraft.axbo.events.AxboClear;
import com.dreikraft.axbo.events.AxboDisconnect;
import com.dreikraft.axbo.events.AxboFind;
import com.dreikraft.axbo.events.AxboFound;
import com.dreikraft.axbo.events.AxboReset;
import com.dreikraft.axbo.events.AxboStatusGet;
import com.dreikraft.axbo.events.AxboStatusGot;
import com.dreikraft.axbo.events.AxboTest;
import com.dreikraft.axbo.events.AxboTimeSet;
import com.dreikraft.axbo.events.DataSearch;
import com.dreikraft.axbo.events.DiagramClose;
import com.dreikraft.axbo.events.DiagramZoom;
import com.dreikraft.axbo.events.SoundPackageUpload;
import com.dreikraft.axbo.events.PrefsOpen;
import com.dreikraft.axbo.events.SleepDataAdded;
import com.dreikraft.axbo.events.DiagramClosed;
import com.dreikraft.axbo.events.SleepDataCompare;
import com.dreikraft.axbo.events.SleepDataDelete;
import com.dreikraft.axbo.events.SleepDataImport;
import com.dreikraft.axbo.events.SleepDataImported;
import com.dreikraft.axbo.events.SleepDataLoad;
import com.dreikraft.axbo.events.SleepDataOpen;
import com.dreikraft.axbo.events.SleepDataSave;
import com.dreikraft.axbo.events.SoundUpload;
import com.dreikraft.axbo.gui.AxboFrame;
import com.dreikraft.axbo.gui.DataFrame;
import com.dreikraft.axbo.task.AxboClearTask;
import com.dreikraft.axbo.task.AxboFindTask;
import com.dreikraft.axbo.task.AxboResetTask;
import com.dreikraft.axbo.task.AxboStatusGetTask;
import com.dreikraft.axbo.task.AxboTask;
import com.dreikraft.axbo.task.AxboTestTask;
import com.dreikraft.axbo.task.AxboTimeSetTask;
import com.dreikraft.axbo.task.SleepDataImportTask;
import com.dreikraft.axbo.task.SleepDataLoadTask;
import com.dreikraft.axbo.task.SoundPackageUploadTask;
import com.dreikraft.axbo.util.BundleUtil;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.XMLEncoder;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.SwingWorker;
import org.apache.commons.logging.*;
import org.apache.commons.logging.Log;
import org.jfree.data.Range;
/**
* AxboFrameController
*
* @author jan.illetschko@3kraft.com
*/
public final class AxboFrameController implements ApplicationEventEnabled {
public static final int MAX_OPEN_DIAGRAMS = 30;
public static final Log log = LogFactory.getLog(AxboFrameController.class);
public static final String MINUTE_CLASS = "org.jfree.data.time.Minute";
public static final String SECOND_CLASS = "org.jfree.data.time.Second";
public static final Range COMPARE_RANGE = new Range(0, 80);
private final AxboFrame frame;
private boolean taskInProgress = false;
@SuppressWarnings("LeakingThisInConstructor")
public AxboFrameController() {
frame = new AxboFrame();
// application events
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
ApplicationInitialize.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
ApplicationInitialized.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
ApplicationExit.class, this);
}
public void handle(final ApplicationInitialize evt) {
// register events
// message events
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
ApplicationMessageEvent.class, this);
// view events
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
DataSearch.class, this);
// data events
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboDisconnect.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboFind.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboFound.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboStatusGet.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboStatusGot.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboReset.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboTest.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboTimeSet.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
AxboClear.class, this);
// diagram events
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
DiagramClosed.class, this);
// sleep data events
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataLoad.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataAdded.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataDelete.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataImport.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataImported.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataOpen.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataCompare.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SleepDataSave.class, this);
// sound events
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SoundPackageUpload.class, this);
ApplicationEventDispatcher.getInstance().registerApplicationEventHandler(
SoundUpload.class, this);
// create model and view objects
frame.init();
// enable view
ApplicationEventDispatcher.getInstance().dispatchGUIEvent(
new ApplicationInitialized(
this));
// load stored data
ApplicationEventDispatcher.getInstance().dispatchGUIEvent(new SleepDataLoad(
this));
}
public void handle(final ApplicationInitialized evt) {
registerForMacOSXEvents();
frame.setSize(1280, 768);
frame.setVisible(true);
}
public void handle(final ApplicationExit evt) {
exit();
}
public void handle(final ApplicationMessageEvent evt) {
if (evt.isError()) {
frame.showMessage(evt.getMessage(), true);
} else {
frame.showStatusMessage(evt.getMessage());
}
}
public void handle(final AxboDisconnect evt) {
DeviceContext.getDeviceType().getDataInterface().stop();
}
public void handle(final AxboFind evt) {
if (taskInProgress) {
return;
}
final AxboFindTask task = new AxboFindTask(evt.getFollowUpTask());
task.addPropertyChangeListener(new TaskProgressListener(frame, BundleUtil.
getMessage("statusLabel.findAxbo"), BundleUtil.getErrorMessage(
"info.axboFound"),
BundleUtil.getErrorMessage("globalError.axboNotFound"), false));
task.execute();
}
public void handle(final AxboFound evt) {
if (evt.getPortName() != null && evt.getFollowUpTask() != null) {
evt.getFollowUpTask().execute();
}
}
public void handle(final AxboReset evt) {
final AxboResetTask task = new AxboResetTask();
task.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.resetingAxbo"),
BundleUtil.getMessage("statusLabel.resetedAxbo"),
BundleUtil.getErrorMessage("globalError.failedToResetClock"),
true));
ApplicationEventDispatcher.getInstance().dispatchEvent(new AxboFind(this,
task));
}
public void handle(final AxboTest evt) {
final AxboTestTask task = new AxboTestTask((byte) 0x08);
task.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.testingAxbo"),
BundleUtil.getMessage("statusLabel.testedAxbo"),
BundleUtil.getErrorMessage("globalError.failedToTestAxbo"),
true));
ApplicationEventDispatcher.getInstance().dispatchEvent(new AxboFind(this,
task));
}
public void handle(final AxboTimeSet evt) {
final AxboTimeSetTask task = new AxboTimeSetTask();
task.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.setClockDate"),
BundleUtil.getMessage("statusLabel.setClockDateSucceeded"),
BundleUtil.getErrorMessage("globalError.failedToSetDate"),
true));
ApplicationEventDispatcher.getInstance().dispatchEvent(new AxboFind(this,
task));
}
public void handle(final AxboClear evt) {
final AxboClearTask task = new AxboClearTask();
task.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.clearClockData"),
BundleUtil.getMessage("statusLabel.clearedClockData"),
BundleUtil.getErrorMessage("globalError.failedToClearClockData"),
true));
ApplicationEventDispatcher.getInstance().dispatchEvent(new AxboFind(this,
task));
}
public void handle(final AxboStatusGet evt) {
final AxboStatusGetTask task = new AxboStatusGetTask();
task.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.getClockStatus"),
BundleUtil.getMessage("statusLabel.gotClockStatus"),
BundleUtil.getErrorMessage("globalError.failedToReadStatus"),
true));
ApplicationEventDispatcher.getInstance().dispatchEvent(new AxboFind(this,
task));
}
public void handle(final AxboStatusGot evt) {
DeviceContext.getDeviceType().getDataInterface().stop();
frame.setStatusProgressBarIndeterminate(false);
frame.showDeviceDisabled();
final AxboInfo infoData = evt.getInfoData();
if (infoData != null) {
final StringBuilder msg = new StringBuilder();
if (infoData.getSerialNumber() != null && infoData.getSerialNumber().
length() > 0) {
msg.append(BundleUtil.getMessage("axboData.serialNumber.label"));
msg.append(": ");
msg.append(infoData.getSerialNumber()).append("\n");
}
msg.append(BundleUtil.getMessage("axboData.hardwareVersion.label"));
msg.append(": ");
msg.append(infoData.getHardwareVersion()).append("\n");
msg.append(BundleUtil.getMessage("axboData.softwareVersion.label"));
msg.append(": ");
msg.append(infoData.getSoftwareVersion()).append("\n");
msg.append(BundleUtil.getMessage("axboData.rtc.label"));
msg.append(": ");
msg.append(infoData.getRtcCalibration());
frame.showMessage(msg.toString(), false);
} else {
frame.showStatusMessage("");
frame.showMessage(BundleUtil.getErrorMessage(
"globalError.failedToReadStatus"),
true);
}
}
public void handle(final SleepDataLoad evt) {
frame.showStatusMessage(BundleUtil.getMessage("statusLabel.loadProject"));
final File dir = new File(Axbo.PROJECT_DIR_DEFAULT);
if (log.isDebugEnabled()) {
log.debug("open project directory: " + dir.getAbsolutePath());
}
final File[] files = dir.listFiles(new Axbo.SPWFilenameFilter());
if (log.isDebugEnabled()) {
log.debug(files.length + " axbo sleep data files found");
}
// load files
if (files.length > 0) {
final SleepDataLoadTask task = new SleepDataLoadTask(files);
task.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.loadProject"),
MessageFormat.format(BundleUtil
.getMessage("statusLabel.projectLoaded"), files.length),
MessageFormat.format(BundleUtil
.getMessage("statusLabel.projectLoaded"), 0), false));
task.execute();
}
}
public void handle(final SleepDataDelete evt) {
final SleepData sleepData = evt.getSleepData();
if (!sleepData.getDataFile().delete())
log.warn("failed to delete sleep data file: " + sleepData.getDataFile()
.getAbsolutePath());
frame.getMetaDataTableModel().removeSleepData(sleepData);
final DataFrame dataView = getDataViewForSleepData(sleepData);
if (dataView != null) {
ApplicationEventDispatcher.getInstance().dispatchGUIEvent(
new DiagramClose(
this, dataView));
}
}
public void handle(final SleepDataAdded evt) {
frame.getMetaDataTableModel().addSleepData(evt.getSleepData());
}
public void handle(final SleepDataImport evt) {
final SleepDataImportTask task = new SleepDataImportTask(
frame.getMetaDataTableModel().getData());
task.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.importSleepData"),
BundleUtil.getMessage("statusLabel.importedSleepData"),
BundleUtil.getErrorMessage("globalError.failedToStoreClockData"),
true));
ApplicationEventDispatcher.getInstance().dispatchEvent(new AxboFind(this,
task));
}
public void handle(final SleepDataImported evt) {
final Integer newCount = evt.getNewSleepDataCount();
if (newCount != -1) {
frame.showStatusMessage(BundleUtil.getMessage(
"statusLabel.importedSleepData", newCount));
} else {
final String msg = BundleUtil.getErrorMessage(
"globalError.failedToStoreClockData");
frame.showStatusMessage(BundleUtil.getMessage(
"statusLabel.importedSleepData", 0));
frame.showMessage(msg, true);
}
}
public void handle(final DiagramClosed evt) {
ApplicationEventDispatcher.getInstance().deregisterApplicationEventHandler(
DiagramClose.class, evt.getDataViewController());
frame.updateDataViewsPanel();
calculateSummary();
}
/**
* Opens a sleep data record selected in the list of records.
*
* @param evt a SleepDataOpen application event
*/
public void handle(final SleepDataOpen evt) {
final List<SleepData> selectedSleepDataList = evt.getSleepDataList();
final List<SleepData> openSleepDataList = getOpenSleepDataList();
int countNew = 0;
SleepData curSleepData = null;
for (final SleepData sleepData : selectedSleepDataList) {
curSleepData = sleepData;
if (!openSleepDataList.contains(sleepData)) {
// open select Sleepdates
final DataFrameController dataViewCtrl = new DataFrameController(
sleepData);
frame
.addDataView(dataViewCtrl.getView(), Axbo.getChartType(), sleepData);
dataViewCtrl.init();
countNew++;
}
if (openSleepDataList.size() + countNew > MAX_OPEN_DIAGRAMS) {
ApplicationEventDispatcher.getInstance().dispatchEvent(
new ApplicationMessageEvent(
this, BundleUtil.getErrorMessage(
"globalError.toManyOpenDiagrams"),
true));
break;
}
}
// scroll to last opened data view
frame.jumpToDataView(getDataViewForSleepData(curSleepData));
// show summary for open sleep dates
calculateSummary();
}
/**
* Calculates and displays the overall summary of all open sleep data records.
*/
private void calculateSummary() {
final List<SleepData> openSleepDataList = getOpenSleepDataList();
long sumDuration = 0;
long minDuration = 1000 * 60 * 60 * 24;
long maxDuration = 0;
long saving = 0;
int count = 0;
for (final SleepData sleepData : openSleepDataList) {
if (sleepData.getWakeupTime() != null && !sleepData.isPowerNap()) {
count++;
final long duration = sleepData.calculateDuration();
sumDuration += duration;
saving += sleepData.calculateTimeSaving();
if (duration < minDuration) {
minDuration = duration;
}
if (duration > maxDuration) {
maxDuration = duration;
}
}
}
final long avgDuration = count > 0 ? sumDuration / count : 0;
frame
.showSummary(sumDuration, avgDuration, minDuration, maxDuration, saving,
count, openSleepDataList.size());
}
/**
* Returns a list with opened sleep data records.
*
* @return open sleep data records
*/
private List<SleepData> getOpenSleepDataList() {
final List<SleepData> openSleepDataList = new ArrayList<>();
for (final DataFrame dataView : frame.getDataViews()) {
openSleepDataList.add(dataView.getSleepData());
}
return openSleepDataList;
}
/**
* Returns the view for a sleep data record.
*
* @param sleepData a sleep data record
* @return the corresponding view
*/
private DataFrame getDataViewForSleepData(final SleepData sleepData) {
for (final DataFrame dataView : frame.getDataViews()) {
if (dataView.getSleepData().equals(sleepData)) {
return dataView;
}
}
return null;
}
/**
* Compares open sleep data records. Zooms all diagrams to the same start and
* end time for easy visual comparison of the charts.
*
* @param evt a SleepDataCompare event with a list of sleep records to compare
*/
public void handle(final SleepDataCompare evt) {
final List<SleepData> openSleepDataList = getOpenSleepDataList();
if (openSleepDataList.size() > 1) {
// set initial compare start hours to begin of sleep record
for (final SleepData sleepData : openSleepDataList) {
sleepData.setCompareStartHour(sleepData.calculateStartHour());
}
// sort sleep records by start hour of day
Collections.sort(openSleepDataList, new SleepDataComparator());
// calculate the maximum gap in hours of day
final int maxStartGap = openSleepDataList
.get(openSleepDataList.size() - 1).getCompareStartHour()
- openSleepDataList.get(0).getCompareStartHour();
// if the maximum gap is greater than 12 hours
if (maxStartGap > 12) {
// align sleep data records
for (SleepData sleepData : openSleepDataList) {
int offset = sleepData.getCompareStartHour() < 12 ? 24 : 0;
sleepData
.setCompareStartHour(sleepData.getCompareStartHour() + offset);
}
Collections.sort(openSleepDataList, new SleepDataComparator());
}
// get the start
int minStartHour = openSleepDataList.get(0).getCompareStartHour();
// get latest end time
int maxEndHour = 0;
for (SleepData sleepData : openSleepDataList) {
int endHour = sleepData.getCompareStartHour() + (int) (sleepData
.calculateDuration() / SleepData.HOUR) + 2;
if (endHour > maxEndHour) {
maxEndHour = endHour;
}
}
// zoom all diagrams to the same range
for (final SleepData sleepData : openSleepDataList) {
ApplicationEventDispatcher.getInstance().dispatchEvent(new DiagramZoom(
this, minStartHour + ":01", (maxEndHour - minStartHour) * 60,
COMPARE_RANGE));
final String msgParam = new StringBuffer(sleepData.getName()).append(
" ").append(DateFormat.getDateInstance(DateFormat.SHORT).format(
sleepData.
calculateStartTime())).toString();
final String msg = BundleUtil.getMessage("statusLabel.compareData",
msgParam);
ApplicationEventDispatcher.getInstance().dispatchEvent(
new ApplicationMessageEvent(
this, msg, false));
}
}
}
/**
* Saves sleep data to file.
*
* @param evt a SleepDataSave application event
*/
public void handle(final SleepDataSave evt) {
final SleepData sleepData = evt.getSleepData();
try (XMLEncoder encoder
= new XMLEncoder(new BufferedOutputStream(
new FileOutputStream(sleepData
.getDataFile())), Charset.forName("UTF-8").name(), true, 0)) {
encoder.writeObject(sleepData);
} catch (FileNotFoundException ex) {
log.error("failed to save file" + sleepData.getDataFile(), ex);
}
}
/**
* Filters the meta data table.
*
* @param evt a DataSearch application event
*/
public void handle(final DataSearch evt) {
// recalculate the table model
frame.getMetaDataTableModel().filterData(evt.getName(), evt.getFrom(), evt.
getTo());
}
/**
* Upload a sound package from file to aXbo.
*
* @param evt a sound package upload event was initiated by the user
*/
public void handle(final SoundPackageUpload evt) {
// prepare upload task
final SoundPackageUploadTask uploadTask = new SoundPackageUploadTask(evt
.getSoundPackageFile());
// register progress bar updates
uploadTask.addPropertyChangeListener(new TaskProgressListener(frame,
BundleUtil.getMessage("statusLabel.uploadSoundPackage"),
MessageFormat.format(BundleUtil.getMessage(
"statusLabel.uploadSoundPackageSuccess"), evt
.getSoundPackageFile()
.getName()),
BundleUtil.getErrorMessage("globalError.uploadFailed"),
false));
// find aXbo first, then execute upload
ApplicationEventDispatcher.getInstance().dispatchEvent(new AxboFind(this,
uploadTask));
}
/**
* A new sound gets uploaded from a sound package to aXbo.
*
* @param evt a new upload event
*/
public void handle(final SoundUpload evt) {
frame.showStatusMessage(BundleUtil.getMessage(
"statusLabel.uploadProgress", evt.getSoundName()));
}
/**
* Registers OSX specific handlers.
*/
private void registerForMacOSXEvents() {
if (OS.Mac.isCurrent()) {
try {
OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("exit",
(Class[]) null));
OSXAdapter.setPreferencesHandler(this, getClass().
getDeclaredMethod("showPrefs", (Class[]) null));
} catch (NoSuchMethodException ex) {
log.error(ex.getMessage(), ex);
}
}
}
/**
* Opens the preferences dialog.
*/
public void showPrefs() {
ApplicationEventDispatcher.getInstance().dispatchEvent(new PrefsOpen(
this, frame));
}
/**
* Exits aXbo research. Stops the data interface first.
*/
public void exit() {
DeviceContext.getDeviceType().getDataInterface().stop();
System.exit(0);
}
/**
* A progress listener for background tasks.
*/
private class TaskProgressListener implements
PropertyChangeListener {
private final AxboFrame view;
private final String msg;
private final String successMsg;
private final String failedMsg;
private final boolean indeterminate;
/**
* Creates a new listener.
*
* @param view for a view
* @param msg with a message
* @param successMsg with a success message
* @param failedMsg with an error message
* @param indeterminate is indeterminate
*/
public TaskProgressListener(final AxboFrame view, final String msg,
final String successMsg, final String failedMsg,
final boolean indeterminate) {
this.view = view;
this.msg = msg;
this.successMsg = successMsg;
this.failedMsg = failedMsg;
this.indeterminate = indeterminate;
}
/**
* Handles progress status updates for property types progress, state and
* result.
*
* @param evt the event.
*/
@Override
public void propertyChange(final PropertyChangeEvent evt) {
if (null != evt.getPropertyName())
switch (evt.getPropertyName()) {
case "progress":
if (!indeterminate) {
view.setStatusProgressBarValue((Integer) evt.getNewValue());
}
break;
case "state":
if (SwingWorker.StateValue.STARTED.equals(evt.getNewValue())) {
taskInProgress = true;
view.showStatusMessage(msg);
view.showDeviceEnabled();
view.setStatusProgressBarValue(0);
view.setStatusProgressBarStringPainted(!indeterminate);
view.setStatusProgressBarIndeterminate(indeterminate);
} else if (SwingWorker.StateValue.DONE.equals(evt.getNewValue())) {
taskInProgress = false;
view.showDeviceDisabled();
view.setStatusProgressBarValue(0);
view.setStatusProgressBarStringPainted(false);
view.setStatusProgressBarIndeterminate(false);
}
break;
case "result":
if (AxboTask.Result.SUCCESS.equals(evt.getNewValue())) {
view.showStatusMessage(successMsg);
} else if (AxboTask.Result.FAILED.equals(evt.getNewValue())) {
view.showMessage(failedMsg, true);
view.showStatusMessage("");
} else if (AxboTask.Result.INTERRUPTED.equals(evt.getNewValue())) {
view.showMessage(failedMsg, true);
view.showStatusMessage("");
}
break;
}
}
}
}