package org.geogebra.web.web.gui.view.dataCollection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeSet;
import org.geogebra.common.gui.SetLabels;
import org.geogebra.common.kernel.ModeSetter;
import org.geogebra.common.kernel.View;
import org.geogebra.common.kernel.commands.CmdDataFunction;
import org.geogebra.common.kernel.geos.GProperty;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoFunction;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumeric;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Localization;
import org.geogebra.common.main.settings.AbstractSettings;
import org.geogebra.common.main.settings.DataCollectionSettings;
import org.geogebra.common.main.settings.SettingListener;
import org.geogebra.common.plugin.Operation;
import org.geogebra.common.plugin.SensorLogger.Types;
import org.geogebra.common.util.lang.Unicode;
import org.geogebra.web.html5.gui.FastClickHandler;
import org.geogebra.web.html5.main.AppW;
import org.geogebra.web.web.css.GuiResources;
import org.geogebra.web.web.gui.util.MyToggleButtonW;
import org.geogebra.web.web.gui.util.StandardButton;
import org.geogebra.web.web.gui.view.dataCollection.GeoListBox.DefaultEntries;
import org.geogebra.web.web.gui.view.dataCollection.Settings.AccSetting;
import org.geogebra.web.web.gui.view.dataCollection.Settings.LightSetting;
import org.geogebra.web.web.gui.view.dataCollection.Settings.MagFieldSetting;
import org.geogebra.web.web.gui.view.dataCollection.Settings.OrientationSetting;
import org.geogebra.web.web.gui.view.dataCollection.Settings.ProxiSetting;
import org.geogebra.web.web.gui.view.dataCollection.Settings.SensorSetting;
import org.geogebra.web.web.gui.view.dataCollection.Settings.TimeSetting;
import org.geogebra.web.web.main.AppWFull;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TabLayoutPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
/**
*
*/
public class DataCollectionView extends FlowPanel implements View, SetLabels,
ChangeHandler, SettingListener {
/** caption of Tab */
private final static String SENSORS = "Sensors";
private final static String DATA_CONNECTION = "DataConnection";
private final static String DATA_SHARING_CODE = "DataSharingCode";
private final static String CONNECTION_FAILD = "DataConnectionFailed";
private final static String CONNECTING = "Connecting";
private final static String FREQUENCY = "FrequencyHz";
private AppW app;
private TabLayoutPanel tabPanel;
private FlowPanel dataCollectionTab;
/** GeoElements that are available and not used from another sensor */
ArrayList<GeoElement> availableObjects = new ArrayList<GeoElement>();
/** list of all used GeoElements */
private ArrayList<GeoElement> usedObjects = new ArrayList<GeoElement>();
/** panel with the available sensors */
private FlowPanel sensorSettings;
private ArrayList<SensorSetting> sensors = new ArrayList<SensorSetting>();
/** panels which are shown or hidden depending on connection-status */
private FlowPanel connectionStatusPanel;
private FlowPanel freqPanel;
private MyToggleButtonW connectButton;
private TextBox appIDTextBox;
/** different settings for the available sensors */
private AccSetting acc;
private MagFieldSetting magField;
private TimeSetting time;
private OrientationSetting orientation;
private ProxiSetting proxi;
private LightSetting light;
// private LoudnessSetting loudness;
/** widgets which need translation */
private Label connectionLabel;
private Label appID;
private Label connectionFailed;
private Label connecting;
private Label frequency;
/** holds sensor settings (e.g. Accelerometer x logs to function f) */
private DataCollectionSettings settings;
/** current used frequency */
private int freqHz;
private Localization loc;
/**
* @param app
* {@link AppW}
*/
public DataCollectionView(AppW app) {
this.app = app;
this.loc = app.getLocalization();
this.addStyleName("dataCollectionView");
this.settings = app.getSettings().getDataCollection();
this.settings.addListener(this);
createGUI();
this.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
handleClick();
}
}, ClickEvent.getType());
settingsChanged(this.settings);
}
/**
* attaches this view to kernel
*/
public void attachView() {
app.getKernel().attach(this);
}
/**
* handles a click on this view. used to recognize if this view gains focus.
*/
void handleClick() {
this.app.setActiveView(App.VIEW_DATA_COLLECTION);
}
private void createGUI() {
this.tabPanel = new TabLayoutPanel(30, Unit.PX);
addTabContent();
this.add(this.tabPanel);
addCloseButton();
}
/**
* adds a cross into the upper right corner to close this view
*/
private void addCloseButton() {
FlowPanel closeButtonPanel = new FlowPanel();
closeButtonPanel.setStyleName("closeButtonPanel");
StandardButton closeButton = new StandardButton(
GuiResources.INSTANCE.dockbar_close());
closeButton.addFastClickHandler(new FastClickHandler() {
@Override
public void onClick(Widget source) {
app.getGuiManager()
.setShowView(false, App.VIEW_DATA_COLLECTION);
}
});
closeButtonPanel.add(closeButton);
this.add(closeButtonPanel);
}
/**
* adds a new tab to the {@link #tabPanel}
*/
private void addTabContent() {
this.dataCollectionTab = new FlowPanel();
this.dataCollectionTab.addStyleName("dataCollectionTab");
addConnection();
addSettingsPanel();
this.tabPanel.add(this.dataCollectionTab, loc.getMenu(SENSORS));
}
private void addSettingsPanel() {
this.sensorSettings = new FlowPanel();
this.sensorSettings.addStyleName("sensorSettings");
updateGeoList();
addAccelerometer();
addOrientation();
addMagneticField();
addProximity();
addLight();
addTime();
// addLoudness(); not in use now
this.dataCollectionTab.add(this.sensorSettings);
}
private void addConnection() {
FlowPanel connection = new FlowPanel();
connection.addStyleName("connection");
// Caption
FlowPanel connectionCaption = new FlowPanel();
connectionCaption.addStyleName("panelTitle");
this.connectionLabel = new Label(
loc.getMenu(DATA_CONNECTION));
connectionCaption.add(this.connectionLabel);
connection.add(connectionCaption);
// Content
FlowPanel setting = new FlowPanel();
setting.addStyleName("panelIndent");
addSharingCode(setting);
addConnectionStatus(setting);
addFrequency(setting);
connection.add(setting);
this.dataCollectionTab.add(connection);
}
private void addConnectionStatus(FlowPanel setting) {
this.connectionStatusPanel = new FlowPanel();
this.connectionStatusPanel.setVisible(false);
this.connectionStatusPanel.addStyleName("rowContainer");
this.connectionFailed = new Label(this.loc.getMenu(CONNECTION_FAILD));
this.connectionFailed.setVisible(false);
this.connecting = new Label(this.loc.getMenu(CONNECTING));
this.connecting.setVisible(false);
this.connectionStatusPanel.add(this.connectionFailed);
this.connectionStatusPanel.add(this.connecting);
setting.add(this.connectionStatusPanel);
}
/**
* adds SharingCode to the given panel
*
* @param setting
*/
private void addSharingCode(FlowPanel setting) {
FlowPanel appIDpanel = new FlowPanel();
appIDpanel.addStyleName("rowContainer");
appIDpanel.addStyleName("sharingCodePanel");
this.appID = new Label(
loc.getMenu(DataCollectionView.DATA_SHARING_CODE));
this.appIDTextBox = new TextBox();
this.appIDTextBox.addStyleName("appIdTextBox");
this.appIDTextBox.addKeyDownHandler(new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent event) {
if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
onAppIdChange();
}
}
});
Image imgON = new Image(GuiResources.INSTANCE.datacollection_on());
Image imgOFF = new Image(GuiResources.INSTANCE.datacollection_off());
this.connectButton = new MyToggleButtonW(imgOFF, imgON);
this.connectButton.addStyleName("connectButton");
this.connectButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (appIDTextBox.getText().length() > 0) {
handleConnectionClicked();
} else {
connectButton.setDown(false);
}
}
});
appIDpanel.add(this.appID);
appIDpanel.add(this.appIDTextBox);
appIDpanel.add(this.connectButton);
setting.add(appIDpanel);
}
/**
* Triggered by changed app ID
*/
protected void onAppIdChange() {
if (appIDTextBox.getText().length() > 0) {
connectButton.setDown(true);
appIDTextBox.setFocus(false);
handleConnectionClicked();
}
}
/**
* adds frequency to the given panel
*
* @param setting
*/
private void addFrequency(FlowPanel setting) {
this.freqPanel = new FlowPanel();
this.freqPanel.setVisible(false);
this.freqPanel.addStyleName("rowContainer");
this.frequency = new Label();
this.freqPanel.add(this.frequency);
setting.add(this.freqPanel);
}
/**
*
*/
void handleConnectionClicked() {
if (this.connectButton.isDown()) {
this.connectionStatusPanel.setVisible(true);
this.connecting.setVisible(true);
this.connectionFailed.setVisible(false);
((AppWFull) this.app).getDataCollection().onConnect(
this.appIDTextBox.getText().toUpperCase());
} else {
this.appIDTextBox.removeStyleName("disabled");
this.connectionStatusPanel.setVisible(false);
this.freqPanel.setVisible(false);
((AppWFull) this.app).getDataCollection().onDisconnect();
for (SensorSetting setting : this.sensors) {
setting.setOn(false);
}
}
}
private void addAccelerometer() {
this.acc = new AccSetting(this.app, this, "Accelerometer", "m/s\u00B2");
this.sensors.add(this.acc);
this.sensorSettings.add(this.acc);
}
private void addMagneticField() {
this.magField = new MagFieldSetting(this.app, this, "MagneticField",
Unicode.micro + "T");
this.sensors.add(this.magField);
this.sensorSettings.add(this.magField);
}
private void addTime() {
this.time = new TimeSetting(this.app, this, "ReceivedData", "");
this.sensors.add(this.time);
this.sensorSettings.add(this.time);
}
private void addOrientation() {
this.orientation = new OrientationSetting(this.app, this,
"Orientation", "DegreeUnit");
this.sensors.add(this.orientation);
this.sensorSettings.add(this.orientation);
}
private void addProximity() {
this.proxi = new ProxiSetting(this.app, this, "Proximity", "cm");
this.sensors.add(this.proxi);
this.sensorSettings.add(this.proxi);
}
private void addLight() {
this.light = new LightSetting(this.app, this, "Light", "lx");
this.sensors.add(this.light);
this.sensorSettings.add(this.light);
}
/**
* @param sensor
* which sensor
* @param flag
* {@code true} if sensor is turned on
*/
public void setSensorOn(Types sensor, boolean flag) {
switch (sensor) {
case ACCELEROMETER_X:
this.acc.setOn(flag);
break;
case MAGNETIC_FIELD_X:
this.magField.setOn(flag);
break;
case ORIENTATION_X:
this.orientation.setOn(flag);
break;
case PROXIMITY:
this.proxi.setOn(flag);
break;
case LIGHT:
this.light.setOn(flag);
break;
case TIMESTAMP:
this.time.setOn(flag);
break;
// case LOUDNESS:
// this.loudness.setOn(flag);
// break;
default:
break;
}
}
@Override
public void setLabels() {
// we only have one tab
this.tabPanel.setTabText(0, loc.getMenu(SENSORS));
this.appID.setText(this.loc.getMenu(DATA_SHARING_CODE));
this.connectionLabel.setText(this.loc.getMenu(DATA_CONNECTION));
this.connecting.setText(this.loc.getMenu(CONNECTING));
this.connectionFailed.setText(this.loc.getMenu(CONNECTION_FAILD));
this.frequency.setText(loc.getMenu(FREQUENCY) + ": " + this.freqHz);
for (SensorSetting setting : this.sensors) {
setting.setLabels();
}
updateListBoxes();
}
/**
* if this view gets focus back, the list of GeoElements has to be updated
* because it's possible, that some new GeoElements were created or some
* were deleted
*/
public void updateGeoList() {
TreeSet<GeoElement> newTreeSet = this.app.getKernel().getConstruction()
.getGeoSetNameDescriptionOrder();
this.availableObjects.clear();
// fill list of available objects
for (GeoElement element : newTreeSet) {
if ((element instanceof GeoNumeric || (element instanceof GeoFunction
&& ((GeoFunction) element).getFunctionExpression()
.getOperation() == Operation.DATA || element instanceof GeoList))
&& !this.usedObjects.contains(element)) {
this.availableObjects.add(element);
}
}
this.usedObjects.clear();
for (SensorSetting setting : this.sensors) {
for (GeoListBox listBox : setting.getListBoxes()) {
GeoElement selection = this.settings.getGeoMappedToSensor(
listBox.getType(), this.app.getKernel()
.getConstruction());
if (selection != null && newTreeSet.contains(selection)) {
this.usedObjects.add(selection);
this.availableObjects.remove(selection);
} else {
//
this.settings.removeMappedGeo(listBox.getType());
((AppWFull) this.app).getDataCollection()
.removeRegisteredGeo(listBox.getType());
}
}
}
updateListBoxes();
}
/**
* the old selected geo is moved to {@link #availableObjects} and the new
* selected geo is moved to {@link #usedObjects}. the chosen geo is removed
* from the selectionList of the other listBoxes
*/
@Override
public void onChange(ChangeEvent event) {
GeoListBox listBox = (GeoListBox) event.getSource();
// if a geo was selected give it free
GeoElement oldSelection = listBox.getSelection();
int selectedIndex = listBox.getSelectedIndex();
if (oldSelection != null) {
setGeoUnused(oldSelection, listBox);
}
// get new selection
GeoElement newSelection = listBox.getGeoElement(listBox
.getSelectedItemText());
if (newSelection == null) {
if (DefaultEntries.EMPTY_SELECTION.getText()
.equals(listBox.getValue(selectedIndex))) {
listBox.setSelection(null); // set null, handled in
// GeoListBox
} else if (DefaultEntries.CREATE_NUMBER.getText()
.equals(listBox.getValue(selectedIndex))) {
GeoNumeric num = new GeoNumeric(
this.app.getKernel().getConstruction(), 0);
num.setShowExtendedAV(false);
num.setDrawable(false);
num.setLabel(null);
listBox.addItem(num);
setGeoUsed(num, listBox);
} else if (DefaultEntries.CREATE_DATA_FUNCTION.getText()
.equals(listBox.getValue(selectedIndex))) {
// create new data function
newSelection = CmdDataFunction.emptyFunction(app.getKernel(),
null)[0];
listBox.addItem(newSelection);
setGeoUsed(newSelection, listBox);
} else if (DefaultEntries.CREATE_LIST.getText()
.equals(listBox.getValue(selectedIndex))) {
newSelection = new GeoList(this.app.getKernel()
.getConstruction());
newSelection.setLabel(null);
listBox.addItem(newSelection);
setGeoUsed(newSelection, listBox);
}
} else {
setGeoUsed(newSelection, listBox);
}
// updateOtherListBoxes(listBox);somehow this sown {} list instead of
// "time list" in the listbox it was created
updateListBoxes();
}
/**
* removes the given {@link GeoElement} from the list of
* {@link #availableObjects} and adds it to the list of {@link #usedObjects}
* . It sets the correct selection of the given {@link GeoListBox} and
* starts logging data if the depending sensor is set to ON.
*
* @param geo
* {@link GeoElement}
* @param listBox
* {@link GeoListBox}
*/
private void setGeoUsed(GeoElement geo, GeoListBox listBox) {
listBox.setSelection(geo);
this.availableObjects.remove(geo);
this.usedObjects.add(geo);
if (listBox.getSensorSetting().isOn()) {
((AppWFull) this.app).getDataCollection().registerGeo(
listBox.getType().toString(), geo);
}
}
/**
* removes the given {@link GeoElement} from the list of
* {@link #usedObjects}, adds it to the list of {@link #availableObjects}
* and stops logging data.
*
* @param geo
* @param listBox
*/
private void setGeoUnused(GeoElement geo, GeoListBox listBox) {
this.availableObjects.add(geo);
this.usedObjects.remove(geo);
((AppWFull) this.app).getDataCollection().removeRegisteredGeo(
listBox.getType());
}
/**
* updates the entries of every box in the list of {@link #listBoxes boxes}
*/
private void updateListBoxes() {
for (SensorSetting setting : this.sensors) {
setting.updateAllBoxes(this.availableObjects, this.usedObjects);
}
}
/**
* @return a hashMap with the specific sensor-string and the depending
* GeoElement
*/
public HashMap<Types, GeoElement> getActivedSensors() {
HashMap<Types, GeoElement> activeSensors = new HashMap<Types, GeoElement>();
for (SensorSetting setting : this.sensors) {
if (setting.isOn()) {
for (GeoListBox listBox : setting.getListBoxes()) {
if (listBox.getSelectedIndex() != 0) {
activeSensors.put(listBox.getType(),
listBox.getSelection());
}
}
}
}
return activeSensors;
}
/**
* update GUI if entered ID was wrong
*/
public void onWrongID() {
this.connectionFailed.setVisible(true);
this.connecting.setVisible(false);
((AppWFull) this.app).getDataCollection().onDisconnect();
this.connectButton.setDown(false);
this.appIDTextBox.setEnabled(true);
this.appIDTextBox.removeStyleName("disabled");
this.appIDTextBox.setSelectionRange(0, this.appIDTextBox.getText()
.length());
this.appIDTextBox.setFocus(true);
this.freqPanel.setVisible(false);
}
/**
* update GUI if entered ID was correct
*/
public void onCorrectID() {
this.connectionStatusPanel.setVisible(false);
this.appIDTextBox.addStyleName("disabled");
this.freqPanel.setVisible(true);
((AppWFull) this.app).getDataCollection()
.triggerAvailableSensors();
((AppWFull) this.app).getDataCollection().triggerFrequency();
}
/**
* updates the label for the used frequency from GeoGebra Data App
*
* @param freq
* frequency in Hz
*/
public void setFrequency(int freq) {
this.freqHz = freq;
this.frequency.setText(loc.getMenu(FREQUENCY) + ": " + this.freqHz);
}
@Override
public void add(GeoElement geo) {
this.updateGeoList();
}
@Override
public void remove(GeoElement geo) {
this.updateGeoList();
}
@Override
public void rename(GeoElement geo) {
this.updateGeoList();
}
@Override
public void update(GeoElement geo) {
this.updateGeoList();
}
@Override
public void updateVisualStyle(GeoElement geo, GProperty prop) {
// not used
}
@Override
public void updatePreviewFromInputBar(GeoElement[] geos) {
// not used
}
@Override
public void updateAuxiliaryObject(GeoElement geo) {
// not used
}
@Override
public void repaintView() {
// not used
}
@Override
public boolean suggestRepaint() {
// not used
return false;
}
@Override
public void reset() {
// not used
}
@Override
public void clearView() {
// not used
}
@Override
public void setMode(int mode, ModeSetter m) {
// not used
}
@Override
public int getViewID() {
return App.VIEW_DATA_COLLECTION;
}
@Override
public boolean hasFocus() {
// not used
return false;
}
@Override
public boolean isShowing() {
// not used
return false;
}
@Override
public void startBatchUpdate() {
// not used
}
@Override
public void endBatchUpdate() {
// not used
}
/**
* returns settings in XML format
*
* @param sb
* to store XML
* @param asPreference
* asPreference
*/
public void getXML(StringBuilder sb, boolean asPreference) {
settings.getXML(sb, asPreference, app.getKernel().getConstruction());
}
public DataCollectionSettings getDataSettings() {
return this.settings;
}
/**
* only after opening a file
*/
@Override
public void settingsChanged(AbstractSettings settings) {
TreeSet<GeoElement> newTreeSet = app.getKernel().getConstruction()
.getGeoSetNameDescriptionOrder();
this.availableObjects.clear();
// fill list of available objects
for (GeoElement element : newTreeSet) {
if ((element instanceof GeoNumeric || (element instanceof GeoFunction
&& ((GeoFunction) element).getFunctionExpression()
.getOperation() == Operation.DATA || element instanceof GeoList))) {
this.availableObjects.add(element);
}
}
this.usedObjects.clear();
for (SensorSetting setting : this.sensors) {
for (GeoListBox listBox : setting.getListBoxes()) {
GeoElement selection = ((DataCollectionSettings) settings)
.getGeoMappedToSensor(listBox.getType(), app
.getKernel().getConstruction());
if (selection != null) {
this.usedObjects.add(selection);
this.availableObjects.remove(selection);
}
}
}
updateListBoxes();
}
/**
* update the label for the real frequency for the given type of sensor
*
* @param sensor
* {@link Types}
* @param freq
* int
*/
public void setRealFrequency(Types sensor, int freq) {
switch (sensor) {
default:
// do nothing
break;
case ACCELEROMETER_X:
this.acc.setRealFrequency(freq);
break;
case MAGNETIC_FIELD_X:
this.magField.setRealFrequency(freq);
break;
case ORIENTATION_X:
this.orientation.setRealFrequency(freq);
break;
case LIGHT:
this.light.setRealFrequency(freq);
break;
case PROXIMITY:
this.proxi.setRealFrequency(freq);
break;
// case LOUDNESS:
// this.loudness.setRealFrequency(freq);
// break;
}
}
}