Compare commits

...

6 Commits

10 changed files with 214 additions and 61 deletions

55
Example.schema Normal file
View File

@ -0,0 +1,55 @@
// Kommentare (nur Ganzzeilig! werden daran erkannt, dass die Zeile mit // beginnt)
// :base gibt die Start-Punkte an und überschreibt die maximal Punkt, wenn ungleich 0
:base 0.0
// Count of decimal places
:decimals 1
// Design für Punkte. Pts ist dabei für die Punkte, PtsText für den Text. die Punkte zählen als Teil des Textes (<li style=PtsText><span style=Pts>'Punkte'</span>Text</li>)
// full -> best-Case
:fullPts color: #0f0; font-weight: bold;
:fullPtsText
// no -> worst-case
:noPts color: #f00; font-weight: bold;
:noPtsText
// partial -> alle verbleibenden Fälle. Sonderfall: Elemente die 0 Punkte bringen (egal was ausgewählt ist) haben auch diesen Fall!
:partialPts color: #fb0; font-weight: bold;
:partialPtsText
// Bei Checkbox-Multi-Select, das CSS für ausgewählt / nicht ausgewählt
:cbmsOk color: #080;
:cbmsFail color: #800;
// !header (|#|##|###|####|#####|######|*|_) Titel
!header # Test-Schema
// Einfache Checkboxen. Werden im Output versteckt, wenn nicht ausgewählt
[] -0.5 Punktabzug
[] +0.5 Bonus-Punkt
!header ## Sub-Header zum versteck-Test
[] -0.5 Punktabzug (uncheck um Sub-Header zu verstecken)
!header ## Sub-Header für Punkte
// Entweder-Oder-Radio-Boxen. Links von der Pipe ist die "bessere" Punktzahl. Die Pipe mit dem worst-case-Text ist optional.
\ 3.5 Nur ein Text
\ 1.0 Element existiert | Element existiert nicht
\ 2 Anderes Element existiert | Anderes Element fehlt
\ 1.5 Beispiel | fehlt
[] -0.5 Punktabzug weil is so.
!header ## Sub-Header für Punkte Multiline
// Radio-Box-Multiline, bei dem nur der Text der Ausgewählten Zeile angezeigt wird (und deren Punkte). Negative Punkte nicht unterstützt.
\\ 5.0
5.0 Volle Punkte
2.5 Halbe Punkte
0 Null Punkte
// Radio-Box-Multiline, bei dem der Basis-Text angezeigt wird und die ausgewählte Zeile in Klammern dahinter angezeigt wird (und deren Punkte). Negative Punkte nicht unterstützt.
\\ 5.0 Text der immer angezeigt wird. Dadurch wird der Text bei den Punkten zur Begründung
5.0 Volle Punkte
2.5 Halbe Punkte
0 Null Punkte
// Checkbox-Multiline, Zeigt den Basis-Text mit Punkten in der ersten Zeile an und zeigt die Checkbox auswahlen darunter an. Negative Punkte nicht unterstützt.
\\ 5.0 Checkbox-Multiselect "basis"-Text wird immer benötigt (Multi-Select anstatt Radio-Box. das kann nicht gemischt werden!)
[] 1.0 Ziel 1
[] 1.0 Ziel 2
[] 2.0 Ziel 3
[] 0.5 Ziel 4
[] 0.5 Ziel 5

11
Readme.md Normal file
View File

@ -0,0 +1,11 @@
# Korrektur Helper
für Schema-Datei Beispiel siehe Example.schema
#### Programm-Start
```
java -jar KorrekturHelper.jar <Schema-Datei>
```
Wenn `<Schema-Datei>` nicht angegeben ist, dann wird die interne Test Datei genutzt

View File

@ -16,6 +16,8 @@ public class HtmlContext
public String cbmsFail = "";
/** Style for checked Boxes in CheckboxMultiSelect */
public String cbmsOk = "";
/** Number of Decimals on Points */
public int decimals = 1;
/** Style for the Points, when the best case Points have been achieved */
public String fullPtsStyle = "";
/** Style for the Text, when the best case Points have been achieved */
@ -24,6 +26,7 @@ public class HtmlContext
public MathContext mc = MathContext.DECIMAL32;
/** Style for the Points, when the worst case Points have been achieved */
public String noPtsStyle = "";
/** Style for the Text, when the best case Points have been achieved */
public String noPtsTextStyle = "";

View File

