/*******************************************************************************
* Copyright 2011 Antti Havanko
*
* This file is part of Motiver.fi.
* Motiver.fi is licensed under one open source license and one commercial license.
*
* Commercial license: This is the appropriate option if you want to use Motiver.fi in
* commercial purposes. Contact license@motiver.fi for licensing options.
*
* Open source license: This is the appropriate option if you are creating an open source
* application with a license compatible with the GNU GPL license v3. Although the GPLv3 has
* many terms, the most important is that you must provide the source code of your application
* to your users so they can be free to modify your application for their own needs.
******************************************************************************/
package com.delect.motiver.client.view.profile;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.delect.motiver.client.AppController;
import com.delect.motiver.client.Motiver;
import com.delect.motiver.client.presenter.profile.MeasurementPresenter;
import com.delect.motiver.client.presenter.profile.MeasurementPresenter.MeasurementHandler;
import com.delect.motiver.client.res.MyResources;
import com.delect.motiver.client.view.MySpinnerField;
import com.delect.motiver.client.view.SmallNotePanel;
import com.delect.motiver.client.view.SmallNotePanelDisplay;
import com.delect.motiver.client.view.widget.NameInputWidget;
import com.delect.motiver.client.view.widget.MyButton;
import com.delect.motiver.client.view.widget.NameInputWidget.EnterNamePanelHandler;
import com.delect.motiver.shared.Constants;
import com.delect.motiver.shared.util.CommonUtils;
import com.delect.motiver.shared.util.CommonUtils.MessageBoxHandler;
import com.delect.motiver.shared.MeasurementModel;
import com.delect.motiver.shared.MeasurementValueModel;
import com.extjs.gxt.charts.client.Chart;
import com.extjs.gxt.charts.client.model.ChartModel;
import com.extjs.gxt.charts.client.model.Legend;
import com.extjs.gxt.charts.client.model.Legend.Position;
import com.extjs.gxt.charts.client.model.LineDataProvider;
import com.extjs.gxt.charts.client.model.Scale;
import com.extjs.gxt.charts.client.model.ScaleProvider;
import com.extjs.gxt.charts.client.model.axis.XAxis;
import com.extjs.gxt.charts.client.model.charts.ChartConfig;
import com.extjs.gxt.charts.client.model.charts.LineChart;
import com.extjs.gxt.charts.client.model.charts.dots.Dot;
import com.extjs.gxt.ui.client.Style.ButtonScale;
import com.extjs.gxt.ui.client.Style.HorizontalAlignment;
import com.extjs.gxt.ui.client.Style.SelectionMode;
import com.extjs.gxt.ui.client.data.BasePagingLoader;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.ModelStringProvider;
import com.extjs.gxt.ui.client.data.PagingLoadResult;
import com.extjs.gxt.ui.client.data.PagingLoader;
import com.extjs.gxt.ui.client.data.PagingModelMemoryProxy;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.FieldEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.util.KeyNav;
import com.extjs.gxt.ui.client.util.Margins;
import com.extjs.gxt.ui.client.widget.Label;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.Text;
import com.extjs.gxt.ui.client.widget.form.DateField;
import com.extjs.gxt.ui.client.widget.form.DateTimePropertyEditor;
import com.extjs.gxt.ui.client.widget.form.NumberField;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.grid.CellEditor;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnData;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.grid.GridCellRenderer;
import com.extjs.gxt.ui.client.widget.grid.GridSelectionModel;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayout;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayout.HBoxLayoutAlign;
import com.extjs.gxt.ui.client.widget.layout.HBoxLayoutData;
import com.extjs.gxt.ui.client.widget.layout.RowData;
import com.extjs.gxt.ui.client.widget.layout.RowLayout;
import com.extjs.gxt.ui.client.widget.toolbar.PagingToolBar;
public class MeasurementView extends MeasurementPresenter.MeasurementDisplay {
private DateField dfMeas;
private Grid<MeasurementValueModel> grid;
private MeasurementHandler handler;
private MeasurementModel measurement;
private LayoutContainer panelComments = new LayoutContainer();
private LayoutContainer panelDataOld = new LayoutContainer();
private LayoutContainer panelDates = new LayoutContainer();
private boolean showGraph = true;
private MySpinnerField textValue;
private List<MeasurementValueModel> values;
MessageBox box = null;
MyButton btnSwitchView = new MyButton();
LayoutContainer lcTop = new LayoutContainer();
SmallNotePanelDisplay panelBase = (SmallNotePanelDisplay)GWT.create(SmallNotePanel.class);
ListStore<MeasurementValueModel> store = new ListStore<MeasurementValueModel>();
TextField<String> tfUnit = new TextField<String>();
/**
* Measurement view
* @param showOnlyTitle : show title (TRUE, open measurement when clicked) or show just measurement (FALSE)
*/
public MeasurementView() {
try {
panelBase.setStylePrefix("panel-measurement");
panelBase.getPanelData().addListener(Events.Show, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
handler.valuesVisible();
}
});
this.add(panelBase);
} catch (Exception e) {
Motiver.showException(e);
}
}
@Override
public Widget asWidget() {
panelBase.getPanelData().removeAll();
try {
//if no model -> ask for name
if(measurement.getId() == 0) {
//add panel where user can type name
NameInputWidget panelNameInput = new NameInputWidget(new EnterNamePanelHandler() {
@Override
public void newName(String name) {
//if cancelled
if(name == null) {
handler.saveData(null);
}
else {
measurement.setName(name);
handler.saveData(measurement);
}
}
});
panelBase.getPanelData().add(panelNameInput);
panelBase.getPanelData().setVisible(true);
}
//model set
else {
//top panel
getTopPanel();
panelBase.getPanelData().add(lcTop, new RowData(-1, -1, new Margins(5, 0, 10, 0)));
//PANEL DATES
panelBase.getPanelData().add(panelDates, new RowData(-1, -1, new Margins(0, 0, 10, 0)));
//switch view
btnSwitchView = new MyButton();
btnSwitchView.setScale(ButtonScale.MEDIUM);
btnSwitchView.setText( (showGraph)? AppController.Lang.ShowInList() : AppController.Lang.ShowInGraph());
btnSwitchView.addListener(Events.OnClick, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
showGraph = !showGraph;
btnSwitchView.setText( (showGraph)? AppController.Lang.ShowInList() : AppController.Lang.ShowInGraph());
setValues(values);
}
});
panelBase.getPanelData().add(btnSwitchView, new RowData(-1, -1, new Margins(0, 0, 10, 0)));
//graph / list
panelDataOld.setStyleAttribute("min-height", "150px");
panelDataOld.setLayout(new RowLayout());
panelBase.getPanelData().add(panelDataOld, new RowData(-1, -1, new Margins(0, 0, 5, 0)));
panelBase.getPanelData().add(panelComments, new RowData(-1, -1, new Margins(10, 0, 0, 0)));
//update fields
dfMeas.setValue(measurement.getDate());
tfUnit.setValue(measurement.getUnit());
textValue.setValue(measurement.getTarget());
textValue.setFormat(NumberFormat.getFormat("0.0 " + measurement.getUnit()));
initTitlePanel();
// panelBase.getPanelData().setVisible(false);
//hide top panel if not our measurement
if(!measurement.getUid().equals(AppController.User.getUid())) {
lcTop.removeFromParent();
}
}
} catch (Exception e) {
Motiver.showException(e);
}
panelBase.getPanelData().layout();
return this;
}
@Override
public LayoutContainer getCommentsContainer() {
return panelComments;
}
@Override
public LayoutContainer getDataContainer() {
return panelDataOld;
}
@Override
public LayoutContainer getDatesContainer() {
return panelDates;
}
@Override
public void onStop() {
if(box != null && box.isVisible()) {
box.close();
}
}
@Override
public void setCollapsible(boolean isCollapsible) {
panelBase.setCollapsible(isCollapsible);
}
@Override
public void setHandler(MeasurementHandler handler) {
this.handler = handler;
}
@Override
public void setModel(MeasurementModel measurement) {
this.measurement = measurement;
}
/**
* Populates list/graph with given values
*/
@Override
public void setValues(List<MeasurementValueModel> values) {
try {
this.values = values;
//if null -> clear panel
if(values == null) {
panelDataOld.removeAll();
btnSwitchView.setVisible(false);
return;
}
//if no values -> hide button
btnSwitchView.setVisible(values.size() > 0);
panelDataOld.removeAll();
if(values.size() > 0) {
//graph
if(showGraph ) {
//if only single value -> show as text
if(values.size() == 1) {
VerticalPanel panelSingleValue = new VerticalPanel();
panelSingleValue.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
panelSingleValue.setSpacing(10);
panelSingleValue.setWidth("100%");
Text text = new Text(AppController.Lang.OnlyOneValueFound() + ":");
text.setStyleName("label-title-small");
panelSingleValue.add(text);
//value
MeasurementValueModel value = values.get(0);
Text textValue = new Text();
textValue.setText(CommonUtils.getDateString(value.getDate(), true, true) + ": " + value.getValue() + measurement.getUnit());
panelSingleValue.add(textValue);
panelDataOld.add(panelSingleValue, new RowData(-1, -1, new Margins(100, 0, 100, 0)));
}
//show in graph
else {
store.removeAll();
store.add(values);
//show graph
final Chart chart = new Chart(Constants.URL_APP_STATIC + "resources/chart/open-flash-chart.swf");
chart.setHeight(500);
chart.setBorders(true);
chart.setChartModel(getChartData());
panelDataOld.add(chart);
}
}
else { //show list
initList();
}
panelDataOld.layout();
}
} catch (Exception e) {
Motiver.showException(e);
}
}
@Override
public void showContent() {
panelBase.getPanelData().setVisible(true);
}
private ChartModel getChartData() {
ChartModel model = new ChartModel();
model.setBackgroundColour("-1");
model.setLegend(new Legend(Position.TOP, true));
model.setScaleProvider(new ScaleProvider() {
@Override
public Scale calcScale(double min, double max) {
if (min == 0 && max == 0) {
return new Scale(-1, 1, 1);
}
min = (int)(min * ((min > 0) ? 1 : 1.00));
max = (int)(max * ((max > 0) ? 1.00 : 1)) + 1;
//round to closest five
min -= min % 5;
max += (5 - max % 5);
double interval = 2;
double diff = Math.abs(max - min);
if(diff <= 30) {
interval = 5;
}
else if(diff <= 60) {
interval = 10;
}
else {
interval = (int)(diff / 5);
}
return new Scale(min, max, interval);
}
});
//values
LineChart line = new LineChart();
line.setColour("#000000");
LineDataProvider lineProvider = new LineDataProvider("v") {
@Override
public void populateData(ChartConfig config) {
LineChart chart = (LineChart) config;
chart.getValues().clear();
XAxis xAxis = null;
if (labelProperty != null || labelProvider != null) {
xAxis = chart.getModel().getXAxis();
if (xAxis == null) {
xAxis = new XAxis();
chart.getModel().setXAxis(xAxis);
}
xAxis.getLabels().getLabels().clear();
}
boolean first = true;
for (ModelData m : store.getModels()) {
Number value = getValue(m);
if (value == null) {
chart.addNullValue();
} else {
Dot dot = new Dot();
dot.setValue(value);
dot.setTooltip(value.doubleValue() + measurement.getUnit() + " (" + CommonUtils.getDateString(((MeasurementValueModel)m).getDate(), true, true) + ")");
chart.addDots(dot);
maxYValue = first ? value.doubleValue() : Math.max(maxYValue, value.doubleValue());
minYValue = first ? value.doubleValue() : Math.min(minYValue, value.doubleValue());
first = false;
}
if (xAxis != null) {
xAxis.addLabels(getLabel(m));
}
}
}
};
lineProvider.setLabelProvider(new ModelStringProvider<ModelData>() {
@Override
public String getStringValue(ModelData model, String property) {
try {
if(store.getModels().size() * 43 <= (getWidth() - 25)) {
MeasurementValueModel measurement = (MeasurementValueModel)model;
return CommonUtils.getDateString(measurement.getDate(), false, true, true);
}
} catch (Exception e) {
Motiver.showException(e);
}
return "";
}
});
lineProvider.bind(store);
line.setDataProvider(lineProvider);
model.addChartConfig(line);
return model;
}
/**
* Returns top panel
*/
private void getTopPanel() {
//TOP PANEL
HBoxLayout layout = new HBoxLayout();
layout.setHBoxLayoutAlign(HBoxLayoutAlign.MIDDLE);
lcTop.setLayout(layout);
//unit & target (left)
lcTop.add(new Text(AppController.Lang.Unit() + ":"), new HBoxLayoutData(new Margins(0, 5, 0, 0)));
tfUnit.setStyleAttribute("margin-left", "10px");
tfUnit.setMaxLength(5);
tfUnit.setAutoValidate(true);
CommonUtils.setWarningMessages(tfUnit);
tfUnit.addListener(Events.Valid, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
//if value changed
if(!measurement.getUnit().equals(tfUnit.getValue())) {
measurement.setUnit(tfUnit.getValue());
handler.saveData(measurement);
}
}
});
lcTop.add(tfUnit, new HBoxLayoutData(new Margins(0, 40, 0, 0)));
//target
lcTop.add(new Label(AppController.Lang.Goal() + ": "), new HBoxLayoutData(new Margins(0, 5, 0, 0)));
dfMeas = new DateField();
DateTimePropertyEditor pr = new DateTimePropertyEditor(CommonUtils.FmtShort);
dfMeas.setPropertyEditor(pr);
dfMeas.setMinValue(new Date());
dfMeas.setFieldLabel(AppController.Lang.Date());
dfMeas.setStyleAttribute("margin-left", "10px");
dfMeas.addListener(Events.Valid, new Listener<FieldEvent>() {
@Override
public void handleEvent(FieldEvent be) {
if(measurement != null) {
measurement.setDate(dfMeas.getValue());
handler.saveData(measurement);
}
}
});
lcTop.add(dfMeas, new HBoxLayoutData(new Margins(0, 5, 0, 0)));
textValue = new MySpinnerField();
textValue.setFieldLabel(AppController.Lang.Value());
textValue.setAllowBlank(false);
textValue.setEditable(true);
textValue.setMinValue(0);
textValue.setMaxValue(10000);
textValue.setStyleAttribute("margin-left", "10px");
textValue.setPropertyEditorType(Double.class);
new KeyNav<ComponentEvent>(textValue) {
@Override
public void onKeyPress(ComponentEvent event) {
if(event.getKeyCode() == KeyCodes.KEY_ENTER && handler != null) {
//only if target has changed
if(Double.compare(measurement.getTarget(), textValue.getValue().doubleValue()) != 0) {
measurement.setTarget(textValue.getValue().doubleValue());
//refresh view
setValues(values);
handler.saveData(measurement);
}
}
}
};
textValue.addListener(Events.Blur, new Listener<FieldEvent>() {
@Override
public void handleEvent(FieldEvent be) {
if(handler != null) {
//only if target has changed
if(Double.compare(measurement.getTarget(), textValue.getValue().doubleValue()) != 0) {
measurement.setTarget(textValue.getValue().doubleValue());
//refresh view
setValues(values);
handler.saveData(measurement);
}
}
}
});
lcTop.add(textValue);
//spacer
HBoxLayoutData flex = new HBoxLayoutData(new Margins(0, 5, 0, 0));
flex.setFlex(1);
lcTop.add(new Text(), flex);
}
/**
* Inits list that shows measurements
* Parameters: unit for value column
*/
private void initList() {
//columns
List<ColumnConfig> configs = new ArrayList<ColumnConfig>();
ColumnConfig column = new ColumnConfig("d", AppController.Lang.Date(), 200);
column.setDateTimeFormat(CommonUtils.Fmt);
column.setRenderer(new GridCellRenderer<MeasurementValueModel>() {
@Override
public Object render(MeasurementValueModel model, String property, ColumnData config, int rowIndex, int colIndex, ListStore<MeasurementValueModel> store, Grid<MeasurementValueModel> grid) {
return CommonUtils.getDateString(model.getDate(), false, true);
}
});
column.setMenuDisabled(true);
configs.add(column);
column = new ColumnConfig("v", AppController.Lang.Value(), 200);
column.setAlignment(HorizontalAlignment.RIGHT);
column.setNumberFormat(NumberFormat.getFormat("0.0 " + measurement.getUnit()));
column.setEditor(new CellEditor(new NumberField()));
column.setMenuDisabled(true);
configs.add(column);
final PagingToolBar toolBar = new PagingToolBar(Constants.LIMIT_LIST_RECORDS);
//set paging if more than 50 rows
if(values.size() > Constants.LIMIT_LIST_RECORDS) {
PagingModelMemoryProxy proxy = new PagingModelMemoryProxy(values);
// loader
PagingLoader<PagingLoadResult<MeasurementValueModel>> loader = new BasePagingLoader<PagingLoadResult<MeasurementValueModel>>(proxy);
loader.setRemoteSort(true);
store = new ListStore<MeasurementValueModel>(loader);
toolBar.bind(loader);
loader.load(0, Constants.LIMIT_LIST_RECORDS);
}
else {
//populate store
store.removeAll();
store.add(values);
}
//grid
ColumnModel cm = new ColumnModel(configs);
grid = new Grid<MeasurementValueModel>(store, cm);
grid.setStripeRows(true);
grid.setSelectionModel(new GridSelectionModel<MeasurementValueModel>());
grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
grid.setBorders(true);
grid.getView().setEmptyText(AppController.Lang.NoMeasurements());
grid.setColumnResize(false);
grid.setAutoWidth(true);
grid.setAutoHeight(true);
grid.setColumnResize(false);
grid.addListener(Events.OnClick, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
grid.focus();
}
});
//remove selected rows from ALL GRIDS when delete button is pressed
new KeyNav<ComponentEvent>(grid) {
@Override
public void onDelete(ComponentEvent ce) {
deleteSelectedValues();
}
};
panelDataOld.add(grid);
if(values.size() > Constants.LIMIT_LIST_RECORDS) {
panelDataOld.add(toolBar);
}
}
/**
* Inits panel which contains the title
*/
private void initTitlePanel() {
try {
panelBase.setTitleText(measurement.getNameClient());
//buttons
if(measurement.getId() != 0) {
if(measurement.getUid().equals(AppController.User.getUid())) {
//add value
panelBase.addHeaderButton(AppController.Lang.AddTarget(AppController.Lang.Value().toLowerCase()),
new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
handler.newValue();
}
});
//rename measurement
panelBase.addHeaderImageButton(AppController.Lang.Rename(), MyResources.INSTANCE.iconBtnRename(),
new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
if(box != null && box.isVisible()) {
box.close();
}
//ask for confirm
box = CommonUtils.getMessageBoxPrompt(measurement.getNameClient(), new MessageBoxHandler() {
@Override
public void okPressed(String text) {
if(!measurement.getNameClient().equals( text )) {
measurement.setName(text);
panelBase.setTitleText(measurement.getNameClient());
handler.saveData(measurement);
}
}
});
box.setTitle(AppController.Lang.Name());
box.setMessage(AppController.Lang.EnterName() + ":");
box.show();
}
});
//remove measurement link
panelBase.addHeaderImageButton(AppController.Lang.RemoveTarget(AppController.Lang.Workout().toLowerCase()), MyResources.INSTANCE.iconRemove(),
new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
setData("btnClick", true);
//ask for confirm
box = CommonUtils.getMessageBoxConfirm(AppController.Lang.RemoveConfirm(AppController.Lang.ThisMeasurement().toLowerCase()), new MessageBoxHandler() {
@Override
public void okPressed(String text) {
handler.measurementRemoved();
}
});
box.show();
}
});
}
}
} catch (Exception e) {
Motiver.showException(e);
}
}
/**
* Deletes selected measurements from grid
*/
protected void deleteSelectedValues() {
if(grid.getSelectionModel().getSelectedItems().size() > 0) {
//ask for confirm
box = CommonUtils.getMessageBoxConfirm(AppController.Lang.RemoveConfirm(AppController.Lang.SelectedValues().toLowerCase()), new MessageBoxHandler() {
@Override
public void okPressed(String text) {
try {
//get ids of the selected items
List<MeasurementValueModel> list = new ArrayList<MeasurementValueModel>();
for(MeasurementValueModel m : grid.getSelectionModel().getSelectedItems()) {
list.add(m);
//remove item from grid
store.remove(m);
}
handler.valuesRemoved(list);
} catch (Exception e) {
Motiver.showException(e);
}
}
});
box.show();
}
}
}