From 86057589d5211fbad4b7cdbcd4dd0f1e3777d4c1 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sat, 18 Feb 2017 17:23:48 +0100 Subject: [PATCH] Version 1.1.0: Add progress reporting, move to ui package --- VERSION | 2 +- changelog | 15 + configure.sh | 2 +- src/be/nikiroo/utils/package-info.java | 6 - src/be/nikiroo/utils/test/ProgressTest.java | 187 ++++++++++++ src/be/nikiroo/utils/test/Test.java | 1 + src/be/nikiroo/utils/test/TestCase.java | 23 +- src/be/nikiroo/utils/test/TestLauncher.java | 43 ++- src/be/nikiroo/utils/ui/Progress.java | 279 ++++++++++++++++++ src/be/nikiroo/utils/{ => ui}/UIUtils.java | 2 +- src/be/nikiroo/utils/{ => ui}/WrapLayout.java | 2 +- 11 files changed, 535 insertions(+), 27 deletions(-) delete mode 100644 src/be/nikiroo/utils/package-info.java create mode 100644 src/be/nikiroo/utils/test/ProgressTest.java create mode 100644 src/be/nikiroo/utils/ui/Progress.java rename src/be/nikiroo/utils/{ => ui}/UIUtils.java (96%) rename src/be/nikiroo/utils/{ => ui}/WrapLayout.java (99%) diff --git a/VERSION b/VERSION index 3eefcb9..9084fa2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 +1.1.0 diff --git a/changelog b/changelog index e1c2d8f..5bc5c48 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,18 @@ +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 ------------- diff --git a/configure.sh b/configure.sh index 12984bb..7d8a70d 100755 --- a/configure.sh +++ b/configure.sh @@ -45,7 +45,7 @@ fi; 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 diff --git a/src/be/nikiroo/utils/package-info.java b/src/be/nikiroo/utils/package-info.java deleted file mode 100644 index 4951378..0000000 --- a/src/be/nikiroo/utils/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Some small utilities used through the pogram. - * - * @author niki - */ -package be.nikiroo.utils; \ No newline at end of file diff --git a/src/be/nikiroo/utils/test/ProgressTest.java b/src/be/nikiroo/utils/test/ProgressTest.java new file mode 100644 index 0000000..2509735 --- /dev/null +++ b/src/be/nikiroo/utils/test/ProgressTest.java @@ -0,0 +1,187 @@ +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); + } + }); + } + }); + } +} diff --git a/src/be/nikiroo/utils/test/Test.java b/src/be/nikiroo/utils/test/Test.java index 0804039..4340f5f 100644 --- a/src/be/nikiroo/utils/test/Test.java +++ b/src/be/nikiroo/utils/test/Test.java @@ -9,6 +9,7 @@ public class Test extends TestLauncher { public Test(String[] args) { super("Nikiroo-utils", args); + addSeries(new ProgressTest(args)); addSeries(new BundleTest(args)); } diff --git a/src/be/nikiroo/utils/test/TestCase.java b/src/be/nikiroo/utils/test/TestCase.java index 0349bc0..e4860fa 100644 --- a/src/be/nikiroo/utils/test/TestCase.java +++ b/src/be/nikiroo/utils/test/TestCase.java @@ -123,10 +123,7 @@ abstract public class TestCase { 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) @@ -235,4 +232,22 @@ abstract public class TestCase { 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); + } } diff --git a/src/be/nikiroo/utils/test/TestLauncher.java b/src/be/nikiroo/utils/test/TestLauncher.java index ba4e81d..b6118df 100644 --- a/src/be/nikiroo/utils/test/TestLauncher.java +++ b/src/be/nikiroo/utils/test/TestLauncher.java @@ -1,8 +1,12 @@ 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. @@ -47,6 +51,8 @@ public class TestLauncher { protected int executed; protected int total; + private int currentSeries = 0; + /** * Create a new {@link TestLauncher} with default parameters. * @@ -144,10 +150,12 @@ public class TestLauncher { 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"); @@ -263,7 +271,7 @@ public class TestLauncher { System.out.println("[ Test suite: " + name + " ]"); System.out.println(""); } else { - System.out.println(prefix(depth) + name + ":"); + System.out.println(prefix(depth, false) + name + ":"); } } @@ -276,7 +284,8 @@ public class TestLauncher { * 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 += "."; @@ -296,8 +305,15 @@ public class TestLauncher { 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); @@ -332,11 +348,10 @@ public class TestLauncher { 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)); } } @@ -347,22 +362,25 @@ public class TestLauncher { * * @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; @@ -378,7 +396,6 @@ public class TestLauncher { * @return the string */ private String tabs(int depth) { - StringBuilder builder = new StringBuilder(); for (int i = 0; i < depth; i++) { builder.append(" "); diff --git a/src/be/nikiroo/utils/ui/Progress.java b/src/be/nikiroo/utils/ui/Progress.java new file mode 100644 index 0000000..2785180 --- /dev/null +++ b/src/be/nikiroo/utils/ui/Progress.java @@ -0,0 +1,279 @@ +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 children; + private List 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(); + this.listeners = new ArrayList(); + 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 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); + } +} diff --git a/src/be/nikiroo/utils/UIUtils.java b/src/be/nikiroo/utils/ui/UIUtils.java similarity index 96% rename from src/be/nikiroo/utils/UIUtils.java rename to src/be/nikiroo/utils/ui/UIUtils.java index abdfcad..547ff6c 100644 --- a/src/be/nikiroo/utils/UIUtils.java +++ b/src/be/nikiroo/utils/ui/UIUtils.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils; +package be.nikiroo.utils.ui; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; diff --git a/src/be/nikiroo/utils/WrapLayout.java b/src/be/nikiroo/utils/ui/WrapLayout.java similarity index 99% rename from src/be/nikiroo/utils/WrapLayout.java rename to src/be/nikiroo/utils/ui/WrapLayout.java index da03c2f..7f34d79 100644 --- a/src/be/nikiroo/utils/WrapLayout.java +++ b/src/be/nikiroo/utils/ui/WrapLayout.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils; +package be.nikiroo.utils.ui; import java.awt.Component; import java.awt.Container; -- 2.27.0