@ -81,25 +81,26 @@ public class KorrekturHelper
kh.frm.setVisible(true);
}
private static LinkedHashSet<Node> parseNodes(String[] lines, int[] lineMap) throws IOException
private static LinkedHashSet<Node> parseNodes(String[] lines, int[] lineMap, HtmlContext hc) throws IOException
{
var roots = new LinkedHashSet<Node>();
var path = new LinkedList<Node>();
for (var li = 0; li < lines.length; li++)
{
var line = lines[li];
if (getIndent(line) != path.size())
{ throw new IllegalStateException("Indentation error on Line" + lineMap[li] + "!"); }
var cmd = line.substring(path.size()).split("\t", 2)[0];
System.out.println(indent(path.size()) + lineMap[li] + " - " + cmd + " - " + line);
var n = switch (cmd)
{
case "!header": // !header # Test-Schema
yield new HeaderNode(line.substring(path.size()));
case "[]": // [] -0.5 Punktabzug
yield new CheckboxNode(line.substring(path.size()));
yield new CheckboxNode(line.substring(path.size()), hc);
case "\\": // \ 1.0 Element existiert | Element existiert nicht
yield new EitherNode(line.substring(path.size()));
yield new EitherNode(line.substring(path.size()), hc);
case "\\\\": // \\ MultiSelects
if (lines.length <= li)
@ -108,8 +109,6 @@ public class KorrekturHelper
var i = li + 1;
for (; i < lines.length; i++)
{
System.out.println(indent(path.size() + 1) + lineMap[i] + " - " + cmd + " - " + lines[i]);
System.out.println(getIndent(lines[i]) + " - " + path.size());
if (getIndent(lines[i]) != path.size() + 1)
{
break;
@ -119,19 +118,19 @@ public class KorrekturHelper
if (lines[li + 1].trim().startsWith("[]"))
{
li = i - 1;
yield new CheckboxMultiSelectNode(sb.toString());
yield new CheckboxMultiSelectNode(sb.toString(), hc);
}
else
{
li = i - 1;
yield new RadioMultiSelectNode(sb.toString());
yield new RadioMultiSelectNode(sb.toString(), hc);
}
case "":
throw new IOException("Indentation Error on Line " + lineMap[li] + "!");
default:
throw new IOException("Unknown Command: " + cmd);
throw new IOException("Unknown Command on Line " + lineMap[li] + ": " + cmd);
};
if (!path.isEmpty())
{
@ -201,6 +200,10 @@ public class KorrekturHelper
hc.basePoints = new BigDecimal(split[1]);
break;
case ":decimals":
hc.decimals = Integer.parseInt(split[1]);
break;
case ":fullPts":
hc.fullPtsStyle = split.length > 1 ? split[1] : "";
break;
@ -240,7 +243,7 @@ public class KorrekturHelper
lineMap.add(lineNumber);
}
}
return parseNodes(nodeLines.toArray(String[]::new), lineMap.stream().mapToInt(i -> i).toArray());
return parseNodes(nodeLines.toArray(String[]::new), lineMap.stream().mapToInt(i -> i).toArray(), hc);
}
private static BigDecimal recursiveAchievedPoints(LinkedHashSet<Node> nodes, MathContext mc)
@ -297,13 +300,16 @@ public class KorrekturHelper
}
private final JButton btn_toClipboard;
private final JPanel contentPanel, menuPanel;
private String currentHtml = "";
/** The JFrame for this {@link KorrekturHelper} */
public final JFrame frm;
private final HtmlContext hc;
private HtmlContext hc;
private final LinkedHashSet<Node> nodes;
private boolean inReset = false;
private LinkedHashSet<Node> nodes;
/**
* @param nodes Nodes
@ -311,16 +317,13 @@ public class KorrekturHelper
*/
public KorrekturHelper(LinkedHashSet<Node> nodes, HtmlContext hc)
{
this.nodes = nodes;
this.hc = hc;
frm = new JFrame("Pinggers Korrektur Helper");
frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
var contentPanel = new JPanel(new BorderLayout(), true);
contentPanel = new JPanel(new BorderLayout(), true);
/*
* Menu Panel
*/
var menuPanel = new JPanel(true);
menuPanel = new JPanel(true);
menuPanel.setLayout(new BoxLayout(menuPanel, BoxLayout.X_AXIS));
var jtb_aot = new JToggleButton("Always on Top");
@ -334,27 +337,20 @@ public class KorrekturHelper
btn_toClipboard = new JButton("Copy to Clipboard");
btn_toClipboard.addActionListener(e -> copyToClipboard());
menuPanel.add(btn_toClipboard);
var btn_drop = new JButton("Open Schema");
btn_drop.addActionListener(e -> fileDialog());
menuPanel.add(btn_drop);
var btn_reset = new JButton("Reset");
btn_reset.addActionListener(e -> this.nodes.forEach(Node::reset));
btn_reset.addActionListener(e -> reset());
menuPanel.add(btn_reset);
contentPanel.add(menuPanel, BorderLayout.NORTH);
/*
* Node-Panel with Scrollpane
*/
var panel = new JPanel(true);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
var jsp = new JScrollPane(panel);
Consumer<Node> c = n -> verifyClipboard();
for (var n : nodes)
{
n.addChangeListener(c);
panel.add(n.getComponent());
}
contentPanel.add(jsp, BorderLayout.CENTER);
setNodes(nodes, hc);
frm.setContentPane(contentPanel);
frm.pack();
if (frm.getHeight() > 900)
{
frm.setSize(frm.getWidth(), 900);
}
}
/**
@ -378,11 +374,75 @@ public class KorrekturHelper
btn_toClipboard.setBackground(Color.GREEN);
}
private void fileDialog()
{
var x = new JFileChooser();
if (x.showOpenDialog(frm) == JFileChooser.APPROVE_OPTION)
{
var f = x.getSelectedFile();
try (
var in = new FileInputStream(f);
var isr = new InputStreamReader(in, StandardCharsets.UTF_8);
var br = new BufferedReader(isr, 1024 * 1024)
)
{
var hc = new HtmlContext();
var n = parseSchema(br, hc);
setNodes(n, hc);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
/**
* Resets the Input Mask
*/
public synchronized void reset()
{
inReset = true;
this.nodes.forEach(Node::reset);
inReset = false;
verifyClipboard();
}
/**
* Set the Nodes and {@link HtmlContext}
*
* @param nodes nodes
* @param hc context
*/
public void setNodes(LinkedHashSet<Node> nodes, HtmlContext hc)
{
this.nodes = nodes;
this.hc = hc;
/*
* Node-Panel with Scrollpane
*/
var panel = new JPanel(true);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
var jsp = new JScrollPane(panel);
jsp.getVerticalScrollBar().setUnitIncrement(16);
Consumer<Node> c = n -> verifyClipboard();
for (var n : nodes)
{
n.addChangeListener(c);
panel.add(n.getComponent());
}
contentPanel.removeAll();
contentPanel.add(menuPanel, BorderLayout.NORTH);
contentPanel.add(jsp, BorderLayout.CENTER);
}
/**
* Called on change of data or when the System signifies, the Clipboard changed
*/
public synchronized void verifyClipboard()
{
if (inReset)
{ return; }
var sb = new StringBuilder();
recursiveToHtml(nodes, sb, hc, 0);
currentHtml = sb.toString();

View File

@ -3,13 +3,13 @@
:base 0.0
// Design für Punkte. Pts ist dabei für die Punkte, PtsText für den Text. die Punkte zählen als Teil des Textes (<li style=PtsText><span style=Pts>'Punkte'</span>Text</li>)
// full -> best-Case
:fullPts color: #0f0;
:fullPts color: #0f0; font-weight: bold;
:fullPtsText
// no -> worst-case
:noPts color: #f00;
:noPts color: #f00; font-weight: bold;
:noPtsText
// partial -> alle verbleibenden Fälle. Sonderfall: Elemente die 0 Punkte bringen (egal was ausgewählt ist) haben auch diesen Fall!
:partialPts color: #fb0;
:partialPts color: #fb0; font-weight: bold;
:partialPtsText
:cbmsOk color: #080;
:cbmsFail color: #800;

View File

@ -11,12 +11,13 @@ public class Utils
/**
* formats Points for Users
*
* @param bd the {@link BigDecimal} to format
* @param bd the {@link BigDecimal} to format
* @param decimals decimals
* @return the formatted String (1 decimal place)
*/
public static String formatPoints(BigDecimal bd)
public static String formatPoints(BigDecimal bd, int decimals)
{
return String.format("%.1f", bd.doubleValue());
return String.format("%." + decimals + "f", bd.doubleValue());
}
/**

View File

@ -25,8 +25,9 @@ public class CheckboxMultiSelectNode extends AbstractNode
/**
* @param config the config string to parse
* @param hc {@link HtmlContext}
*/
public CheckboxMultiSelectNode(String config)
public CheckboxMultiSelectNode(String config, HtmlContext hc)
{
var lines = config.split("\n");
if (lines.length <= 1)
@ -58,7 +59,7 @@ public class CheckboxMultiSelectNode extends AbstractNode
{ throw new IllegalArgumentException("Expected []"); }
points[i - 1] = new BigDecimal(s[1]);
text[i - 1] = s[2];
rbs[i - 1] = new JCheckBox(Utils.formatPoints(points[i - 1]) + "P - " + text[i - 1]);
rbs[i - 1] = new JCheckBox(Utils.formatPoints(points[i - 1], hc.decimals) + "P - " + text[i - 1]);
rbs[i - 1].addActionListener(cl);
content.add(rbs[i - 1]);
}
@ -124,7 +125,8 @@ public class CheckboxMultiSelectNode extends AbstractNode
var sb = new StringBuilder();
sb.append("<li").append(hc.styleText(achievedPoints(hc.mc), maxPoints)).append(">");
sb.append("<span").append(hc.stylePts(achievedPoints(hc.mc), maxPoints)).append(">");
sb.append(Utils.formatPoints(achievedPoints(hc.mc))).append(" / ").append(Utils.formatPoints(maxPoints));
sb.append(Utils.formatPoints(achievedPoints(hc.mc), hc.decimals)).append(" / ")
.append(Utils.formatPoints(maxPoints, hc.decimals));
sb.append("P</span> ");
sb.append(baseMsg);
sb.append("\n\t<ul style=\"list-style-type: none;margin-top: 0px;margin-bottom:0px\">\n");
@ -132,7 +134,7 @@ public class CheckboxMultiSelectNode extends AbstractNode
{
sb.append("\t\t<li").append(hc.cbx(rbs[i])).append(">");
sb.append(rbs[i].isSelected() ? "&checkmark; " : "&cross; ");
sb.append("(").append(Utils.formatPoints(points[i])).append("P) ");
sb.append("(").append(Utils.formatPoints(points[i], hc.decimals)).append("P) ");
sb.append(text[i]);
sb.append("</li>\n");
}

View File

@ -21,8 +21,9 @@ public class CheckboxNode extends AbstractNode
/**
* @param configString config String to parse
* @param hc {@link HtmlContext}
*/
public CheckboxNode(String configString)
public CheckboxNode(String configString, HtmlContext hc)
{
if (configString.contains("\n"))
{ throw new IllegalArgumentException("Bad config! Found line feed!"); }
@ -33,7 +34,7 @@ public class CheckboxNode extends AbstractNode
{ throw new IllegalArgumentException("Not a [] Node"); }
points = new BigDecimal(spl[1]);
message = spl[2];
cbx = new JCheckBox(Utils.formatPoints(points) + "P " + message);
cbx = new JCheckBox(Utils.formatPoints(points, hc.decimals) + "P " + message);
cbx.addActionListener(e -> onChange(this));
content.add(cbx);
}
@ -70,7 +71,7 @@ public class CheckboxNode extends AbstractNode
public String toResultHtml(HtmlContext hc)
{
return "<li" + hc.styleText(points.signum()) + "><span" + hc.stylePts(points.signum()) + ">"
+ Utils.formatPoints(achievedPoints(hc.mc)) + "P</span> " + message + "</li>";
+ Utils.formatPoints(achievedPoints(hc.mc), hc.decimals) + "P</span> " + message + "</li>";
}
}

View File

@ -25,8 +25,9 @@ public class EitherNode extends AbstractNode
/**
* @param config the config String
* @param hc {@link HtmlContext}
*/
public EitherNode(String config)
public EitherNode(String config, HtmlContext hc)
{
if (config.contains("\n"))
{ throw new IllegalArgumentException("Bad config! Found line feed!"); }
@ -42,14 +43,15 @@ public class EitherNode extends AbstractNode
messageFail = spl.length == 3 ? null : spl.length == 4 ? spl[3].substring(1).trim() : spl[4];
if (points.signum() > 0)
{
btn_ok = new JRadioButton(Utils.formatPoints(points) + "P " + messageOK);
btn_ok = new JRadioButton(Utils.formatPoints(points, hc.decimals) + "P " + messageOK);
btn_fail = new JRadioButton(
Utils.ptsToString(BigDecimal.ZERO) + "P " + (messageFail == null ? messageOK : messageFail));
Utils.formatPoints(BigDecimal.ZERO, hc.decimals) + "P " + (messageFail == null ? messageOK : messageFail));
}
else
{
btn_ok = new JRadioButton(Utils.formatPoints(BigDecimal.ZERO) + "P " + messageOK);
btn_fail = new JRadioButton(Utils.formatPoints(points) + "P " + (messageFail == null ? messageOK : messageFail));
btn_ok = new JRadioButton(Utils.formatPoints(BigDecimal.ZERO, hc.decimals) + "P " + messageOK);
btn_fail = new JRadioButton(
Utils.formatPoints(points, hc.decimals) + "P " + (messageFail == null ? messageOK : messageFail));
}
btn_ok.addActionListener(e -> onChange(this));
btn_fail.addActionListener(e -> onChange(this));
@ -61,12 +63,14 @@ public class EitherNode extends AbstractNode
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(btn_ok);
content.add(btn_fail);
btn_ok.doClick();
}
@Override
protected void _reset()
{
bg.clearSelection();
btn_ok.doClick();
}
@Override
@ -125,10 +129,12 @@ public class EitherNode extends AbstractNode
}
return "<li" + hc.styleText(signum) + ">" + //
"<span" + hc.stylePts(signum) + ">" + //
Utils.formatPoints(achievedPoints(hc.mc)) + //
(points.signum() < 0 ? "" : " / " + Utils.formatPoints(maximumPoints())) + "P" + //
Utils.formatPoints(achievedPoints(hc.mc), hc.decimals) + //
(points.signum() < 0 ? "" : " / " + Utils.formatPoints(maximumPoints(), hc.decimals)) + "P" + //
"</span> " + //
(btn_fail.isSelected() ? btn_fail.getText() : btn_ok.getText()) + //
(points.signum() < 0 ? btn_fail.isSelected() ? messageOK : messageFail
: btn_fail.isSelected() ? messageFail : messageOK)
+ //
"</li>";
}

