# Step 2: Writing a problem Applet¶

A typical Mumie Problem Applet consists of three parts:
• Demo - Contains an example of the problem, often with a visualization
• Training - Contains tasks similar to the problem, where students can train and check their solution
• Problem - The homework problem

The applet document we created in the Preparation Step already has the three inner classes representing each tab. We only need to fill each tab in the constructor of the inner classes.

Before we begin let's take a look at the init method generated from the template:

 1 public void init() {
2  try {
3    super.init();
4    setTitle(getMessage("applet.title"));
5    demo = new DemoPanel();
7    training = new TrainPanel();
9    if (isHomeworkMode()) {
10       problem = new ProblemPanel(this);
12    }
13    getCenterTabbedPanel().remove(0);
15  } catch (Throwable t) {
16    reportError(t);
17  }
18}


Here we can see that the mathletfactory library already comes with some standard UI Environment such as a Title Panel(line 4), a Tab environment(line 6,8,11), reset button(14) and a multi language support (see getMessage() at line 4).

Messages are currently held in a text file and can be edited in the Message Tab of the Mumie Java Editor.

## Previewing the applet¶

While writing an applet you might sometimes want to look at the result of your code, for example when you're trying to layout some text or UI-components. In MIAU you can do this by clicking the Compile Button, followed by the Preview Button. Note that the preview does not start the applet in a homework mode, so that you will only see the Demo and Training Tabs.

To start the Applet in the homework mode, you go to the problem tex, and click on the View Problem button. Note: If you haven't compile the problem tex, you have to do this and click on New Data to generate a datasheet for the problem.

## Demo¶

In the Demo part of this example we will display a canvas which contains the function, a point which represents the x-Value and a line that connects the x-Point with the functions value for this x.

We will also display the real value of the function in the control panel, together with a slider where the students can adjust the x value.

Further, the function f and the variable name of the function should be parametrized, so that we can control the function from the tex-file.

Let's now focus on the generated inner class DemoPanel. The first thing we are going to do is changing the SuperClass declaration of the DemoPanel from ControlPanel to JPanel.


class DemoPanel extends JPanel{
...
}


Next we add the super constructor call into the Constructor with BorderLayout as the LayoutManager.

public DemoPanel(){
super(new BorderLayout); //calls the super constructor and set the LayoutManager to BorderLayout
}


This step is needed to have a better display/layout of the canvas. The canvas should be added into the CENTER-part and the ControlPanel containing the text will be added into the SOUTH-part of the BorderLayout.

### 1. Declaring the objects needed¶

Looking at the image above we will need the following Objects:
• MMG2DCanvas - The 2D canvas, used to display the Graphical visualization
• MMCoordinateSystem - the 2Dimensional coordinate system displayed on the canvas
• MMFunctionDefinedByOp - the function f(x). Can be displayed graphically in a canvas and textually in a Container.
• MMAffine2DPoint - the orange point that visualizes the current x value.
• MMAffine2DLineSegment - a line segment connecting the point with the function value.
• ControlPanel - The container south of the canvas, used to display all textual visualization
• MMDouble - Real number. We will need two instances of this class, one for x and one for f(x).
• MMDoubleSliderPanel - A slider which is connected to an MMDouble.

For the Parametrization of the function and it's variable name, we also need two String instances.
The class DemoPanel should then start with the following Instance-Variable declarations:

  //parameter objects
String functionString;
String varName;
//other objects
MMG2DCanvas canvas;
MMCoordinateSystem cs;
MMFunctionDefByOp function;
MMAffine2DPoint xPoint;
MMAffine2DLineSegment line;
ControlPanel control;
MMDouble x;
MMDouble y;
MMDoubleSliderPanel xSlider;


So now that the instance variables needed have been declared, we can proceed to the constructor of the DemoPanel where we will initialize the instances and layout the objects.

### 2. Reading the parameter¶

The next thing to do is to read the Parameters for the function and it's variable name. To do this we can use the method getParameter(String name) and getParameter(String name, String defaultValue). With the second alternative, the defaultValue will be returned if the parameter with the given name does not exist.

functionString = getParameter("demoFunction", "x|_.2+2x+1");
varName = getParameter("varName","x");


### 3. Initializing the objects¶

