/*******************************************************************************
* 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.training;
import java.util.List;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
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.Templates;
import com.delect.motiver.client.presenter.training.ExercisePresenter;
import com.delect.motiver.client.presenter.training.ExercisePresenter.ExerciseHandler;
import com.delect.motiver.client.res.MyResources;
import com.delect.motiver.client.view.CustomListener;
import com.delect.motiver.client.view.widget.ImageButton;
import com.delect.motiver.client.view.widget.Widgets;
import com.delect.motiver.shared.Constants;
import com.delect.motiver.shared.ExerciseModel;
import com.delect.motiver.shared.ExerciseNameModel;
import com.delect.motiver.shared.util.CommonUtils;
import com.delect.motiver.shared.util.CommonUtils.MessageBoxHandler;
import com.delect.motiver.shared.WorkoutModel;
import com.extjs.gxt.ui.client.Style.HideMode;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.XTemplate;
import com.extjs.gxt.ui.client.data.BaseListLoadResult;
import com.extjs.gxt.ui.client.data.BaseListLoader;
import com.extjs.gxt.ui.client.data.BasePagingLoadConfig;
import com.extjs.gxt.ui.client.data.ListLoadResult;
import com.extjs.gxt.ui.client.data.LoadEvent;
import com.extjs.gxt.ui.client.data.Loader;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.data.ModelProcessor;
import com.extjs.gxt.ui.client.data.ModelReader;
import com.extjs.gxt.ui.client.data.RpcProxy;
import com.extjs.gxt.ui.client.dnd.DragSource;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.DNDEvent;
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.Margins;
import com.extjs.gxt.ui.client.widget.HtmlContainer;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.form.ComboBox;
import com.extjs.gxt.ui.client.widget.form.ComboBox.TriggerAction;
import com.extjs.gxt.ui.client.widget.form.ListModelPropertyEditor;
import com.extjs.gxt.ui.client.widget.form.SpinnerField;
import com.extjs.gxt.ui.client.widget.form.TextField;
import com.extjs.gxt.ui.client.widget.layout.BoxLayout.BoxLayoutPack;
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;
public class ExerciseView extends ExercisePresenter.ExerciseDisplay {
//template for combobox
private static XTemplate nameTemplate = XTemplate.create(Templates.getExerciseNameTemplate());
protected MessageBox box = null;
protected ImageButton btnVideo;
protected ComboBox<ExerciseNameModel> comboName = null;
protected LayoutContainer containerName = new LayoutContainer();
protected ExerciseModel exercise;
private final HBoxLayoutData flexSets = new HBoxLayoutData(new Margins(0, 0, 0, 10));
private final HBoxLayoutData flexX = new HBoxLayoutData(new Margins(4, 0, 0, 10));
protected ExerciseHandler handler;
protected LayoutContainer panelButtons = new LayoutContainer();
protected LayoutContainer panelVideo = new LayoutContainer();
protected ListStore<ExerciseNameModel> store;
private LayoutContainer thisContent = new LayoutContainer();
/**
* Exercise view
*/
public ExerciseView() {
this.setStyleName("panel-exercise");
HBoxLayout layout = new HBoxLayout();
layout.setHBoxLayoutAlign(HBoxLayoutAlign.MIDDLE);
thisContent.setLayout(layout);
//show hide header buttons based on mouse position
this.addListener(Events.OnMouseOver, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
addStyleName("panel-exercise-active");
panelButtons.setVisible(true);
if(btnVideo != null) {
btnVideo.setVisible(true);
}
thisContent.layout(true);
}
});
this.addListener(Events.OnMouseOut, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
removeStyleName("panel-exercise-active");
panelButtons.setVisible(false);
if(btnVideo != null) {
btnVideo.setVisible(false);
}
thisContent.layout(true);
}
});
}
@Override
public Widget asWidget() {
try {
//if our exercise
if(exercise.getWorkout().getUser().equals(AppController.User)) {
//change order "link" (=drag source)
HBoxLayoutData flexDrag = new HBoxLayoutData(new Margins(0, 5, 0, 0));
thisContent.add(getDragButton(), flexDrag);
//name combo
comboName = addExerciseCombo();
containerName.add(comboName);
thisContent.add(containerName);
//video
panelVideo.setWidth(16);
thisContent.add(panelVideo, new HBoxLayoutData(new Margins(0, 0, 0, 10)));
//sets
thisContent.add(getSpinnerField(), flexSets);
HtmlContainer labelX1 = new HtmlContainer("x");
labelX1.setStyleName("label-x");
thisContent.add(labelX1, flexX);
//reps
HBoxLayoutData flexReps = new HBoxLayoutData(new Margins(0, 0, 0, 10));
flexReps.setFlex(1);
final TextField<String> tfReps = new TextField<String>();
tfReps.addStyleName("field-amount");
tfReps.addListener(Events.OnClick, CustomListener.fieldOnClicked);
tfReps.setValue(exercise.getReps());
tfReps.setEmptyText(AppController.Lang.Reps());
tfReps.setWidth(100);
tfReps.addListener(Events.Change, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
if(handler != null && tfReps.isValid()) {
exercise.setReps(tfReps.getValue());
handler.saveData(exercise, false);
}
}
});
tfReps.setMaxLength(50);
CommonUtils.setWarningMessages(tfReps);
thisContent.add(tfReps, flexReps);
HtmlContainer labelX2 = new HtmlContainer("x");
labelX2.setStyleName("label-x");
thisContent.add(labelX2, flexX);
//weights
final TextField<String> tfWeights = new TextField<String>();
tfWeights.addStyleName("field-amount");
tfWeights.addListener(Events.OnClick, CustomListener.fieldOnClicked);
tfWeights.setValue(exercise.getWeights());
tfWeights.setEmptyText(AppController.Lang.Weights());
tfWeights.addListener(Events.Change, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
if(handler != null && tfWeights.isValid()) {
exercise.setWeights(tfWeights.getValue());
handler.saveData(exercise, false);
}
}
});
tfWeights.setWidth(100);
tfWeights.setMaxLength(50);
CommonUtils.setWarningMessages(tfWeights);
thisContent.add(tfWeights, flexReps);
//buttons layout
thisContent.add(getPanelButtons(), new HBoxLayoutData(new Margins(0, 0, 0, 10)));
}
//not our exercise
else {
HorizontalPanel panel = Widgets.getReadOnlyExercise(exercise);
thisContent.add(panel);
}
} catch (Exception e) {
Motiver.showException(e);
}
panelButtons.setVisible(false);
this.add(thisContent);
return this;
}
protected LayoutContainer getPanelButtons() {
HBoxLayout layoutButtons = new HBoxLayout();
layoutButtons.setHBoxLayoutAlign(HBoxLayoutAlign.MIDDLE);
layoutButtons.setPack(BoxLayoutPack.END);
panelButtons.setLayout(layoutButtons);
panelButtons.setHeight(16);
panelButtons.setWidth(50);
panelButtons.setHideMode(HideMode.VISIBILITY);
//last weights
ImageButton btnLastWeights = new ImageButton(AppController.Lang.LastWeightsForThisExercise(), MyResources.INSTANCE.iconLastWeights());
btnLastWeights.addListener(Events.OnClick, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
handler.showLastWeights();
}
});
panelButtons.add(btnLastWeights, new HBoxLayoutData(new Margins(0, 10, 0, 0)));
//remove exercise link
ImageButton btnRemove = new ImageButton(AppController.Lang.RemoveTarget(AppController.Lang.ThisExercise().toLowerCase()), MyResources.INSTANCE.iconRemove());
btnRemove.addListener(Events.OnClick, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
//ask for confirm
box = CommonUtils.getMessageBoxConfirm(AppController.Lang.RemoveConfirm(AppController.Lang.ThisExercise().toLowerCase()), new MessageBoxHandler() {
@Override
public void okPressed(String text) {
handler.exerciseRemoved();
}
});
box.show();
}
});
panelButtons.add(btnRemove);
return panelButtons;
}
protected SpinnerField getSpinnerField() {
final SpinnerField spinSets = new SpinnerField();
//save value when valid
spinSets.addListener(Events.Change, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
if(spinSets.getValue() != null && handler != null && spinSets.isValid()) {
exercise.setSets(spinSets.getValue().intValue());
handler.saveData(exercise, false);
}
}
});
spinSets.addStyleName("field-amount");
spinSets.addListener(Events.OnClick, CustomListener.fieldOnClicked);
spinSets.setAllowNegative(false);
spinSets.setWidth(50);
spinSets.setIncrement(1);
spinSets.setMinValue(0);
spinSets.setMaxValue(30);
CommonUtils.setWarningMessages(spinSets);
spinSets.setEditable(true);
spinSets.setPropertyEditorType(Integer.class);
spinSets.setFormat(NumberFormat.getFormat("0"));
spinSets.setEmptyText(AppController.Lang.Sets());
if(exercise.getSets() != 0) {
spinSets.setValue(exercise.getSets());
}
return spinSets;
}
protected ImageButton getDragButton() {
ImageButton btnDrag = new ImageButton(AppController.Lang.ChangeExerciseOrder(), MyResources.INSTANCE.iconBtnDrag());
btnDrag.setTabIndex(-1);
btnDrag.setData("view", this);
//drag source
DragSource source = new DragSource(btnDrag) {
@Override
protected void onDragStart(DNDEvent event) {
try {
super.onDragStart(event);
final ExerciseView view = (ExerciseView)event.getComponent().getData("view");
//set exercise name as label and ExerciseView as data
HTML html = new HTML();
html.setHTML(AppController.Lang.MoveTarget(CommonUtils.getExerciseName(view.exercise.getName())));
event.setData(view);
event.getStatus().update(El.fly(html.getElement()).cloneNode(true));
} catch (Exception e) {
Motiver.showException(e);
}
}
};
source.setGroup(Constants.DRAG_GROUP_WORKOUT + exercise.getWorkoutId());
return btnDrag;
}
@Override
public ExerciseModel getExercise() {
return exercise;
}
@Override
public void onStop() {
if(box != null && box.isVisible()) {
box.close();
}
}
@Override
public void setHandler(ExerciseHandler handler) {
this.handler = handler;
}
@Override
public void setModel(ExerciseModel exercise) {
this.exercise = exercise;
//update combo
if(comboName != null) {
comboName = addExerciseCombo();
containerName.removeAll();
containerName.add(comboName);
containerName.layout();
comboName.focus();
}
try {
//show/hide video link
boolean videoFound = false;
if(exercise.getName() != null && exercise.getName().getVideo().length() > 0) {
videoFound = true;
}
if(videoFound) {
btnVideo = new ImageButton(AppController.Lang.ShowTarget(AppController.Lang.Video().toLowerCase()), MyResources.INSTANCE.iconBtnVideo());
btnVideo.addListener(Events.OnClick, new Listener<BaseEvent>() {
@Override
public void handleEvent(BaseEvent be) {
handler.showVideo();
}
});
btnVideo.setVisible(false);
panelVideo.add(btnVideo);
}
else {
panelVideo.removeAll();
btnVideo = null;
}
} catch (Exception e) {
Motiver.showException(e);
}
}
@Override
public void setNameComboEnabled(boolean enabled) {
comboName.setEnabled(enabled);
}
/**
* Adds exercise search combo
*/
protected ComboBox<ExerciseNameModel> addExerciseCombo() {
final ComboBox<ExerciseNameModel> combo = new ComboBox<ExerciseNameModel>();
//custom editors so we see also target correctly
combo.setPropertyEditor(new ListModelPropertyEditor<ExerciseNameModel>() {
@Override
public ExerciseNameModel convertStringValue(String value) {
return store.findModel("fn", value);
}
@Override
public String getStringValue(ExerciseNameModel value) {
return CommonUtils.getExerciseName(value);
}
});
//set fullname to "fn" so we see target correctly
combo.getView().setModelProcessor(new ModelProcessor<ExerciseNameModel>() {
@Override
public ExerciseNameModel prepareData(ExerciseNameModel model) {
model.set("fn", CommonUtils.getExerciseName(model));
return model;
}
});
// proxy, reader and loader
RpcProxy<List<ExerciseNameModel>> proxy = new RpcProxy<List<ExerciseNameModel>>() {
@Override
protected void load(Object loadConfig, AsyncCallback<List<ExerciseNameModel>> callback) {
BasePagingLoadConfig config = (BasePagingLoadConfig)loadConfig;
String query = config.get("query").toString();
handler.query(query, callback);
}
};
ModelReader reader = new ModelReader();
BaseListLoader<ListLoadResult<ModelData>> loader = new BaseListLoader<ListLoadResult<ModelData>>(proxy, reader);
loader.addListener(Loader.BeforeLoad, new Listener<LoadEvent>() {
public void handleEvent(LoadEvent be) {
be.<ModelData>getConfig().set("start", be.<ModelData>getConfig().get("offset"));
}
});
loader.addListener(Loader.Load, new Listener<LoadEvent>() {
@SuppressWarnings("unchecked")
public void handleEvent(LoadEvent be) {
try {
//add "new exercise" to end of the list
BaseListLoadResult<ExerciseNameModel> list = (BaseListLoadResult<ExerciseNameModel>)be.getData();
final ExerciseNameModel model = new ExerciseNameModel(-1L, AppController.Lang.AddNew(AppController.Lang.Exercise().toLowerCase()), 0);
list.getData().add(model);
} catch (Exception e) {
Motiver.showException(e);
}
}
});
store = new ListStore<ExerciseNameModel>(loader);
combo.addStyleName("field-name");
if(exercise.getName() != null && exercise.getName().getId() != 0) {
combo.setValue(exercise.getName());
}
combo.setWidth(280);
combo.setValidationDelay(Constants.DELAY_SEARCH);
combo.setForceSelection(true);
combo.setMessageTarget("none");
combo.setDisplayField("fn");
combo.setMinChars(Constants.LIMIT_MIN_QUERY_WORD);
combo.setTemplate(nameTemplate);
combo.setStore(store);
combo.setEmptyText(AppController.Lang.EnterKeywordToSearchForExercises());
combo.setLoadingText(AppController.Lang.Loading());
combo.setHideTrigger(true);
combo.setTriggerAction(TriggerAction.ALL);
combo.setValidateOnBlur(false);
//save typed value for when adding new name
combo.addListener(Events.BeforeSelect, new Listener<FieldEvent>() {
@Override
public void handleEvent(FieldEvent be) {
ComboBox<ExerciseNameModel> cb = ((ComboBox<ExerciseNameModel>)be.getSource());
cb.setData("val", combo.getRawValue());
}
});
//update model when valid value
combo.addListener(Events.Valid, new Listener<FieldEvent>() {
@SuppressWarnings("unchecked")
@Override
public void handleEvent(FieldEvent be) {
try {
ComboBox<ExerciseNameModel> cb = ((ComboBox<ExerciseNameModel>)be.getSource());
//if selected something from the list
if(cb.getValue() != null) {
ExerciseNameModel mo = cb.getValue();
//if user clicked "add new" value
if(mo.getId() == -1) {
String val = combo.getData("val");
handler.newNameEntered(val);
}
//value selected from list
else if(handler != null) {
//only if changed
boolean changed = false;
if(exercise.getName() != null) {
if(exercise.getName().getId() != mo.getId()) {
changed = true;
}
}
else {
changed = true;
}
exercise.setName(mo);
if(changed) {
handler.saveData(exercise, true);
}
}
}
} catch (Exception e) {
Motiver.showException(e);
}
}
});
return combo;
}
}