package org.geogebra.web.web.gui.dialog;
import java.util.ArrayList;
import java.util.TreeSet;
import org.geogebra.common.kernel.Macro;
import org.geogebra.common.kernel.StringTemplate;
import org.geogebra.common.kernel.geos.GeoBoolean;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.main.App;
import org.geogebra.common.main.Feature;
import org.geogebra.common.main.GeoElementSelectionListener;
import org.geogebra.common.main.Localization;
import org.geogebra.common.util.Assignment;
import org.geogebra.common.util.Assignment.Result;
import org.geogebra.common.util.lang.Unicode;
import org.geogebra.common.util.AsyncOperation;
import org.geogebra.common.util.BoolAssignment;
import org.geogebra.common.util.Exercise;
import org.geogebra.common.util.GeoAssignment;
import org.geogebra.web.html5.main.AppW;
import org.geogebra.web.web.css.GuiResources;
import org.geogebra.web.web.gui.app.GGWToolBar;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
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.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.safehtml.shared.SafeUri;
import com.google.gwt.safehtml.shared.UriUtils;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
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.ListBox;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
/**
* Dialog for editing an {@link Exercise}
*
* @author Christoph
*
*/
public class ExerciseBuilderDialog extends DialogBoxW implements ClickHandler,
GeoElementSelectionListener, ChangeHandler {
/** application */
protected AppW app;
private Exercise exercise;
private VerticalPanel mainWidget;
private FlowPanel bottomWidget;
private Button btApply, btTest;
private FlexTable assignmentsTable;
private FlexTable checkAssignmentsTable;
private ListBox addList;
private ArrayList<Object> addListMappings; // TODO? Override add, remove,...
// instead of relying on calling
// updateAddList each time
private Localization loc;
/**
* Brings up a new ExerciseBuilderDialog
*
* @param app
* application
*/
public ExerciseBuilderDialog(App app) {
super(false, false, null, ((AppW) app).getPanel());
if (app.has(Feature.DIALOGS_OVERLAP_KEYBOARD)) {
setOverlapFeature(true);
}
this.app = (AppW) app;
this.loc = app.getLocalization();
exercise = app.getKernel().getExercise();
addListMappings = new ArrayList<Object>();
createGUI();
}
@Override
public void setVisible(boolean flag) {
super.setVisible(flag);
update();
if (flag) {
app.setMoveMode();
if (!app.getSelectionManager().getSelectionListeners()
.contains(this)) {
app.getSelectionManager().addSelectionListener(this);
}
} else {
app.getSelectionManager().removeSelectionListener(this);
}
}
@Override
public void center() {
app.setMoveMode();
if (!app.getSelectionManager().getSelectionListeners().contains(this)) {
app.getSelectionManager().addSelectionListener(this);
}
super.center();
}
@Override
public void hide() {
app.getSelectionManager().removeSelectionListener(this);
super.hide();
}
private void createGUI() {
getCaption().setText(loc.getMenu("Exercise.CreateNew"));
setWidget(mainWidget = new VerticalPanel());
assignmentsTable = new FlexTable();
createAssignmentsTable();
checkAssignmentsTable = new FlexTable();
checkAssignmentsTable.setVisible(false);
mainWidget.add(assignmentsTable);
mainWidget.add(checkAssignmentsTable);
addList = new ListBox();
addList.addChangeHandler(this);
addList.setStyleName("submenuContent");
buildAddListMappings();
mainWidget.add(addList);
mainWidget.add(bottomWidget = new FlowPanel());
bottomWidget.setStyleName("DialogButtonPanel");
btApply = new Button(loc.getMenu("OK"));
btApply.addClickHandler(this);
btApply.getElement().getStyle().setMargin(3, Style.Unit.PX);
addCancelButton();
btTest = new Button(loc.getMenu("Test"));
btTest.addClickHandler(this);
btTest.getElement().getStyle().setMargin(3, Style.Unit.PX);
if (exercise.getParts().size() == 0) {
btTest.setEnabled(false);
}
bottomWidget.add(btTest);
bottomWidget.add(btApply);
}
private void buildAddListMappings() {
addListMappings.clear();
for (int i = 0; i < app.getKernel().getMacroNumber(); i++) {
if (!exercise.usesMacro(i)) {
addListMappings.add(app.getKernel().getMacro(i));
}
}
TreeSet<GeoElement> geos = app.getKernel().getConstruction()
.getGeoSetConstructionOrder();
for (GeoElement geo : geos) {
if (geo instanceof GeoBoolean) {
if (!exercise.usesBoolean((GeoBoolean) geo)) {
addListMappings.add(geo);
}
}
}
updateAddList();
}
private void createAssignmentsTable() {
assignmentsTable.removeAllRows();
assignmentsTable.setWidget(0, 1, new Label(loc.getMenu("Tool")));
assignmentsTable.setWidget(0, 2,
new Label(loc.getMenu("HintForCorrect")));
assignmentsTable.setWidget(0, 3, new Label(loc.getMenu("Fraction")));
addAssignmentsTableRows();
}
private void addAssignmentsTableRows() {
for (Assignment assignment : exercise.getParts()) {
appendAssignmentRow(assignment);
}
}
private void appendAssignmentRow(final Assignment assignment) {
int row = assignmentsTable.getRowCount();
addAssignmentRow(assignment, row + 1);
}
private void addAssignmentRow(final Assignment assignment, int insertrow) {
int j = 0;
int row = (insertrow <= assignmentsTable.getRowCount()) ? assignmentsTable
.insertRow(insertrow) : insertrow;
Image icon = new Image();
icon.setUrl(getIconFile(assignment.getIconFileName()));
assignmentsTable.setWidget(row, j++, icon);
assignmentsTable.setWidget(row, j++,
new Label(assignment.getDisplayName()));
final TextBox textForSolvedAssignment = getHintTextBox(assignment,
Result.CORRECT);
assignmentsTable.setWidget(row, j++, textForSolvedAssignment);
final TextBox fractions = getFractionsLB(assignment, Result.CORRECT);
assignmentsTable.setWidget(row, j++, fractions);
Image delIcon = getDeleteIcon(assignment);
// assignment
assignmentsTable.setWidget(row, j++, delIcon);
Image editIcon = new Image(GuiResources.INSTANCE.menu_icon_edit());
editIcon.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
handleEditClick(assignment);
}
});
assignmentsTable.setWidget(row, j++, editIcon);
}
/**
* Brings up a new AssignmentEditDialog to edit the assignment and hides
* this ExerciseBuilderDialog
*
* @param assignment
* The Assignment to be edited.
*/
void handleEditClick(Assignment assignment) {
new AssignmentEditDialog(app, assignment, ExerciseBuilderDialog.this)
.center();
hide();
}
/**
* @param assignment
* the assignment for which the Listbox should set the fraction
* @param res
* the result of the assignment for which the Listbox should set
* the fraction
* @return a single select ListBox containing all possible fractions as
* defined in {@link Assignment#FRACTIONS} setting the fraction for
* a result in this assignment when they are changed
*/
// ListBox getFractionsLB(final Assignment assignment, final Result res) {
// final ListBox fractions = new ListBox();
// fractions.setMultipleSelect(false);
// for (int j = 0; j < Assignment.FRACTIONS.length; j++) {
// fractions.addItem(app.getKernel().format(
// Assignment.FRACTIONS[j] * 100,
// StringTemplate.defaultTemplate));
// }
//
// fractions.addChangeHandler(new ChangeHandler() {
//
// public void onChange(ChangeEvent event) {
// assignment.setFractionForResult(res,
// Assignment.FRACTIONS[fractions.getSelectedIndex()]);
// }
// });
//
// fractions.addAttachHandler(new Handler() {
//
// public void onAttachOrDetach(AttachEvent event) {
// fractions.setSelectedIndex(Arrays.binarySearch(
// Assignment.FRACTIONS,
// assignment.getFractionForResult(res)));
// }
// });
//
// return fractions;
// }
TextBox getFractionsLB(final Assignment assignment, final Result res) {
final TextBox fractions = new TextBox();
fractions.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
GeoNumberValue num = app.getKernel().getAlgebraProcessor()
.evaluateToNumeric(fractions.getText(), true);
if(num!=null){
assignment.setFractionForResult(res,
(num.getDouble() * 0.01));
fractions.setText(
num.toValueString(StringTemplate.defaultTemplate));
}
}
});
fractions.addAttachHandler(new Handler() {
@Override
public void onAttachOrDetach(AttachEvent event) {
fractions.setValue(app.getKernel().format(
assignment.getFractionForResult(res) * 100,
StringTemplate.defaultTemplate));
}
});
return fractions;
}
/**
* @param assignment
* the assignment for which the TextBox should set the hint
* @param res
* the result of the assignment for which the TextBox should set
* the hint
* @return a TextBox setting the hint for a result in this assignment it is
* changed
*/
TextBox getHintTextBox(final Assignment assignment, final Result res) {
final TextBox textForResult = new TextBox();
textForResult.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
assignment.setHintForResult(res, textForResult.getText());
}
});
textForResult.addAttachHandler(new Handler() {
@Override
public void onAttachOrDetach(AttachEvent event) {
textForResult.setText(assignment.getHintForResult(res));
}
});
return textForResult;
}
private Image getDeleteIcon(final Assignment assignment) {
Image delIcon = new Image(GuiResources.INSTANCE.menu_icon_edit_delete());
delIcon.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
handleAssignmentDeleteClick(event, assignment);
}
});
return delIcon;
}
private void update() {
exercise.notifyUpdate();
createAssignmentsTable();
updateAddList();
}
/**
* Handles the remove of an Assignment from the Exercise
*
* @param event
* the original event to determine which row should be removed
* from Table
* @param assignment
* the assignment to remove from the Exercise
*/
void handleAssignmentDeleteClick(ClickEvent event, Assignment assignment) {
if (assignment instanceof GeoAssignment) {
addListMappings.add(((GeoAssignment) assignment).getTool());
updateAddList();
} else if (assignment instanceof BoolAssignment) {
addListMappings.add(((BoolAssignment) assignment).getGeoBoolean());
updateAddList();
}
exercise.remove(assignment);
if (exercise.getParts().size() == 0) {
btTest.setEnabled(false);
}
assignmentsTable.removeRow(assignmentsTable.getCellForEvent(event)
.getRowIndex());
center();
}
private void updateAddList() {
addList.clear();
addList.addItem(loc.getMenu("AddToolOrBoolean"));
addList.addItem(loc.getMenu("Tool.CreateNew"));
for (Object obj : addListMappings) {
if (obj instanceof Macro) {
addList.addItem(((Macro) obj).getToolName());
} else if (obj instanceof GeoBoolean) {
addList.addItem(((GeoBoolean) obj).getNameDescription());
}
}
}
/**
* @param fileName
* of user defined tool
* @return {@link SafeUri} of Icon with fileName
*/
SafeUri getIconFile(String fileName) {
if (BoolAssignment.class.getSimpleName().equals(fileName)) {
return ((ImageResource) GGWToolBar.getMyIconResourceBundle()
.mode_showcheckbox_32()).getSafeUri();
} else if (!fileName.isEmpty()) {
String imageURL = app.getImageManager().getExternalImageSrc(
fileName);
if (imageURL != null) {
return UriUtils.fromString(imageURL);
}
}
return ((ImageResource) GGWToolBar.getMyIconResourceBundle()
.mode_tool_32()).getSafeUri();
}
@Override
public void onClick(ClickEvent e) {
Element target = e.getNativeEvent().getEventTarget().cast();
boolean isEditing = assignmentsTable.isVisible();
if (target == btApply.getElement()) {
if (isEditing) {
hide();
app.getActiveEuclidianView().requestFocusInWindow();
app.storeUndoInfo();
app.getKernel().notifyRepaint();
} else {
assignmentsTable.setVisible(true);
checkAssignmentsTable.setVisible(false);
addList.setVisible(true);
btTest.setText(loc.getMenu("Test"));
btApply.setText(loc.getMenu("OK"));
hide();
center();
if (!app.getSelectionManager().getSelectionListeners()
.contains(this)) {
app.getSelectionManager().addSelectionListener(this);
}
}
} else if (target == btTest.getElement()) {
if (isEditing) {
assignmentsTable.setVisible(false);
checkAssignmentsTable.setVisible(true);
addList.setVisible(false);
check();
btTest.setText(loc.getMenu("Check"));
btApply.setText(loc.getMenu("Back"));
hide();
center();
app.getSelectionManager().removeSelectionListener(this);
} else {
check();
}
}
}
private void check() {
if (exercise.getParts().size() > 0) {
exercise.checkExercise();
checkAssignmentsTable.removeAllRows();
int k = 1;
int i = 0; // keep track of the row we're in
checkAssignmentsTable.setWidget(i, k++,
new Label(loc.getMenu("Tool")));
checkAssignmentsTable.setWidget(i, k++,
new Label(loc.getMenu("Result.Exercise")));
checkAssignmentsTable.setWidget(i, k++,
new Label(loc.getMenu("HintForResult")));
checkAssignmentsTable.setWidget(i, k++,
new Label(loc.getMenu("Fraction")));
i++;
ArrayList<Assignment> parts = exercise.getParts();
for (int j = 0; j < parts.size(); j++, i++) {
final Assignment assignment = parts.get(j);
Image icon = new Image();
icon.setUrl(getIconFile(assignment.getIconFileName()));
k = 0;
checkAssignmentsTable.setWidget(i, k++, icon);
checkAssignmentsTable.setWidget(i, k++,
new Label(assignment.getDisplayName()));
checkAssignmentsTable.setWidget(i, k++, new Label(assignment
.getResult().name()));
String hint = assignment.getHint();
if (hint != null && hint.length() > 30) {
hint = hint.substring(0, hint.indexOf(" ", 25))
+ Unicode.ellipsis;
}
checkAssignmentsTable.setWidget(i, k++, new Label(hint));
checkAssignmentsTable.setWidget(
i,
k++,
new Label(app.getKernel().format(
assignment.getFraction() * 100,
StringTemplate.defaultTemplate)));
}
checkAssignmentsTable.setWidget(i, k = 0,
new Label(loc.getMenu("FractionTotal")));
checkAssignmentsTable.setWidget(
i,
k++,
new Label(app.getKernel().format(
exercise.getFraction() * 100,
StringTemplate.defaultTemplate)));
}
}
/**
* Adds a user defined tool to the exercise as well as the view
*
* @param macro
* the user defined tool which should be added to the exercise as
* well as the view
*/
void addAssignment(Macro macro) {
if (!exercise.usesMacro(macro)) {
Assignment a = exercise.addAssignment(macro);
appendAssignmentRow(a);
btTest.setEnabled(true);
center();
}
}
/**
* Adds an Assignment to the Exercise
*
* @param listBoxIndex
* the index of the Object to check for in addListMappings
*/
void addAssignment(int listBoxIndex) {
Object obj = addListMappings.remove(listBoxIndex);
if (obj instanceof Macro) {
addAssignment((Macro) obj);
} else if (obj instanceof GeoBoolean) {
addAssignment((GeoBoolean) obj);
}
addListMappings.remove(obj);
updateAddList();
}
@Override
public void geoElementSelected(GeoElement geo, boolean addToSelection) {
if (geo instanceof GeoBoolean) {
addAssignment(geo);
addListMappings.remove(geo);
updateAddList();
} else {
boolean isDependentObject = false;
for (GeoElement geoP : geo.getAllPredecessors()) {
isDependentObject |= geoP.isLabelSet();
}
if (isDependentObject && isVisible()) {
setVisible(false);
newTool();
}
}
}
private void addAssignment(GeoElement geo) {
if (geo instanceof GeoBoolean) {
GeoBoolean check = (GeoBoolean) geo;
if (!exercise.usesBoolean(check)) {
Assignment a = exercise.addAssignment(check);
appendAssignmentRow(a);
btTest.setEnabled(true);
center();
}
}
}
@Override
public void onChange(ChangeEvent event) {
int selectedIndex = addList.getSelectedIndex();
if (selectedIndex == 1) {
newTool();
} else if (selectedIndex > 1) {
addAssignment(selectedIndex - 2);
}
event.stopPropagation();
addList.setSelectedIndex(0);
}
private void newTool() {
setVisible(false);
ToolCreationDialogW toolCreationDialog = new ToolCreationDialogW(app,
new AsyncOperation<Macro>() {
@Override
public void callback(Macro macro) {
if (app.getKernel().getMacroID(macro) >= 0) {
addAssignment(macro);
}
ExerciseBuilderDialog.this.setVisible(true);
}
});
toolCreationDialog.center();
}
}