Now we initialize the objects as follows:

//create canvas objects
canvas = new MMG2DCanvas();
cs = new MMCoordinateSystem();
cs.setXAxisLabel(varName); // sets the label of the x-axis from the default ("x") to varName
function = new MMFunctionDefByOp(NUMBER_CLASS, functionString);
function.setVariableId(varName);
xPoint = new MMAffine2DPoint(NUMBER_CLASS,0,0);
double yValue = function.evaluate(0);
line = new MMAffine2DLineSegment(NUMBER_CLASS, 0, 0, 0, yValue);
//create control panel objects
control = new ControlPanel();
x = new MMDouble(0); //start value: x = 0
x.setLabel(varName+" = ");//set the label which will be displayed when this object is added to a container
y = new MMDouble(yValue);
xSlider = x.getAsSlider();//get the slider representation of x, everytime the slider is moved, the x value will be updated.
xSlider.setLeftBound(-5);//set the left bound (minimum) of the slider
xSlider.setRightBound(5);//set the right bound (maximum) of the slider


Some MMObjects requires a number class arguments which defines in which number class the object should be calculated in. In the previous code we use the constants NUMBER_CLASS which should be declared and initialized right after the declaration of the applet class.

public class QuadraticFunctions extends NoCanvasApplet implements MultipleTasksIF{
private static final Class NUMBER_CLASS = MDouble.class;//use real numbers
...


### 4. Setting some display properties¶

Every MMObject has a [[doc:manuals:mathletfactory_lib:mmobjects:display_properties|DisplayProperties]] instance which we can access with the method getDisplayProperties();
In the DisplayProperties we can set some display informations such as the color, borderColor, borderWidth, transparency etc.

//set display properties
function.setColor(Color.green.darker());//set the color of the function to dark green
function.getDisplayProperties().setBorderWidth(2);//border width = 2px.
xPoint.setColor(Color.orange);//point color = orange
xPoint.setBorderColor(Color.black);//point border-color = black
line.setColor(Color.blue);// line color = blue
((LineDisplayProperties)line.getDisplayProperties()).setLineWidth(0.1); //set line width to 0.1


### 5. Displaying the objects¶

#### Adding objects to canvas¶

To add objects to a canvas we can simply call the canvas.addObject(MMCanvasObjectIF object) method.

canvas.addObject(cs);


We can set the default scene of the canvas by specifiying the world dimension of it's World to Screen Transformation Handler. With the following code for example, we specify that per default the canvas world width and height is 10. This means that we will at least see the coordinate system from -5 to 5 in both x and y axis.

//set the default scene
canvas.getW2STransformationHandler().setUniformWorldDim(10);


#### Adding Objects to the ControlPanel¶

To add an MMObject into a ControlPanel or any other Container, we can use the getAsContainerContent() method which returns an instance of JComponent. We can then add this JComponent instance directly to any Container.

control.add(function.getAsContainerContent());//gets the UI Component of the function and add it to the ControlPanel
control.insertLineBreak();// ends the current line
control.insertLineBreak();
control.addText("f (");//add a text to the current line


#### Add the Canvas and ControlPanel to the DemoPanel¶

At last we need to add the Canvas and the ControlPanel to the DemoPanel:

//add the canvas and control Panel
add(canvas, BorderLayout.CENTER);//add the canvas to the center
add(control, BorderLayout.SOUTH);//add the canvas to the south


### 6. Setting dependencies between MMObjects¶

Now the Demo-Part should be displayed correctly, but the slider still has no function.
We need to set a dependency between the x-Value and the function value f(x). This can be realized by adding a dependencyAdapter to y as follows:

//first create the adapter
public void doUpdate(){//this method will be executed when x is updated
double xValue = x.getDouble(); // get the currect x value
double yValue = function.evaluate(xValue);//calculate the y value
y.setDouble(yValue);//set the value to y
y.render();//call render to repaint the UI-Component

xPoint.setX(xValue);//update the x coordinate of xPoint
xPoint.render();//call render to refresh the point

line.setInitialPoint(xPoint);//update the initial point
line.setEndPoint(xValue, yValue);//update the end point
line.render();

canvas.repaint();//repaint the canvas
}
};
//then we register the dependency between x and y.
y.dependsOn(x, adapter);//now everytime x is updated the adapter's doUpdate will be executed.