View File

@ -3,6 +3,7 @@ package de.tuDortmund.cs.rvs.pingger.korrekturHelper.nodes;
import java.awt.event.ActionListener;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Arrays;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
@ -27,8 +28,9 @@ public class RadioMultiSelectNode extends AbstractNode
/**
* @param config the config string to parse
* @param hc htmlContext
*/
public RadioMultiSelectNode(String config)
public RadioMultiSelectNode(String config, HtmlContext hc)
{
var lines = config.split("\n");
if (lines.length <= 1)
@ -57,25 +59,28 @@ public class RadioMultiSelectNode extends AbstractNode
var s = lines[i].substring(1).split("\t");
if (s.length != 2)
{ throw new IllegalArgumentException("Expected exactly 1 \\t"); }
System.out.println(s[0] + "\t" + s[1]);
points[i - 1] = new BigDecimal(s[0]);
text[i - 1] = s[1];
rbs[i - 1] = new JRadioButton(Utils.formatPoints(points[i - 1]) + "P - " + text[i - 1]);
rbs[i - 1] = new JRadioButton(Utils.formatPoints(points[i - 1], hc.decimals) + "P - " + text[i - 1]);
rbs[i - 1].addActionListener(cl);
content.add(rbs[i - 1]);
bg.add(rbs[i - 1]);
}
rbs[0].doClick();
}
@Override
protected void _reset()
{
bg.clearSelection();
rbs[0].doClick();
}
@Override
public BigDecimal achievedPoints(MathContext mc)
{
if (!isVisibleInResultHtml())
{ return BigDecimal.ZERO; }
return points[getSelected()];
}
@ -86,17 +91,17 @@ public class RadioMultiSelectNode extends AbstractNode
if (rbs[i].isSelected())
{ return i; }
}
return rbs.length - 1;
return -1;
}
@Override
public boolean isVisibleInResultHtml()
{ return true; }
{ return Arrays.stream(rbs).anyMatch(JRadioButton::isSelected); }
@Override
public BigDecimal maximumPoints()
{
return maxPoints;
return isVisibleInResultHtml() ? maxPoints : BigDecimal.ZERO;
}
@Override
@ -123,9 +128,18 @@ public class RadioMultiSelectNode extends AbstractNode
@Override
public String toResultHtml(HtmlContext hc)
{
if (!isVisibleInResultHtml())
{
return "<li" + hc.styleText(BigDecimal.ZERO, maxPoints) + ">" + //
"<span" + hc.stylePts(achievedPoints(hc.mc), maxPoints) + ">" + //
Utils.formatPoints(BigDecimal.ZERO, hc.decimals) + " / " + Utils.formatPoints(maxPoints, hc.decimals)
+ "P</span> " + //
(baseMsg == null ? "" : baseMsg) + "</li>";
}
return "<li" + hc.styleText(achievedPoints(hc.mc), maxPoints) + ">" + //
"<span" + hc.stylePts(achievedPoints(hc.mc), maxPoints) + "> " + //
Utils.formatPoints(achievedPoints(hc.mc)) + " / " + Utils.formatPoints(maxPoints) + "P</span> " + //
Utils.formatPoints(achievedPoints(hc.mc), hc.decimals) + " / " + Utils.formatPoints(maxPoints, hc.decimals)
+ "P</span> " + //
(baseMsg == null ? text[getSelected()] : baseMsg + " (" + text[getSelected()] + ")") + "</li>";
}