+Version 1.1.0
+-------------
+
+Add progress reporting, move to ui package
+ A new progress reporting system (and tests) in the new ui package
+ (some other classes have been moved into ui, too: WrapLayout and
+ UIUtils)
+
+Version 1.0.0
+-------------
+
+Add WrapLayout and UIUtils
+ A FlowLayout that automatically wrap to the next line (from existing
+ code found on internet) and a method to set a fake-native look & feel
+
Version 0.9.7
-------------
echo "MAIN = be/nikiroo/utils/resources/TransBundle" > Makefile
-echo "MORE = be/nikiroo/utils/StringUtils be/nikiroo/utils/IOUtils be/nikiroo/utils/MarkableFileInputStream be/nikiroo/utils/UIUtils be/nikiroo/utils/WrapLayout be/nikiroo/utils/test/TestLauncher" >> Makefile
+echo "MORE = be/nikiroo/utils/StringUtils be/nikiroo/utils/IOUtils be/nikiroo/utils/MarkableFileInputStream be/nikiroo/utils/ui/UIUtils be/nikiroo/utils/ui/WrapLayout be/nikiroo/utils/ui/Progress be/nikiroo/utils/test/TestLauncher" >> Makefile
echo "TEST = be/nikiroo/utils/test/Test" >> Makefile
echo "TEST_PARAMS = $cols $ok $ko" >> Makefile
echo "NAME = nikiroo-utils" >> Makefile
+++ /dev/null
-/**
- * Some small utilities used through the pogram.
- *
- * @author niki
- */
-package be.nikiroo.utils;
\ No newline at end of file
--- /dev/null
+package be.nikiroo.utils.test;
+
+import be.nikiroo.utils.ui.Progress;
+
+public class ProgressTest extends TestLauncher {
+ public ProgressTest(String[] args) {
+ super("Progress reporting", args);
+
+ addSeries(new TestLauncher("Simple progress", args) {
+ {
+ addTest(new TestCase("Relative values and direct values") {
+ @Override
+ public void test() throws Exception {
+ Progress p = new Progress();
+ assertEquals(0, p.getProgress());
+ assertEquals(0, p.getRelativeProgress());
+ p.setProgress(33);
+ assertEquals(33, p.getProgress());
+ assertEquals(0.33, p.getRelativeProgress());
+ p.setMax(3);
+ p.setProgress(1);
+ assertEquals(1, p.getProgress());
+ assertEquals(
+ generateAssertMessage("0.33..",
+ p.getRelativeProgress()), true,
+ p.getRelativeProgress() >= 0.332);
+ assertEquals(
+ generateAssertMessage("0.33..",
+ p.getRelativeProgress()), true,
+ p.getRelativeProgress() <= 0.334);
+ }
+ });
+
+ addTest(new TestCase("Listeners at first level") {
+ int pg;
+
+ @Override
+ public void test() throws Exception {
+ Progress p = new Progress();
+ p.addProgressListener(new Progress.ProgressListener() {
+ public void progress(Progress progress, String name) {
+ pg = progress.getProgress();
+ }
+ });
+
+ p.setProgress(42);
+ assertEquals(42, pg);
+ p.setProgress(0);
+ assertEquals(0, pg);
+ }
+ });
+ }
+ });
+
+ addSeries(new TestLauncher("Progress with children", args) {
+ {
+ addTest(new TestCase("One child") {
+ @Override
+ public void test() throws Exception {
+ Progress p = new Progress();
+ Progress child = new Progress();
+
+ p.addProgress(child, 100);
+
+ child.setProgress(42);
+ assertEquals(42, p.getProgress());
+ }
+ });
+
+ addTest(new TestCase("Multiple children") {
+ @Override
+ public void test() throws Exception {
+ Progress p = new Progress();
+ Progress child1 = new Progress();
+ Progress child2 = new Progress();
+ Progress child3 = new Progress();
+
+ p.addProgress(child1, 20);
+ p.addProgress(child2, 60);
+ p.addProgress(child3, 20);
+
+ child1.setProgress(50);
+ assertEquals(10, p.getProgress());
+ child2.setProgress(100);
+ assertEquals(70, p.getProgress());
+ child3.setProgress(100);
+ assertEquals(90, p.getProgress());
+ child1.setProgress(100);
+ assertEquals(100, p.getProgress());
+ }
+ });
+
+ addTest(new TestCase("Listeners with children") {
+ int pg;
+
+ @Override
+ public void test() throws Exception {
+ Progress p = new Progress();
+ Progress child1 = new Progress();
+ Progress child2 = new Progress();
+ p.addProgress(child1, 50);
+ p.addProgress(child2, 50);
+
+ p.addProgressListener(new Progress.ProgressListener() {
+ public void progress(Progress progress, String name) {
+ pg = progress.getProgress();
+ }
+ });
+
+ child1.setProgress(50);
+ assertEquals(25, pg);
+ child2.setProgress(100);
+ assertEquals(75, pg);
+ child1.setProgress(100);
+ assertEquals(100, pg);
+ }
+ });
+
+ addTest(new TestCase("Listeners with children, not 1-100") {
+ int pg;
+
+ @Override
+ public void test() throws Exception {
+ Progress p = new Progress();
+ p.setMax(1000);
+
+ Progress child1 = new Progress();
+ child1.setMax(2);
+
+ Progress child2 = new Progress();
+ p.addProgress(child1, 500);
+ p.addProgress(child2, 500);
+
+ p.addProgressListener(new Progress.ProgressListener() {
+ public void progress(Progress progress, String name) {
+ pg = progress.getProgress();
+ }
+ });
+
+ child1.setProgress(1);
+ assertEquals(250, pg);
+ child2.setProgress(100);
+ assertEquals(750, pg);
+ child1.setProgress(2);
+ assertEquals(1000, pg);
+ }
+ });
+
+ addTest(new TestCase(
+ "Listeners with children, not 1-100, local progress") {
+ int pg;
+
+ @Override
+ public void test() throws Exception {
+ Progress p = new Progress();
+ p.setMax(1000);
+
+ Progress child1 = new Progress();
+ child1.setMax(2);
+
+ Progress child2 = new Progress();
+ p.addProgress(child1, 400);
+ p.addProgress(child2, 400);
+ // 200 = local progress
+
+ p.addProgressListener(new Progress.ProgressListener() {
+ public void progress(Progress progress, String name) {
+ pg = progress.getProgress();
+ }
+ });
+
+ child1.setProgress(1);
+ assertEquals(200, pg);
+ child2.setProgress(100);
+ assertEquals(600, pg);
+ p.setProgress(100);
+ assertEquals(700, pg);
+ child1.setProgress(2);
+ assertEquals(900, pg);
+ p.setProgress(200);
+ assertEquals(1000, pg);
+ }
+ });
+ }
+ });
+ }
+}
public Test(String[] args) {
super("Nikiroo-utils", args);
+ addSeries(new ProgressTest(args));
addSeries(new BundleTest(args));
}
throws AssertException {
if (errorMessage == null) {
- errorMessage = String.format("" //
- + "Assertion failed!\n" //
- + "Expected value: [%s]\n" //
- + "Actual value: [%s]", expected, actual);
+ errorMessage = generateAssertMessage(expected, actual);
}
if ((expected == null && actual != null)
throws AssertException {
assertEquals(errorMessage, new Double(expected), new Double(actual));
}
+
+ /**
+ * Generate the default assert message for 2 different values that were
+ * supposed to be equals.
+ *
+ * @param expected
+ * the expected value
+ * @param actual
+ * the actual value
+ *
+ * @return the message
+ */
+ public static String generateAssertMessage(Object expected, Object actual) {
+ return String.format("" //
+ + "Assertion failed!\n" //
+ + "Expected value: [%s]\n" //
+ + "Actual value: [%s]", expected, actual);
+ }
}
package be.nikiroo.utils.test;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
+import be.nikiroo.utils.test.TestCase.AssertException;
+
/**
* A {@link TestLauncher} starts a series of {@link TestCase}s and displays the
* result to the user.
protected int executed;
protected int total;
+ private int currentSeries = 0;
+
/**
* Create a new {@link TestLauncher} with default parameters.
*
System.out.println("");
}
+ currentSeries = 0;
for (TestLauncher serie : series) {
errors += serie.launch(depth + 1);
executed += serie.executed;
total += serie.total;
+ currentSeries++;
}
} catch (Exception e) {
print(depth, "__start");
System.out.println("[ Test suite: " + name + " ]");
System.out.println("");
} else {
- System.out.println(prefix(depth) + name + ":");
+ System.out.println(prefix(depth, false) + name + ":");
}
}
* the {@link TestCase}
*/
protected void print(int depth, String name) {
- name = prefix(depth) + (name == null ? "" : name).replace("\t", " ");
+ name = prefix(depth, false)
+ + (name == null ? "" : name).replace("\t", " ");
while (name.length() < columns - 11) {
name += ".";
private void print(int depth, Exception error) {
if (error != null) {
System.out.println(" " + koString);
- for (String line : (error.getMessage() + "").split("\n")) {
- System.out.println(prefix(depth) + "\t\t" + line);
+ String lines = error.getMessage() + "";
+ if (!(error instanceof AssertException)) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ error.printStackTrace(pw);
+ lines = sw.toString();
+ }
+ for (String line : lines.split("\n")) {
+ System.out.println(prefix(depth, false) + "\t\t" + line);
}
} else {
System.out.println(" " + okString);
if (depth == 0) {
System.out.println(resume);
} else {
- String arrow = "┗▶";
- if (series.isEmpty()) {
- arrow = "━▶";
- }
- System.out.println(prefix(depth) + arrow + resume);
+ String arrow = "┗▶ ";
+ System.out.println(prefix(depth, currentSeries == 0) + arrow
+ + resume);
+ System.out.println(prefix(depth, currentSeries == 0));
}
}
*
* @param depth
* the current depth
+ * @param first
+ * this line is the first of its tabulation level
*
* @return the prefix
*/
- private String prefix(int depth) {
+ private String prefix(int depth, boolean first) {
String space = tabs(depth - 1);
String line = "";
if (depth > 0) {
if (depth > 1) {
- if (depth != last) {
+ if (depth != last && first) {
line = "╻"; // first line
} else {
line = "┃"; // continuation
}
}
- space = space + line + tabs(1);
+
+ space += line + tabs(1);
}
last = depth;
* @return the string
*/
private String tabs(int depth) {
-
StringBuilder builder = new StringBuilder();
for (int i = 0; i < depth; i++) {
builder.append(" ");
--- /dev/null
+package be.nikiroo.utils.ui;
+
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Progress reporting system, possibly nested.
+ *
+ * @author niki
+ */
+public class Progress {
+ public interface ProgressListener extends EventListener {
+ /**
+ * A progression event.
+ *
+ * @param progress
+ * the {@link Progress} object that generated it, or a parent
+ * @param name
+ * the first non-null name of the {@link Progress} step that
+ * generated this event
+ */
+ public void progress(Progress progress, String name);
+ }
+
+ private String name;
+ private Map<Progress, Double> children;
+ private List<ProgressListener> listeners;
+ private int min;
+ private int max;
+ private int localProgress;
+ private int progress; // children included
+
+ /**
+ * Create a new default unnamed {@link Progress}, from 0 to 100.
+ */
+ public Progress() {
+ this(null);
+ }
+
+ /**
+ * Create a new default {@link Progress}, from 0 to 100.
+ *
+ * @param name
+ * the name of this {@link Progress} step
+ */
+ public Progress(String name) {
+ this(name, 0, 100);
+ }
+
+ /**
+ * Create a new unnamed {@link Progress}, from min to max.
+ *
+ * @param min
+ * the minimum progress value (and starting value) -- must be
+ * non-negative
+ * @param max
+ * the maximum progress value
+ */
+ public Progress(int min, int max) {
+ this(null, min, max);
+ }
+
+ /**
+ * Create a new {@link Progress}, from min to max.
+ *
+ * @param name
+ * the name of this {@link Progress} step
+ * @param min
+ * the minimum progress value (and starting value) -- must be
+ * non-negative
+ * @param max
+ * the maximum progress value
+ */
+ public Progress(String name, int min, int max) {
+ this.name = name;
+ this.children = new HashMap<Progress, Double>();
+ this.listeners = new ArrayList<Progress.ProgressListener>();
+ setMinMax(min, max);
+ setProgress(min);
+ }
+
+ /**
+ * The name of this {@link Progress} step.
+ *
+ * @return the name
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The minimum progress value.
+ *
+ * @return the min
+ */
+ public int getMin() {
+ return min;
+ }
+
+ /**
+ * The minimum progress value.
+ *
+ * @param min
+ * the min to set
+ */
+ public void setMin(int min) {
+ if (min < 0) {
+ throw new Error("negative values not supported");
+ }
+
+ if (min > max) {
+ throw new Error(
+ "The minimum progress value must be <= the maximum progress value");
+ }
+
+ this.min = min;
+ }
+
+ /**
+ * The maximum progress value.
+ *
+ * @return the max
+ */
+ public int getMax() {
+ return max;
+ }
+
+ /**
+ * The maximum progress value (must be >= the minimum progress value).
+ *
+ * @param max
+ * the max to set
+ */
+ public void setMax(int max) {
+ if (max < min) {
+ throw new Error(
+ "The maximum progress value must be >= the minimum progress value");
+ }
+
+ this.max = max;
+ }
+
+ /**
+ * Set both the minimum and maximum progress values.
+ *
+ * @param min
+ * the min
+ * @param max
+ * the max
+ */
+ public void setMinMax(int min, int max) {
+ if (min < 0) {
+ throw new Error("negative values not supported");
+ }
+
+ if (min > max) {
+ throw new Error(
+ "The minimum progress value must be <= the maximum progress value");
+ }
+
+ this.min = min;
+ this.max = max;
+ }
+
+ /**
+ * Get the total progress value (including the optional children
+ * {@link Progress}) on a {@link Progress#getMin()} to
+ * {@link Progress#getMax()} scale.
+ *
+ * @return the progress the value
+ */
+ public int getProgress() {
+ return progress;
+ }
+
+ /**
+ * Set the local progress value (not including the optional children
+ * {@link Progress}), on a {@link Progress#getMin()} to
+ * {@link Progress#getMax()} scale.
+ *
+ * @param progress
+ * the progress to set
+ */
+ public void setProgress(int progress) {
+ int diff = this.progress - this.localProgress;
+ this.localProgress = progress;
+ setTotalProgress(name, progress + diff);
+ }
+
+ /**
+ * Check if the action corresponding to this {@link Progress} is done (i.e.,
+ * if its progress value is >= its max value).
+ *
+ * @return TRUE if it is
+ */
+ public boolean isDone() {
+ return progress >= max;
+ }
+
+ /**
+ * Get the total progress value (including the optional children
+ * {@link Progress}) on a 0.0 to 1.0 scale.
+ *
+ * @return the progress
+ */
+ public double getRelativeProgress() {
+ return (((double) progress) / (max - min));
+ }
+
+ /**
+ * Set the total progress value (including the optional children
+ * {@link Progress}), on a {@link Progress#getMin()} to
+ * {@link Progress#getMax()} scale.
+ *
+ * @param name
+ * the current name (if it is NULL, the first non-null name in
+ * the hierarchy will overwrite it)
+ * @param progress
+ * the progress to set
+ */
+ private void setTotalProgress(String name, int progress) {
+ this.progress = progress;
+
+ for (ProgressListener l : listeners) {
+ l.progress(this, name);
+ }
+ }
+
+ /**
+ * Add a {@link ProgressListener} that will trigger on progress changes.
+ *
+ * @param l
+ * the listener
+ */
+ public void addProgressListener(ProgressListener l) {
+ this.listeners.add(l);
+ }
+
+ /**
+ * Add a child {@link Progress} of the given weight.
+ *
+ * @param progress
+ * the child {@link Progress} to add
+ * @param weight
+ * the weight (on a {@link Progress#getMin()} to
+ * {@link Progress#getMax()} scale) of this child
+ * {@link Progress} in relation to its parent
+ */
+ public void addProgress(Progress progress, double weight) {
+ if (weight < min || weight > max) {
+ throw new Error(
+ "A Progress object cannot have a weight outside its parent range");
+ }
+
+ // Note: this is quite inefficient, especially with many children
+ // TODO: improve it?
+ progress.addProgressListener(new ProgressListener() {
+ public void progress(Progress progress, String name) {
+ double total = ((double) localProgress) / (max - min);
+ for (Entry<Progress, Double> entry : children.entrySet()) {
+ total += (entry.getValue() / (max - min))
+ * entry.getKey().getRelativeProgress();
+ }
+
+ if (name == null) {
+ name = Progress.this.name;
+ }
+
+ setTotalProgress(name, (int) (total * (max - min)));
+ }
+ });
+
+ this.children.put(progress, weight);
+ }
+}
-package be.nikiroo.utils;
+package be.nikiroo.utils.ui;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
-package be.nikiroo.utils;
+package be.nikiroo.utils.ui;
import java.awt.Component;
import java.awt.Container;