### 7. Implementing the reset method¶

In the reset method we will define the things that should happen whenever the user clicks on the reset button while the demo tab is shown.
Normally we will reset the demo to it's initial state.
Here for example, we will set the scene of the canvas into the initial state and reset the x value back to 0.

public void reset() {
x.setDouble(0);//set x to 0
x.render();//refresh
DependencyUpdater.performActionCycleFromObject(x);//update all objects depending on x
canvas.getW2STransformationHandler().setUniformWorldDim(10);//reset the scene of the canvas
canvas.renderScene();//render the scene
canvas.repaint();//repaint
}


### Controlling the Function from the Problem-Tex¶

Now you can control the function expression and variable names from the tex-Source using the \param{name}{value} command:

\begin{execute}
\begin{applet}{applet1}
\param{appletWidth}{500}
\param{appletHeight}{500}
\param{demoFunction}{t|_.2+3t+1}
\param{varName}{t}
\end{applet}
\end{execute}


Try to change the values of the parameters demoFunction and varName. After editing the file you can compile the tex source and click on View Problem to start the applet. The demo should be started with the function defined on the tex source.

## Training¶

Now we are going to continue with the training part. Here we will again display the function on a canvas. On the control panel we are going to display the problem similar to the homework and let the students check their solution and generate new task as long as she/he likes.

Because we want to display a canvas like in the Demo part, we will similarly begin with replacing the superclass from ControlPanel to JPanel.

class TrainPanel extends JPanel {
//...
public TrainPanel(){
super(new BorderLayout());
//...
}
}


### 1. Declaring the objects needed¶

We will then start by declaring the objects needed:

 1 private static final String CORRECT = "<html><font color=green><b>\u2713</b></font></html>";//unicode for check mark
2 private static final String FALSE = "<html><font color=red><b>\u0192</b></font></html>";//unicode for false mark
3
4 //task parameter f(x) = x|_.2 + b + c
5 int x1,x2,b,c; // x1 and x2 are the roots, b and c will be calculated
6 MMDouble d; //x for task b
7
8 MMG2DCanvas canvas;
9 MMFunctionDefByOp function;
10 MMCoordinateSystem cs;
11
12 ControlPanel control;//the outer control panel
14 ControlPanel root2Panel;//wrapper for the root2 textfield
15 JComboBox numberOfRoots;//drop down list
16 MMDouble root1,root2;//real number editable field for root1 and root2
17 MMDouble answer;//real number editable field for the answer of subtask b
18
19 JButton check, newTask;//buttons for check
20 JLabel label1,label2;//correction label for task a and task b.


In the first two lines we define two String constants which will be used as the value of the correction labels (see line 20).

### 2. Initializing the objects¶

//init canvas objects
canvas = new MMG2DCanvas();
cs = new MMCoordinateSystem();
function = new MMFunctionDefByOp(NUMBER_CLASS, "x");
//init control panel objects
d = new MMDouble(0);
label1 = new JLabel();
label2 = new JLabel();
root1 = new MMDouble();
root1.setEditable(true);//set as editable
root1.setEdited(false);
//The input fields should be at the unedited state, a red question mark will be displayed
//We repeat this for the other number fields
root2 = new MMDouble();
root2.setEditable(true);
root2.setEdited(false);
answer = new MMDouble();
control = new ControlPanel();
taskPanel = new ControlPanel();
//root2Panel is a wrapper for root2 which will only be visible if the user select 2 as number of roots
root2Panel = new ControlPanel();
root2Panel.addText(",");// add the "," text because the roots will be displayed as a set
root2Panel.setVisible(false);//at the beginning root2Panel is hidden
String[] entries = new String[]{"1","2"};//entries for the drop down list
numberOfRoots = new JComboBox(entries);
numberOfRoots.setSelectedIndex(0);//at the beginning, selected index = 0

check = new JButton("Check");//Button for checking the result



After we initialized the objects we can generate the task. Normally we would want to have a default task at the start. So we will create a method that private getNewTask(boolean default) as follows:

private void getNewTask(boolean defaultTask){
label1.setText(""); //reset the correction label for task a
label2.setText(""); //reset the correction label for task b
//set the roots for the default task here
x1 = -2;
x2 = 3;
}else{
//otherwise select the roots randomly
x1 = -5+(int)(Math.random()*11); // x1 \in [-5,5]
x2 = -5+(int)(Math.random()*11); // x1 \in [-5,5]
}
//f(x) = (x-x1)*(x-x2) = x|_.2 - (x1+x2)*x + x1*x2
b = -(x1 + x2); //calculate b
c = x1 * x2;//calculate c

//Generate value for d, make sure d is not one of the roots
double dValue = x1;
if (dValue == x1 || dValue == x2){
dValue = -5+(int)(Math.random()*11);
}
d.setDouble(dValue);
d.render();
function.setOperation("x|_.2+"+b+"x+"+c);//set the operation as f(x) = x|_.2+bx+c
function.render();
canvas.repaint();
}


We can call this method at the constructor and whenever the user clicks on the reset button with the argument defaultTask = true, and also everytime the user clicks on the new task button with defaultTask = false.

### 3. Adding ActionListeners to the Buttons and drop down list¶

//adding action listeners
@Override
public void actionPerformed(ActionEvent e) {
check();//call the check method
}
});
@Override
public void actionPerformed(ActionEvent e) {
}
});
@Override
public void actionPerformed(ActionEvent e) {
if(numberOfRoots.getSelectedIndex()==0){
root2Panel.setVisible(false); //only one root, hide root2Panel
}else{
root2Panel.setVisible(true);//show root2Panel
}
}
});


### 4. Checking the result (Training Corrector)¶

private void check(){
boolean correct = false;//answer is correct?
boolean x1Correct = false;//x1 is in answer?
boolean x2Correct = false;//x2 is in answer?
boolean isEdited = false;//are the answers edited?
if(x1!=x2){// 2 different roots were generated
isEdited = root1.isEdited() && root2.isEdited(); //check if both roots are edited
x1Correct = equalCheck(x1, root1.getDouble()) || equalCheck(x1, root2.getDouble());
x2Correct = equalCheck(x2, root1.getDouble()) || equalCheck(x2, root2.getDouble());
correct = isEdited && x1Correct && x2Correct;//only correct if both roots are edited and both x1 and x2 are found in the answer
}else{
correct = x1 == root1.getDouble() && root1.isEdited();//correct if root1 is edited and root1 equals x1
}
if(correct){
label1.setText(CORRECT);//set the label to correct
}else{
label1.setText(FALSE);//set the label to false
}

double expected = function.evaluate(d.getDouble());//the expected solution
boolean correct2 = equalCheck(expected, answer.getDouble()) && answer.isEdited();
if(correct2){
label2.setText(CORRECT);
}else{
label2.setText(FALSE);
}
}

/**
* checks whether or not two real numbers are equal with 0.005 precision
* @return true if |a-b| < 0.005
*/
private boolean equalCheck(double a, double b){
return Math.abs(a-b)<0.005; //check with 0.005 precision
}


NOTE: Per default MMDouble instances are displayed with 2 decimal digits. In other words, the graphical representation of the user's input will be rounded to two decimal digits, but when you acess the actual number by calling getDouble() you will get the exact real numbers typed by the student. Therefore, in most cases we will have to use a 0.005 precision when comparing real numbers.

### 5.Displaying the Objects¶

//add objects to the canvas
canvas.getW2STransformationHandler().setUniformWorldDim(10);

taskPanel.addText("\\textbf{a). }");//using tex-commands to make the text bold
taskPanel.addText("Numbers of roots of f(x) = ");

//add objects to the controlPanel
control.addText("Consider the function: ");
control.insertLineBreaks(2);
control.insertLineBreaks(2);
control.insertTab();

//add canvas and control to the TrainPanel


### 7. Implementing the reset method¶

public void reset() {
canvas.getW2STransformationHandler().setUniformWorldDim(10);//reset canvas scene
canvas.renderScene();
canvas.repaint();
}


## Problem¶

In the problem part we will display the common task and according to which task the user currently selected the subtask specific problem and input fields.

When the user clicks on the save button, the answer of the current subtask will be written to the datasheet and submitted to the server.

### 1. Declaring the objects needed¶

First let's adjust the variables generated by the template.
In this example we have two subtasks so we can adjust the subtaskLabels and the subtaskcount:

private final String[] subtaskLabel = new String[]{"a","b"};//adjust if necessary
private final int subtaskCount = 2;//adjust if necessary


After that we can add other objects needed to display the problem.
//end of generated variables
private MMOpNumber polynomial;//to display the function
private MMInteger d;//the x-Value for task b
//user input
JComboBox numberOfRoots;//drop down list
MMDouble root1,root2;//real number editable fields for the root(s)
ControlPanel root2Panel;//wrapper for root2


Note: because we don't need to display the function in a canvas, we can use MMOpNumber to display the polynomial.

If we go to the constructor of the ProblemPanel generated by the template, we will see that at the first line the mumieExercise is created.

mumieExercise = new MumieExercise(this, subtaskCount);


This mumieExercise provides us with methods to read from the datasheet and write answers to the datasheet. To load a data we use the method loadElement(String path, MathMLSerializeable object). Path defines the datasheet path where the data is stored, and object is the target where the data should be load into. In the following code we will load the polynomial and the integer d from the problem:
//load the problem from the datasheet
polynomial = new MMOpNumber();
d = new MMInteger();


After loading the polynomial we can display it on the ProblemPanel. Because the ProblemPanel is an extension of ControlPanel, we can use all the methods of ControlPanel directly.

addText("Consider the function");
insertLineBreak();
insertLineBreaks(2);


Now displaying the other should be no problem since we have done this in the TrainingPanel:

 1 for (int i = 0; i < subtaskCount; i++) {
2   subtasks[i] = new ControlPanel();
7 }
9 root1 = new MMDouble();
10 root1.setEditable(true);
11 root1.setEdited(false);
12 root2 = new MMDouble();
13 root2.setEditable(true);
14 root2.setEdited(false);
15
16 root2Panel = new ControlPanel();
19 root2Panel.setVisible(false);
20
21 numberOfRoots = new JComboBox(new String[]{"1","2"});
22 numberOfRoots.setSelectedIndex(0);
23 numberOfRoots.addActionListener(new ActionListener() {
24   public void actionPerformed(ActionEvent e) {
25     if(numberOfRoots.getSelectedIndex()==0){
26       root2Panel.setVisible(false);
27     }else{
28       root2Panel.setVisible(true);
29     }
30   }
31 });
42 subtasks[0].insertTabs(3);//add some space to make the subtask panel wider
44 answer = new MMDouble();
49 subtasks[1].insertTabs(4);//add some space to make the subtask panel wider
50
52 insertLineBreak();


### 3. Implementing the generated methods¶

Here we defined the path where the answers will be stored in the datasheet and write the answer to the datasheet. To write the answer we use the method setAnswer(int subtask, String path, MathMLSerializable object);

public boolean collectAnswers() {
mumieExercise.setAnswer(1, "numOfRoots", new MInteger(numberOfRoots.getSelectedIndex()+1));
mumieExercise.setAnswer(1, "root1", root1);//save the first root
if(numberOfRoots.getSelectedIndex()==1){
mumieExercise.setAnswer(1, "root2", root2);//save the second root (if exists)
}
return true;
}


Everytime the student start the applet, previously saved questions should be loaded from the datasheet. We can use the method userElementExists(int subtask, String path) to check wether or not the specified answer exist, and to load the answer we can use the method loadUserElement(int subtask, String path, MathMLSerializable target).

public void loadAnswers() {
if(mumieExercise.userElementExists(1, "numOfRoots")){//check if the answer exists
MInteger temp = new MInteger();//temporary object for numOfRoots
numberOfRoots.setSelectedIndex(temp.getIntValue()-1);
if (numOFRoots == 2){//check if numOFRoots == 2
root2Panel.setVisible(true);//show root2
}
root1.render();//refresh the input field
root2.render();//refresh the input field
}
}
}


In this example we don't need to change the generated implementation of selectTask. This methods simply shows the current selected subtask panel.

#### reset()¶

In the reset method of a problem panel we usually reset all input fields of the current subtask into the unedited state.

public void reset() {
root1.setEdited(false);
root1.render();
root2.setEdited(false);
root2.render();
numberOfRoots.setSelectedIndex(0);
root2Panel.setVisible(false);
}