From 475e5ed9d7676c6e61c322bced569ef88ee559d5 Mon Sep 17 00:00:00 2001 From: Patrick Corless Date: Mon, 28 Dec 2020 20:44:35 -0700 Subject: [PATCH] GH-146 QA capture framework (#163) --- qa/viewer-jfx/build.gradle | 70 +++ qa/viewer-jfx/readme.md | 18 + .../java/org/icepdf/qa/config/CaptureSet.java | 170 +++++ .../icepdf/qa/config/ConfigSerializer.java | 176 ++++++ .../icepdf/qa/config/ConfigWriteTests.java | 52 ++ .../java/org/icepdf/qa/config/ContentSet.java | 101 +++ .../java/org/icepdf/qa/config/Project.java | 119 ++++ .../java/org/icepdf/qa/config/Result.java | 83 +++ .../org/icepdf/qa/tests/AbstractTestTask.java | 14 + .../org/icepdf/qa/tests/ClassloaderTests.java | 175 ++++++ .../org/icepdf/qa/tests/ImageCompareTask.java | 587 ++++++++++++++++++ .../java/org/icepdf/qa/tests/TestFactory.java | 25 + .../org/icepdf/qa/tests/TestInterface.java | 21 + .../org/icepdf/qa/tests/TextCompareTask.java | 7 + .../qa/tests/exceptions/AnalyzeException.java | 7 + .../exceptions/ConfigurationException.java | 11 + .../qa/tests/exceptions/TestException.java | 7 + .../tests/exceptions/ValidationException.java | 11 + .../icepdf/qa/utilities/TimeTestWatcher.java | 25 + .../java/org/icepdf/qa/viewer/Launcher.java | 236 +++++++ .../icepdf/qa/viewer/commands/Command.java | 8 + .../viewer/commands/CreateProjectCommand.java | 27 + .../qa/viewer/commands/ExitCommand.java | 22 + .../commands/NextDiffFilterCommand.java | 25 + .../viewer/commands/OpenProjectCommand.java | 46 ++ .../commands/ToggleDiffFilterCommand.java | 27 + .../org/icepdf/qa/viewer/common/Mediator.java | 559 +++++++++++++++++ .../viewer/common/PreferencesController.java | 156 +++++ .../org/icepdf/qa/viewer/common/Viewer.java | 119 ++++ .../qa/viewer/comparitors/ComparatorPane.java | 16 + .../comparitors/ComparatorPaneInterface.java | 16 + .../comparitors/ComparatorsViewFactory.java | 27 + .../viewer/comparitors/ImageComparePane.java | 225 +++++++ .../viewer/comparitors/MetricComparePane.java | 28 + .../viewer/comparitors/TextComparePane.java | 27 + .../qa/viewer/project/AbstractDialog.java | 24 + .../project/CaptureSetPropertyPane.java | 325 ++++++++++ .../project/ContentSetSelectorDialog.java | 72 +++ .../icepdf/qa/viewer/project/MetaDataTab.java | 15 + .../viewer/project/NewCaptureSetDialog.java | 89 +++ .../viewer/project/NewContentSetDialog.java | 163 +++++ .../qa/viewer/project/NewProjectDialog.java | 93 +++ .../qa/viewer/project/ProjectCompareView.java | 54 ++ .../project/ProjectPropertiesTabSet.java | 50 ++ .../icepdf/qa/viewer/project/ProjectTab.java | 40 ++ .../qa/viewer/project/ProjectTitledPane.java | 71 +++ .../qa/viewer/project/ProjectUtilityPane.java | 73 +++ .../icepdf/qa/viewer/project/ResultsTab.java | 160 +++++ .../qa/viewer/utilities/ImageLoader.java | 14 + .../viewer/images/icepdf-app-icon-32x32.png | Bin 0 -> 2116 bytes .../viewer/images/icepdf-app-icon-64x64.png | Bin 0 -> 2393 bytes .../org/icepdf/qa/viewer/images/pause.png | Bin 0 -> 1147 bytes .../org/icepdf/qa/viewer/images/run.png | Bin 0 -> 1369 bytes .../org/icepdf/qa/viewer/images/stop.png | Bin 0 -> 1122 bytes settings.gradle | 1 + 55 files changed, 4487 insertions(+) create mode 100644 qa/viewer-jfx/build.gradle create mode 100644 qa/viewer-jfx/readme.md create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/config/CaptureSet.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigSerializer.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigWriteTests.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ContentSet.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Project.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Result.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/AbstractTestTask.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ClassloaderTests.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ImageCompareTask.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestFactory.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestInterface.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TextCompareTask.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/AnalyzeException.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ConfigurationException.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/TestException.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ValidationException.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/utilities/TimeTestWatcher.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/Launcher.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/Command.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/CreateProjectCommand.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ExitCommand.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/NextDiffFilterCommand.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/OpenProjectCommand.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ToggleDiffFilterCommand.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Mediator.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/PreferencesController.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Viewer.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPane.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPaneInterface.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorsViewFactory.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ImageComparePane.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/MetricComparePane.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/TextComparePane.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/AbstractDialog.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/CaptureSetPropertyPane.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ContentSetSelectorDialog.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/MetaDataTab.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewCaptureSetDialog.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewContentSetDialog.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewProjectDialog.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectCompareView.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectPropertiesTabSet.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTab.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTitledPane.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectUtilityPane.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ResultsTab.java create mode 100644 qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/utilities/ImageLoader.java create mode 100644 qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/icepdf-app-icon-32x32.png create mode 100644 qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/icepdf-app-icon-64x64.png create mode 100644 qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/pause.png create mode 100644 qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/run.png create mode 100644 qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/stop.png diff --git a/qa/viewer-jfx/build.gradle b/qa/viewer-jfx/build.gradle new file mode 100644 index 000000000..858997d6d --- /dev/null +++ b/qa/viewer-jfx/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'application' + id 'org.openjfx.javafxplugin' version '0.0.9' +} + +repositories { + mavenCentral() +} + +description 'ICEpdf qa framework application' + +mainClassName = "org.icepdf.qa.viewer.Launcher" +applicationDefaultJvmArgs = ["-Xms64m", "-Xmx4096m"] + +def sectionName = 'org/icepdf/qa/' + +repositories { + mavenCentral() + jcenter() +} + +jar { + archiveBaseName.set('icepdf-qa') + archiveAppendix.set("viewer") + archiveVersion.set("${VERSION}") + archiveClassifier.set("${RELEASE_TYPE}") + + doFirst { + manifest { + attributes ('Created-By': System.getProperty('java.version') + ' (' + System.getProperty('java.vendor') + ')') + // executable jar + attributes("Main-Class": 'org.icepdf.qa.viewer.Launcher') + if (!configurations.runtimeClasspath.isEmpty()) { + attributes('Class-Path': + configurations.runtimeClasspath.files.collect{it.name}.join(' ')) + } + } + } + + manifest { + // section names attributes + attributes("Implementation-Title": "${archiveBaseName.get() + '-' + archiveAppendix.get()}", "${sectionName}") + attributes("Implementation-Version": "${VERSION + (RELEASE_TYPE?.trim()? '-' + RELEASE_TYPE:'')}", "${sectionName}") + attributes("Implementation-Vendor": "${COMPANY}", "${sectionName}") + } +} + +javafx { + version = "11.0.2" + modules = [ 'javafx.base', 'javafx.controls', 'javafx.graphics' ] +} + + +dependencies { + // jackson json. + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.8.6' + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.8.6' + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.8.6' + implementation group: 'org.apache.commons', name: 'commons-io', version: '1.3.2' + implementation group: 'junit', name: 'junit', version: '4.12' + // signature validation. + implementation 'org.bouncycastle:bcprov-jdk15on:' + "${BOUNCY_VERSION}" + implementation 'org.bouncycastle:bcprov-ext-jdk15on:' + "${BOUNCY_VERSION}" + implementation 'org.bouncycastle:bcpkix-jdk15on:' + "${BOUNCY_VERSION}" + // tiff, jpeg2000 and jbig decoding + implementation 'com.twelvemonkeys.imageio:imageio-tiff:' + "${MONKEY_VERSION}" + implementation 'com.github.jai-imageio:jai-imageio-jpeg2000:' + "${JAI_VERSION}" + implementation 'org.apache.pdfbox:jbig2-imageio:' + "${JBIG2_VERSION}" + +} diff --git a/qa/viewer-jfx/readme.md b/qa/viewer-jfx/readme.md new file mode 100644 index 000000000..eaf7ad961 --- /dev/null +++ b/qa/viewer-jfx/readme.md @@ -0,0 +1,18 @@ +# ICEpdf QA + +This is an optional capture/viewer utility for testing the render core for regressions. Capture can be taken for a +particular build and compared against another. Capture results are stored in `~/dev/pdf-qa/results/` but can be +configured via the `PreferencesController`. + +## Building + +The UI wrapper for the capture tool was written in JFX which adds a hurdle to building the whole library. +As a result if you find yourself in a situation were you need to do rendering core regression checking you'll need to +uncomment `'qa:viewer-jfx',` in the root settings.gradle config file. There currently isn't a maven build for this +application as it only appeals to a small group of users. + +### Launch + +`gradle --stacktrace :qa:viewer-jfx:run` + +Head over to https://openjfx.io/index.html to install JFX for your version of java. diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/CaptureSet.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/CaptureSet.java new file mode 100644 index 000000000..df1bc8e42 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/CaptureSet.java @@ -0,0 +1,170 @@ +package org.icepdf.qa.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * Capture set details, mainly keeps track of the content sets being used and some meta data. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) +public class CaptureSet { + + public enum Type { + + capture { + public String toString() { + return "Capture"; + } + }, + metric { + public String toString() { + return "Metric"; + } + }, + textExtraction { + public String toString() { + return "Text Extraction"; + } + } + } + + private Path captureSetPath; + + private String name; + private Type type; + + private String version; + private String classPath; + private URLClassLoader classLoader; + private int capturePageCount; + + private String jdkVersion; + private String systemProperties; + + private List contentSets; + + private Path contentSetPath; + + private boolean complete; + + @JsonCreator + public CaptureSet(@JsonProperty("name") String name, + @JsonProperty("type") Type type) { + this.name = name; + this.type = type; + contentSets = new ArrayList<>(); + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getClassPath() { + return classPath; + } + + public void setClassPath(String classPath) { + this.classPath = classPath; + } + + public int getCapturePageCount() { + return capturePageCount; + } + + public void setCapturePageCount(int capturePageCount) { + this.capturePageCount = capturePageCount; + } + + public String getJdkVersion() { + return jdkVersion; + } + + public void setJdkVersion(String jdkVersion) { + this.jdkVersion = jdkVersion; + } + + public String getSystemProperties() { + return systemProperties; + } + + public void setSystemProperties(String systemProperties) { + this.systemProperties = systemProperties; + } + + public List getContentSets() { + return contentSets; + } + + public void setContentSets(List contentSets) { + this.contentSets = contentSets; + } + + public boolean isComplete() { + return complete; + } + + public void setComplete(boolean complete) { + this.complete = complete; + } + + @Override + public String toString() { + return name; + } + + @Override + public boolean equals(Object obj) { + if (obj != null) + return this.getName().equals(((CaptureSet) obj).getName()); + else { + return super.equals(obj); + } + } + + @JsonIgnore + public Path getCaptureSetPath() { + return captureSetPath; + } + + @JsonIgnore + public void setCaptureSetPath(Path captureSetPath) { + this.captureSetPath = captureSetPath; + } + + @JsonIgnore + public URLClassLoader getClassLoader() { + return classLoader; + } + + @JsonIgnore + public void setClassLoader(URLClassLoader classLoader) { + this.classLoader = classLoader; + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigSerializer.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigSerializer.java new file mode 100644 index 000000000..72772efe1 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigSerializer.java @@ -0,0 +1,176 @@ +package org.icepdf.qa.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.icepdf.qa.viewer.common.PreferencesController; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Serialize one of the jackson objects to the relative locations as defined by the properties objet. + */ +public class ConfigSerializer { + + private static ObjectMapper mapper = new ObjectMapper(); + + static { + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + } + + public static Path save(Object object) { + try { + if (object instanceof Project) { + Project project = (Project) object; + String filePath; + Path path = project.getProjectPath(); + if (path == null) { + filePath = PreferencesController.getProjectDirectory() + generateFileName(project.getName()); + path = Paths.get(filePath); + } + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + // check if file all ready exist. + mapper.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(path.toFile()), project); + return path; + } else if (object instanceof CaptureSet) { + CaptureSet captureSet = (CaptureSet) object; + String filePath; + Path path = captureSet.getCaptureSetPath(); + if (path == null) { + filePath = PreferencesController.getCaptureSetDirectory() + generateFileName(captureSet.getName()); + path = Paths.get(filePath); + } + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + // check if file all ready exist. + mapper.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(path.toFile()), captureSet); + return path; + } else if (object instanceof ContentSet) { + ContentSet contentSet = (ContentSet) object; + String filePath; + Path path = contentSet.getContentSetPath(); + if (path == null) { + filePath = PreferencesController.getContentSetDirectory() + generateFileName(contentSet.getName()); + path = Paths.get(filePath); + } + if (!Files.exists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + // check if file all ready exist. + mapper.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(path.toFile()), contentSet); + return path; + } + + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static Project retrieveProject(Path projectPath) { + try { + Project project = mapper.readValue(new FileReader(projectPath.toFile()), Project.class); + project.setProjectPath(projectPath); + return project; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static CaptureSet retrieveCaptureSet(String captureSetFileName) { + try { + Path contentSetPath = Paths.get(PreferencesController.getCaptureSetDirectory(), captureSetFileName); + if (contentSetPath != null) { + CaptureSet captureSet = mapper.readValue(new FileReader(contentSetPath.toFile()), CaptureSet.class); + captureSet.setCaptureSetPath(contentSetPath.toAbsolutePath()); + return captureSet; + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static ContentSet retrieveContentSet(Path contentSetPath) { + try { + ContentSet contentSet = mapper.readValue(new FileReader(contentSetPath.toFile()), ContentSet.class); + contentSet.setContentSetPath(contentSetPath); + return contentSet; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static List retrieveAllCaptureSets() { + try { + Path contentSetPath = Paths.get(PreferencesController.getCaptureSetDirectory()); + if (!Files.exists(contentSetPath)) { + Files.createDirectories(contentSetPath); + } + List contentSets = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(contentSetPath)) { + for (Path entry : stream) { + CaptureSet captureSet = mapper.readValue(new FileReader(entry.toFile()), CaptureSet.class); + captureSet.setCaptureSetPath(entry.toAbsolutePath()); + contentSets.add(captureSet); + } + } + return contentSets; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static List retrieveAllContentSets() { + try { + Path contentSetPath = Paths.get(PreferencesController.getContentSetDirectory()); + if (!Files.exists(contentSetPath)) { + Files.createDirectories(contentSetPath); + } + List contentSets = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(contentSetPath)) { + for (Path entry : stream) { + ContentSet contentSet = mapper.readValue(new FileReader(entry.toFile()), ContentSet.class); + contentSet.setContentSetPath(entry.toAbsolutePath()); + contentSets.add(contentSet); + } + } + return contentSets; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static boolean exists(String parentPath, String fileName) { + String filePath = parentPath + generateFileName(fileName); + return Files.exists(Paths.get(filePath)); + } + + /** + * Utility to convert a name to a file name with a json extension. + * Converts to lowercase and replaces spaces with underscores. + * + * @param name file name to clean. + * @return cleaned file name + */ + private static String generateFileName(String name) { + return name.toLowerCase().trim().replaceAll("( )+", "_") + ".json"; + } + +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigWriteTests.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigWriteTests.java new file mode 100644 index 000000000..ad470db00 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ConfigWriteTests.java @@ -0,0 +1,52 @@ +package org.icepdf.qa.config; + +import com.fasterxml.jackson.core.JsonGenerationException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** + * Jackson serialization tests. + */ +public class ConfigWriteTests { + + public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException { + + // jackson object mapper. + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + + // project testAndAnalyze + Project project = new Project("image capture"); + project.setCaptureSetAConfigFile("pro_6.1.3_capture"); + project.setCaptureSetBConfigFile("pro_6.2.2_capture"); + project.setStatus(Project.Status.complete); +// project.add(new Result("testAndAnalyze-file_1.pdf", "image_1_0.png", 98.9)); +// project.add(new Result("testAndAnalyze-file_2.pdf","image_1_0.png", 93.9)); +// project.add(new Result("testAndAnalyze-file_2.pdf", "image_1_0.png",99.9)); + + mapper.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(new File("project-capture.json")), project); + project = mapper.readValue(FileUtils.readFileToByteArray(new File("project-capture.json")), Project.class); + System.out.println(project); + + // content set testAndAnalyze. + ContentSet contentSet = new ContentSet("Full Monty", "c://"); + contentSet.getFileNames().add("test_1.pdf"); + contentSet.getFileNames().add("test_2.pdf"); + mapper.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(new File("contentSet-full-monty.json")), contentSet); + + // Capture set testAndAnalyze. + CaptureSet captureSet = new CaptureSet("v6.2.2", CaptureSet.Type.capture); + captureSet.setClassPath("d:/products/testAndAnalyze/PDF"); + captureSet.getContentSets().add("annotations.jon"); + captureSet.getContentSets().add("fonts-cid.json"); + mapper.writerWithDefaultPrettyPrinter().writeValue(new FileWriter(new File("captureSet.json")), captureSet); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ContentSet.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ContentSet.java new file mode 100644 index 000000000..812a5a1a7 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/ContentSet.java @@ -0,0 +1,101 @@ +package org.icepdf.qa.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.icepdf.qa.viewer.common.PreferencesController; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +/** + * Content set for testing against certain types of common files. + */ + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) +public class ContentSet { + + private Path contentSetPath; + + private String name; + + private String path; + + private List fileNames; + + @JsonCreator + public ContentSet(@JsonProperty("name") String name, @JsonProperty("path") String path) { + this.name = name; + this.path = path; + fileNames = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getFileNames() { + return fileNames; + } + + public void setFileNames(List fileNames) { + this.fileNames = fileNames; + } + + public String getPath() { + return path; + } + + @JsonIgnore + public Path getFilePath() { + return Paths.get(PreferencesController.getContentSetFilesDiretory(), path); + } + + public void setPath(String path) { + this.path = path; + } + + @JsonIgnore + public Path getContentSetPath() { + return contentSetPath; + } + + @JsonIgnore + public void setContentSetPath(Path contentSetPath) { + this.contentSetPath = contentSetPath; + } + + public void refreshFiles() { + if (fileNames != null) { + fileNames.clear(); + + } + Path contentPath = Paths.get(PreferencesController.getContentSetFilesDiretory(), path); + if (Files.isDirectory(contentPath)) { + try (DirectoryStream stream = Files.newDirectoryStream(contentPath)) { + for (Path entry : stream) { + if (entry.getFileName().toString().toLowerCase().endsWith(".pdf")) { + fileNames.add(entry.getFileName().toString()); + } + } + } catch (IOException ex) { + ex.printStackTrace(); + } + } + } + + @Override + public String toString() { + return name; + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Project.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Project.java new file mode 100644 index 000000000..4f2801f9e --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Project.java @@ -0,0 +1,119 @@ +package org.icepdf.qa.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * QA Project declaration. + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = As.WRAPPER_OBJECT) +public class Project { + + private String name; + + private CaptureSet captureSetA; + private String captureSetAConfigFile; + + private CaptureSet captureSetB; + private String captureSetBConfigFile; + + public enum Status {complete, running, pause, stopped, failed, incomplete} + + private Status status; + + private List results; + + private Path projectPath; + + @JsonCreator + public Project(@JsonProperty("name") String name) { + this.name = name; + status = Status.incomplete; + results = new ArrayList<>(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCaptureSetAConfigFile() { + return captureSetAConfigFile; + } + + public void setCaptureSetAConfigFile(String captureSetAConfigFile) { + this.captureSetAConfigFile = captureSetAConfigFile; + } + + public String getCaptureSetBConfigFile() { + return captureSetBConfigFile; + } + + public void setCaptureSetBConfigFile(String captureSetBConfigFile) { + this.captureSetBConfigFile = captureSetBConfigFile; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public List getResults() { + return results; + } + + public void setResults(List results) { + this.results = results; + } + + public void add(Result result) { + results.add(result); + } + + public void clearResults() { + results.clear(); + } + + @JsonIgnore + public CaptureSet getCaptureSetA() { + return captureSetA; + } + + @JsonIgnore + public void setCaptureSetA(CaptureSet captureSetA) { + this.captureSetA = captureSetA; + } + + @JsonIgnore + public CaptureSet getCaptureSetB() { + return captureSetB; + } + + @JsonIgnore + public void setCaptureSetB(CaptureSet captureSetB) { + this.captureSetB = captureSetB; + } + + @JsonIgnore + public Path getProjectPath() { + return projectPath; + } + + @JsonIgnore + public void setProjectPath(Path projectPath) { + this.projectPath = projectPath; + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Result.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Result.java new file mode 100644 index 000000000..1d9c81162 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/config/Result.java @@ -0,0 +1,83 @@ +package org.icepdf.qa.config; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleStringProperty; + +import java.nio.file.Paths; + +/** + * Base result class + * Further reading on polymorphism here, http://www.studytrails.com/java/json/java-jackson-Serialization-polymorphism/ + */ +@JsonTypeInfo(use = JsonTypeInfo.Id.NONE, include = JsonTypeInfo.As.WRAPPER_OBJECT) +public class Result { + + private final SimpleStringProperty documentName; + + private final SimpleStringProperty captureNameA; + private final SimpleStringProperty captureNameB; + + // percent difference between captureOne and captureTwo + private final SimpleDoubleProperty difference; + + // can be extended for other properties. + + @JsonCreator + public Result(@JsonProperty("documentName") String documentName, + @JsonProperty("fileNameA") String fileNameA, + @JsonProperty("fileName") String fileNameB, + @JsonProperty("difference") double difference) { + // relative path to content set location + this.documentName = new SimpleStringProperty(documentName); + // relative path to capture set location + this.captureNameA = new SimpleStringProperty(fileNameA); + this.captureNameB = new SimpleStringProperty(fileNameB); + this.difference = new SimpleDoubleProperty(difference); + } + + public String getDocumentName() { + return documentName.get(); + } + + @JsonIgnore + public String getDocumentFileName() { + return Paths.get(documentName.get()).getFileName().toString(); + } + + public void setDocumentName(String documentName) { + this.documentName.set(documentName); + } + + public String getCaptureNameA() { + return captureNameA.get(); + } + + @JsonIgnore + public String getFileNameA() { + return Paths.get(captureNameA.get()).getFileName().toString(); + } + + public void setCaptureNameA(String captureNameA) { + this.captureNameA.set(captureNameA); + } + + public String getCaptureNameB() { + return captureNameB.get(); + } + + public void setCaptureNameB(String captureNameB) { + this.captureNameB.set(captureNameB); + } + + public double getDifference() { + return Math.round(difference.get() * 100.0) / 100.0; + } + + public void setDifference(double difference) { + this.difference.set(difference); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/AbstractTestTask.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/AbstractTestTask.java new file mode 100644 index 000000000..7ed177970 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/AbstractTestTask.java @@ -0,0 +1,14 @@ +package org.icepdf.qa.tests; + +import javafx.concurrent.Task; +import org.icepdf.qa.config.Result; + +import java.util.List; + +/** + * + */ +public abstract class AbstractTestTask extends Task> implements TestInterface { + + +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ClassloaderTests.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ClassloaderTests.java new file mode 100644 index 000000000..8388d5033 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ClassloaderTests.java @@ -0,0 +1,175 @@ +package org.icepdf.qa.tests; + +import javafx.scene.image.Image; +import javafx.scene.image.PixelFormat; +import javafx.scene.image.PixelReader; +import org.icepdf.qa.utilities.TimeTestWatcher; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.geom.Dimension2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.*; +import java.util.ArrayList; + +/** + * Class loader tests for having two version of ICEpdf in one JVM + */ +public class ClassloaderTests { + + public static void main(String[] args) { + + // custom class loader to load pdf lib + String userHome = System.getProperties().getProperty("user.home"); + loadPDF(userHome + "/dev/products/icepdf-pro-6.3.0/libs/", "base"); + loadPDF(userHome + "/dev/products/icepdf-pro-6.3.2/libs/", "baseTwo"); + + // tests for compression speed and diff results. + try { + // todo try new expression to foreach notation. + for (int i = 0; i < 4; i++) { + File file = new File("imageCapture_" + i + "_base.png"); + File file2 = new File("imageCapture_" + i + "_baseTwo.png"); + System.out.println(file2.getAbsolutePath()); + TimeTestWatcher timer = new TimeTestWatcher(); + timer.starting("diff testAndAnalyze"); + double diff = percentageCompare(new Image(new FileInputStream(file)), + new Image(new FileInputStream(file2))); + timer.finished(); + System.out.println("diff: " + diff); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + + public static void loadPDF(String pdfJarDirectory, String label) { + + try { + Path baseDir = Paths.get(pdfJarDirectory); + ArrayList classPath = new ArrayList<>(baseDir.getNameCount()); + try (DirectoryStream stream = Files.newDirectoryStream(baseDir, "*.{class,jar}")) { + for (Path file : stream) { + System.out.println(file.toUri()); + classPath.add(file.normalize().toUri().toURL()); + } + } catch (IOException | DirectoryIteratorException x) { + // IOException can never be thrown by the iteration. + // In this snippet, it can only be thrown by newDirectoryStream. + System.err.println(x); + } + + ClassLoader parent = String.class.getClassLoader(); + URLClassLoader clsLoader = URLClassLoader.newInstance(classPath.toArray(new URL[0]), parent); + + Class documentClass = clsLoader.loadClass("org.icepdf.core.pobjects.Document"); + Constructor fontClassConstructor = documentClass.getDeclaredConstructor(); + Object documentObject = fontClassConstructor.newInstance(); + System.out.println(documentObject); + + + // load document + Method setFileMethod = documentClass.getMethod("setFile", String.class); + String test = Paths.get(System.getProperty("user.home") + "/dev/pdf-qa/pdf_reference_addendum_redaction.pdf").normalize().toString(); + setFileMethod.invoke(documentObject, test); + + // number of pages. + Method getNumberOfPagesMethod = documentClass.getMethod("getNumberOfPages"); + int pages = (int) getNumberOfPagesMethod.invoke(documentObject); + + for (int pageNumber = 0; pageNumber < pages; pageNumber++) { + + // get the page tree + Method getPageTreeMethod = documentClass.getMethod("getPageTree"); + Object pageTree = getPageTreeMethod.invoke(documentObject); + + // get next page. + Class pageTreeClass = clsLoader.loadClass("org.icepdf.core.pobjects.PageTree"); + Method getPageMethod = pageTreeClass.getMethod("getPage", int.class); + Object pageObject = getPageMethod.invoke(pageTree, pageNumber); + + // init the page. + Class pageClass = clsLoader.loadClass("org.icepdf.core.pobjects.Page"); + Method initMethod = pageClass.getMethod("init"); + initMethod.invoke(pageObject); + + // get the size + Method getSizeMethod = pageClass.getMethod("getSize", int.class, float.class, float.class); + Dimension2D sz = (Dimension2D) getSizeMethod.invoke(pageObject, 2, 0, 1); + + int pageWidth = (int) sz.getWidth(); + int pageHeight = (int) sz.getHeight(); + + BufferedImage image = new BufferedImage(pageWidth, pageHeight, BufferedImage.TYPE_INT_RGB); + Graphics g = image.createGraphics(); + + // paint the page. + Method paintMethod = pageClass.getMethod("paint", + Graphics.class, int.class, int.class, float.class, float.class); + paintMethod.invoke(pageObject, g, 2, 2, 0f, 1f); + + g.dispose(); + // capture the page image to file + + System.out.println("Capturing page " + pageNumber); + File file = new File("imageCapture_" + pageNumber + "_" + label + ".png"); + ImageIO.write(image, "png", file); + image.flush(); + } + + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + + private static double percentageCompare(Image image1, Image image2) { + + assert (image1.getWidth() == image2.getWidth() && image2.getHeight() == image2.getHeight()); + + PixelReader pixelReader1 = image1.getPixelReader(); + PixelReader pixelReader2 = image2.getPixelReader(); + + int width = (int) image1.getWidth(); + int height = (int) image1.getHeight(); + + int[] buffer1 = new int[width]; + int[] buffer2 = new int[width]; + + int badPixels = 0; + for (int y = 0; y < height; y++) { + pixelReader1.getPixels(0, y, width, 1, PixelFormat.getIntArgbInstance(), buffer1, 0, 1); + pixelReader2.getPixels(0, y, width, 1, PixelFormat.getIntArgbInstance(), buffer2, 0, 1); + for (int x = 0; x < width; x++) { + if (buffer1[x] != buffer2[x]) { + badPixels++; + } + } + } + + + int totalPixels = width * height; + int goodPixels = totalPixels - badPixels; + + return 100d * goodPixels / totalPixels; + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ImageCompareTask.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ImageCompareTask.java new file mode 100644 index 000000000..b0ffa1533 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/ImageCompareTask.java @@ -0,0 +1,587 @@ +package org.icepdf.qa.tests; + +import javafx.application.Platform; +import javafx.scene.image.PixelFormat; +import javafx.scene.image.PixelReader; +import org.icepdf.qa.config.*; +import org.icepdf.qa.tests.exceptions.AnalyzeException; +import org.icepdf.qa.tests.exceptions.ConfigurationException; +import org.icepdf.qa.tests.exceptions.TestException; +import org.icepdf.qa.tests.exceptions.ValidationException; +import org.icepdf.qa.utilities.TimeTestWatcher; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.PreferencesController; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * + */ +public class ImageCompareTask extends AbstractTestTask { + + private static final int THREAD_EXECUTOR_SIZE = 8; + + // page capture test + public static final String PAGE_CAPTURE_CLASS = "org.icepdf.ri.util.qa.PageCapture"; + public static final String PAGE_CAPTURE_SETUP_METHOD = "load"; + public static final String PAGE_CAPTURE_METHOD = "capture"; + public static final String PAGE_CAPTURE_DISPOSE_METHOD = "dispose"; + + private Mediator mediator; + + private Project project; + private CaptureSet captureSetA; + private CaptureSet captureSetB; + private List commonContentSets; + private int commonPageCount; + + private ArrayList filePaths; + + protected static ExecutorService executorService; + + public ImageCompareTask(Mediator mediator) { + this.mediator = mediator; + } + + @Override + protected List call() throws TestException, AnalyzeException, ConfigurationException, ValidationException { + try { + TimeTestWatcher timeTestWatcher = new TimeTestWatcher(); + timeTestWatcher.starting("Image Compare"); + setup(); + validate(); + config(); + testAndAnalyze(); + timeTestWatcher.finished(); + + } catch (ValidationException e) { + System.out.println("There was a validation error: " + e.getMessage()); + } catch (ConfigurationException e) { + System.out.println("There was a configuration error: " + e.getMessage()); + } catch (TestException e) { + System.out.println("There was a test error: " + e.getMessage()); + } finally { + teardown(); + } + return null; + } + + @Override + protected void succeeded() { + super.succeeded(); + updateMessage("Done"); + } + + @Override + protected void cancelled() { + super.cancelled(); + updateMessage("Cancelled"); + } + + @Override + protected void failed() { + super.failed(); + updateMessage("Failed"); + } + + @Override + public void setup() { + + // disable project level config controls. + Platform.runLater(() -> mediator.setStartTestTaskGuiState()); + + // setup pointers to project and each capture set. + project = mediator.getCurrentProject(); + captureSetA = project.getCaptureSetA(); + captureSetB = project.getCaptureSetB(); + + executorService = Executors.newFixedThreadPool(THREAD_EXECUTOR_SIZE); + } + + @Override + public void validate() throws ValidationException { + System.out.println("---- Validation started -----"); + // validate project, make sure we captures sets that are of the same type. + if (captureSetA.getType() != captureSetB.getType()) { + throw new ValidationException("Capture set types must match."); + } + // throw exception if content sets have not intersection of content + List contentSetsA = captureSetA.getContentSets(); + List contentSetsB = captureSetB.getContentSets(); + if (contentSetsA.size() == 0 || contentSetsB.size() == 0) { + throw new ValidationException("Content sets must contain at least one item."); + } + + // check for intersection of at least one common set. + commonContentSets = new ArrayList<>(); + for (String contentNameA : contentSetsA) { + for (String contentNameB : contentSetsB) { + if (contentNameA.equals(contentNameB)) { + commonContentSets.add(contentNameA); + } + } + } + if (commonContentSets.size() == 0) { + throw new ValidationException("Content sets must have at least one set in common."); + } + } + + @Override + public void config() throws ConfigurationException { + System.out.println("---- Configuration started -----"); + // create union of content sets that will be used in the compare + if (captureSetA.getCapturePageCount() < captureSetB.getCapturePageCount()) { + commonPageCount = captureSetA.getCapturePageCount(); + } else { + commonPageCount = captureSetB.getCapturePageCount(); + } + if (commonPageCount == 0) { + throw new ConfigurationException("Capture page count must be at least one."); + } + + // create new urlClass loaders for each capture set as part of this work and validate that the class loader + // have valid classes. + try { + getTestInstance(captureSetA); + } catch (Throwable e) { + e.printStackTrace(); + throw new ConfigurationException("Capture set A classpath does not contain ICEpdf or is invalid."); + } + try { + getTestInstance(captureSetB); + } catch (Throwable e) { + throw new ConfigurationException("Capture set B classpath does not contain ICEpdf or is invalid."); + } + + // load the content set files and assign to hash, name -> ContentSet. + Map contentSetMap = new HashMap<>(); + List contentSets = ConfigSerializer.retrieveAllContentSets(); + for (ContentSet contentSet : contentSets) { + contentSetMap.put(contentSet.getName(), contentSet); + } + + // build out the actual list of files that needs to be captured + String contentSetFilesDirectory = PreferencesController.getContentSetFilesDiretory(); + filePaths = new ArrayList<>(); + for (String contentSetName : commonContentSets) { + // pull form the content set hash an get fileset + ContentSet contentSet = contentSetMap.get(contentSetName); + List contentFileNames = contentSet.getFileNames(); + for (String contentFileName : contentFileNames) { + filePaths.add(Paths.get(contentSetFilesDirectory, contentSet.getPath(), contentFileName)); + } + } + + // create the capture set results folder, where we save the image captures. + try { + Path resultsFolder = Paths.get(PreferencesController.getResultsPathDirectory(), + captureSetA.getCaptureSetPath().getFileName().toString()); + if (!Files.exists(resultsFolder)) { + Files.createDirectories(resultsFolder); + } + resultsFolder = Paths.get(PreferencesController.getResultsPathDirectory(), + captureSetB.getCaptureSetPath().getFileName().toString()); + if (!Files.exists(resultsFolder)) { + Files.createDirectories(resultsFolder); + } + } catch (IOException e) { + e.printStackTrace(); + } + System.out.format("Common content set has been defined: %d files.%n", filePaths.size()); + } + + @Override + public void testAndAnalyze() throws TestException { + System.out.println("---- Test and Analyze started -----"); + try { + List results = new ArrayList<>(commonContentSets.size() * commonPageCount); + Platform.runLater(() -> mediator.resetProjectResults()); + + int testSize = filePaths.size(); + int i = 1; + for (Path filePath : filePaths) { + if (isCancelled()) { + updateMessage("Cancelled"); + break; + } + // update the UI + updateProgress(i, testSize); + // setup what needs to be captured. + if (!captureSetA.isComplete() && !captureSetB.isComplete()) { + capturePages(filePath, i, captureSetA, captureSetB); + } else if (!captureSetA.isComplete()) { + capturePages(filePath, i, captureSetA); + } else if (!captureSetB.isComplete()) { + capturePages(filePath, i, captureSetB); + } + // compare output + results.addAll(comparePages(i, filePath, captureSetA, captureSetB)); + + i++; + } + // mark each content set as complete. + if (!captureSetA.isComplete() && !captureSetB.isComplete()) { + if (!isCancelled()) { + captureSetA.setComplete(true); + captureSetB.setComplete(true); + } + ConfigSerializer.save(captureSetA); + ConfigSerializer.save(captureSetB); + } else if (!captureSetA.isComplete()) { + if (!isCancelled()) { + captureSetA.setComplete(true); + } + ConfigSerializer.save(captureSetA); + } else if (!captureSetB.isComplete()) { + if (!isCancelled()) { + captureSetB.setComplete(true); + } + ConfigSerializer.save(captureSetB); + } + + //save teh projects state. + project.setResults(results); + ConfigSerializer.save(project); + } catch (InterruptedException e) { + if (isCancelled()) { + updateMessage("Cancelled"); + } + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + @Override + public void teardown() { + // do and cleanup. + Platform.runLater(() -> mediator.setStopTestTaskGuiState()); + executorService.shutdownNow(); + } + + private Object getTestInstance(CaptureSet captureSet) { + // create our new class loader and create new pageCaptureTest instance. + try { + + URLClassLoader classLoader; + if (captureSet.getClassLoader() == null) { + // build out the class path + Path baseDir = Paths.get(captureSet.getClassPath()); + ArrayList classPath = new ArrayList<>(baseDir.getNameCount()); + System.out.println("Loading class path:"); + try (DirectoryStream stream = Files.newDirectoryStream(baseDir, "*.{class,jar}")) { + for (Path file : stream) { + System.out.println("\t" + file.toUri()); + classPath.add(file.normalize().toUri().toURL()); + } + } catch (IOException | DirectoryIteratorException x) { + // IOException can never be thrown by the iteration. + // In this snippet, it can only be thrown by newDirectoryStream. + System.err.println(x); + } + ClassLoader parent = String.class.getClassLoader(); + URL[] urls = classPath.toArray(new URL[0]); + classLoader = URLClassLoader.newInstance(urls, parent); + captureSet.setClassLoader(classLoader); + } else { + classLoader = captureSet.getClassLoader(); + } + + // open the file + Class pageCaptureClass = classLoader.loadClass(PAGE_CAPTURE_CLASS); + Constructor fontClassConstructor = pageCaptureClass.getDeclaredConstructor(); + Object pageCaptureObject = fontClassConstructor.newInstance(); + + return pageCaptureObject; + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + return null; + } + + private int loadTestInstance(CaptureSet captureSet, Object testInstance, Path filePath, int documentIndex, int captureSetTotal) { + if (filePath != null) { + System.err.format("File [%d/%d]=%s\n", documentIndex, captureSetTotal, filePath.toString()); + + // call setup on the test which returns total number of pages. + try { + URLClassLoader classLoader = captureSet.getClassLoader(); + Class pageCaptureClass = classLoader.loadClass(PAGE_CAPTURE_CLASS); + Method setFileMethod = pageCaptureClass.getMethod(PAGE_CAPTURE_SETUP_METHOD, Path.class); + Integer pageCount = (Integer) setFileMethod.invoke(testInstance, filePath); + return pageCount; + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + return 0; + } + + private void capturePages(Path filePath, int documentIndex, CaptureSet... captureSet) throws InterruptedException, ExecutionException { + // capture pageCaptureTest + Object[] testInstances; + List> callables = new ArrayList<>(commonPageCount * 2); + testInstances = new Object[captureSet.length]; + for (int i = 0; i < captureSet.length; i++) { + Object testInstance = getTestInstance(captureSet[i]); + + int numberOfPages = loadTestInstance(captureSet[i], testInstance, filePath, documentIndex, filePaths.size()); + + for (int pageNumber = 0; pageNumber < commonPageCount; pageNumber++) { + callables.add(new CapturePage(testInstance, captureSet[i], filePath, pageNumber, numberOfPages)); + } + testInstances[i] = testInstance; + } + // run the pageCaptureTest capture + executorService.invokeAll(callables); + // close + for (int i = 0; i < captureSet.length; i++) { + executorService.submit(new DocumentCloser(captureSet[i], testInstances[i])).get(); + } + } + + private List comparePages(int documentIndex, Path filePath, CaptureSet captureSetA, CaptureSet captureSetB) + throws InterruptedException, ExecutionException { + List results = new ArrayList<>(); + for (int i = 0; i < commonPageCount; i++) { + Result result = executorService.submit(new DocumentImageCompare(filePath, i, captureSetA, captureSetB)).get(); + if (result != null) { + results.add(result); + if (result.getDifference() < 100) { + System.err.format("File [%d/%d|%.2f%%]=%s%n", documentIndex, filePaths.size(), result.getDifference(), filePath.toString()); + System.out.print('~'); + } else { + System.out.print('='); + } + } + } + Platform.runLater(() -> mediator.addProjectResults(results)); + + return results; + } + + public class CapturePage implements Callable { + private Object pageCaptureTest; + private int pageNumber; + private float scale = 1f; + private float rotation = 0f; + private String fileName; + private int numberOfPage; + + private CaptureSet captureSet; + + private CapturePage(Object pageCaptureTest, CaptureSet captureSet, Path filePath, int pageNumber, int numberOfPage) { + this.pageCaptureTest = pageCaptureTest; + this.pageNumber = pageNumber; + this.captureSet = captureSet; + this.numberOfPage = numberOfPage; + this.fileName = filePath.getFileName().toString(); + } + + public Void call() { + try { + URLClassLoader classLoader = captureSet.getClassLoader(); + Class pageCaptureClass = classLoader.loadClass(PAGE_CAPTURE_CLASS); + + if (pageNumber < numberOfPage) { + + Path imageCapture = Paths.get(PreferencesController.getResultsPathDirectory(), + captureSet.getCaptureSetPath().getFileName().toString(), + fileName + "_" + pageNumber + ".png"); + + if (!Files.exists(imageCapture)) { + System.out.print("|"); + + // paint the page. + Method captureMethod = pageCaptureClass.getMethod(PAGE_CAPTURE_METHOD, + int.class, int.class, int.class, float.class, float.class); + BufferedImage image = (BufferedImage) captureMethod.invoke(pageCaptureTest, pageNumber, 2, 2, rotation, scale); + + File file = Files.createFile(imageCapture).toFile(); + ImageIO.write(image, "png", file); + image.flush(); + } + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + return null; + } + } + + + /** + * closes the two documents and compares, followed by adding a new results ot the results tab. + */ + public class DocumentImageCompare implements Callable { + + private Path fileName; + private int pageNumber; + private CaptureSet[] captureSets; + + private DocumentImageCompare(Path filePath, int pageNumber, CaptureSet... captureSets) { + this.captureSets = captureSets; + this.fileName = filePath; + this.pageNumber = pageNumber; + } + + public Result call() { + try { + + Path imageCaptureA = Paths.get(PreferencesController.getResultsPathDirectory(), + captureSets[0].getCaptureSetPath().getFileName().toString(), + fileName.getFileName() + "_" + pageNumber + ".png"); + Path imageCaptureB = Paths.get(PreferencesController.getResultsPathDirectory(), + captureSets[1].getCaptureSetPath().getFileName().toString(), + fileName.getFileName() + "_" + pageNumber + ".png"); + + FileInputStream imageCaptureAFileStream = new FileInputStream(imageCaptureA.toFile()); + FileInputStream imageCaptureBFileStream = new FileInputStream(imageCaptureB.toFile()); + + javafx.scene.image.Image imageA = new javafx.scene.image.Image(imageCaptureAFileStream); + javafx.scene.image.Image imageB = new javafx.scene.image.Image(imageCaptureBFileStream); + + double diff = percentageCompare(imageA, imageB); + + imageCaptureAFileStream.close(); + imageCaptureBFileStream.close(); + + return new Result( + fileName.toString(), + imageCaptureA.toString(), + imageCaptureB.toString(), diff); + + } catch (FileNotFoundException e) { + // silently move on if the page wasn't created. + } catch (IOException e) { + e.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + } + + /** + * Disposes the pageCaptureTest. + */ + public class DocumentCloser implements Callable { + private Object pageCaptureTest; + private CaptureSet captureSet; + + private DocumentCloser(CaptureSet captureSet, Object pageCaptureTest) { + this.pageCaptureTest = pageCaptureTest; + this.captureSet = captureSet; + } + + public Void call() { + try { + URLClassLoader classLoader = captureSet.getClassLoader(); + Class pageCaptureClass = classLoader.loadClass(PAGE_CAPTURE_CLASS); + Method disposeMethod = pageCaptureClass.getMethod(PAGE_CAPTURE_DISPOSE_METHOD); + disposeMethod.invoke(pageCaptureTest); + + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); + } + return null; + } + } + + public class UpdateResultsTab implements Callable { + + + private UpdateResultsTab(List results) { + // pass the results off to the mediator so we can update the UI. + Platform.runLater(() -> mediator.addProjectResults(results)); + } + + public Void call() { + + return null; + } + } + + private static double percentageCompare(javafx.scene.image.Image image1, javafx.scene.image.Image image2) { + + assert (image1.getWidth() == image2.getWidth() && image2.getHeight() == image2.getHeight()); + + PixelReader pixelReader1 = image1.getPixelReader(); + PixelReader pixelReader2 = image2.getPixelReader(); + + int width = (int) image1.getWidth(); + int height = (int) image1.getHeight(); + + int[] buffer1 = new int[width]; + int[] buffer2 = new int[width]; + + int badPixels = 0; + for (int y = 0; y < height; y++) { + pixelReader1.getPixels(0, y, width, 1, PixelFormat.getIntArgbInstance(), buffer1, 0, 1); + pixelReader2.getPixels(0, y, width, 1, PixelFormat.getIntArgbInstance(), buffer2, 0, 1); + for (int x = 0; x < width; x++) { + if (buffer1[x] != buffer2[x]) { + badPixels++; + } + } + } + + int totalPixels = width * height; + int goodPixels = totalPixels - badPixels; + + return 100d * goodPixels / totalPixels; + } + + +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestFactory.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestFactory.java new file mode 100644 index 000000000..44b104d81 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestFactory.java @@ -0,0 +1,25 @@ +package org.icepdf.qa.tests; + +import org.icepdf.qa.viewer.common.Mediator; + +/** + * + */ +public class TestFactory { + + private static TestFactory testFactory; + + private TestFactory() { + } + + public static TestFactory getInstance() { + if (testFactory == null) { + testFactory = new TestFactory(); + } + return testFactory; + } + + public AbstractTestTask createTestInstance(Mediator mediator) { + return new ImageCompareTask(mediator); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestInterface.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestInterface.java new file mode 100644 index 000000000..1d42a3a65 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TestInterface.java @@ -0,0 +1,21 @@ +package org.icepdf.qa.tests; + +import org.icepdf.qa.tests.exceptions.ConfigurationException; +import org.icepdf.qa.tests.exceptions.TestException; +import org.icepdf.qa.tests.exceptions.ValidationException; + +/** + * + */ +public interface TestInterface { + + void setup(); + + void validate() throws ValidationException; + + void config() throws ConfigurationException; + + void testAndAnalyze() throws TestException; + + void teardown(); +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TextCompareTask.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TextCompareTask.java new file mode 100644 index 000000000..85e10a4fe --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/TextCompareTask.java @@ -0,0 +1,7 @@ +package org.icepdf.qa.tests; + +/** + * Created by patri on 5/17/2017. + */ +public class TextCompareTask { +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/AnalyzeException.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/AnalyzeException.java new file mode 100644 index 000000000..90a7d7835 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/AnalyzeException.java @@ -0,0 +1,7 @@ +package org.icepdf.qa.tests.exceptions; + +/** + * + */ +public class AnalyzeException extends Exception { +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ConfigurationException.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ConfigurationException.java new file mode 100644 index 000000000..34740c406 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ConfigurationException.java @@ -0,0 +1,11 @@ +package org.icepdf.qa.tests.exceptions; + +/** + * + */ +public class ConfigurationException extends Exception { + + public ConfigurationException(String message) { + super(message); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/TestException.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/TestException.java new file mode 100644 index 000000000..8e6faf2f7 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/TestException.java @@ -0,0 +1,7 @@ +package org.icepdf.qa.tests.exceptions; + +/** + * + */ +public class TestException extends Exception { +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ValidationException.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ValidationException.java new file mode 100644 index 000000000..f8853094d --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/tests/exceptions/ValidationException.java @@ -0,0 +1,11 @@ +package org.icepdf.qa.tests.exceptions; + +/** + * + */ +public class ValidationException extends Exception { + + public ValidationException(String message) { + super(message); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/utilities/TimeTestWatcher.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/utilities/TimeTestWatcher.java new file mode 100644 index 000000000..4d43467be --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/utilities/TimeTestWatcher.java @@ -0,0 +1,25 @@ +package org.icepdf.qa.utilities; + +import javafx.util.Duration; + +/** + * Utility for capture work time. + */ + +public class TimeTestWatcher { + Duration start; + Duration end; + String testName; + + public void starting(String testName) { + this.testName = testName; + start = new Duration(System.currentTimeMillis()); + } + + public double finished() { + end = new Duration(System.currentTimeMillis()); + double elapsed = end.subtract(start).toMinutes(); + System.out.println(String.format("%nTest %s took %.2f min.", testName, elapsed)); + return elapsed; + } +}; \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/Launcher.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/Launcher.java new file mode 100644 index 000000000..348fc21b6 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/Launcher.java @@ -0,0 +1,236 @@ +package org.icepdf.qa.viewer; + +import javafx.application.Application; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.layout.*; +import javafx.stage.Stage; +import org.icepdf.qa.config.CaptureSet; +import org.icepdf.qa.viewer.commands.*; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.project.ProjectCompareView; +import org.icepdf.qa.viewer.project.ProjectPropertiesTabSet; +import org.icepdf.qa.viewer.utilities.ImageLoader; + +/** + * QA Launcher for testing ICEpdf library. The test harness was written using pure JavaFX and was designed ot + * aid developers in creating content sets and capture sets as needed for various version of the library. The + * test harness allows for results to be viewed in real time and config to be as easy as possible to setup + * and verify. + *

+ * The launcher is project based and a project contains a capture set A and B. Each capture set can have + * one or more content sets. A content set specifies a grouping of a common files that are located in a directory. + * A capture set defines captures for set of content sets, generally captures are thought as of image captures but + * they can be of any type. The capture set also defines the classpath used to for the captures and the number of + * pages captured. + */ +public class Launcher extends Application { + + private Mediator mediator; + + private Stage primaryStage; + + public static void main(String[] args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) throws Exception { +// setUserAgentStylesheet(STYLESHEET_MODENA); + + // our main primary stage. + this.primaryStage = primaryStage; + + // core mediator to try and keeps various UI elements from knowing too much about each other. + mediator = new Mediator(primaryStage); + + // build Menu Bar + BorderPane rootBorderPane = new BorderPane(); + rootBorderPane.setTop(buildMenuBar()); + + // build the main project content area + Pane projectViewContent = createProjectViewContent(); + mediator.setClosedProjectState(); + rootBorderPane.setCenter(projectViewContent); + + Scene scene = new Scene(rootBorderPane, 1024, 768); + // build out the icon state. + primaryStage.getIcons().addAll( + ImageLoader.loadImage("icepdf-app-icon-32x32.png"), + ImageLoader.loadImage("icepdf-app-icon-64x64.png")); + primaryStage.setTitle(Mediator.TITLE_TEXT); + primaryStage.setScene(scene); + primaryStage.setOnCloseRequest(event -> { + mediator.cancelTestInstance(); + }); + primaryStage.show(); + + // check for last opened project + mediator.loadOrCreateProject(); + } + + private BorderPane createProjectViewContent() { + BorderPane projectBorderPane = new BorderPane(); + projectBorderPane.setTop(buildProjectToolBar()); + projectBorderPane.setCenter(buildViewerPane()); + projectBorderPane.setBottom(buildStatusToolBar()); + mediator.setProjectBorderPane(projectBorderPane); + return projectBorderPane; + } + + private Parent buildProjectToolBar() { + ToolBar projectToolbar = new ToolBar(); + + // new project + Button newProjectButton = new Button("New Project"); + newProjectButton.setStyle(String.format("-fx-base: %s;", "indigo")); + newProjectButton.setOnAction(new CreateProjectCommand(mediator)); + mediator.setNewProjectButton(newProjectButton); + + // Capture Set one + Label captureSetALabel = new Label("Capture Set A:"); + mediator.setCaptureSetALabel(captureSetALabel); + ChoiceBox captureSetAChoiceBox = new ChoiceBox<>(); + captureSetAChoiceBox.setOnAction((event) -> mediator.captureSetASelection()); + mediator.setCaptureSetAChoiceBox(captureSetAChoiceBox); + Button newCaptureSetAButton = new Button("New"); + newCaptureSetAButton.setOnAction(event -> mediator.showNewCaptureSetDialog(captureSetAChoiceBox)); + mediator.setNewCaptureSetAButton(newCaptureSetAButton); + + // Capture Set two + Label captureSetBLabel = new Label("Capture Set B:"); + mediator.setCaptureSetBLabel(captureSetBLabel); + ChoiceBox captureSetBChoiceBox = new ChoiceBox<>(); + captureSetBChoiceBox.setOnAction((event) -> mediator.captureSetBSelection()); + mediator.setCaptureSetBChoiceBox(captureSetBChoiceBox); + Button newCaptureSetBButton = new Button("New"); + newCaptureSetBButton.setOnAction(event -> mediator.showNewCaptureSetDialog(captureSetBChoiceBox)); + mediator.setNewCaptureSetBButton(newCaptureSetBButton); + + // testAndAnalyze control buttons. + Button runButton = new Button("Run"); + runButton.setStyle(String.format("-fx-base: %s;", "green")); + runButton.setOnAction(event -> mediator.createTestInstanceAndRun()); + mediator.setRunButton(runButton); + + Button stopButton = new Button("Stop"); + stopButton.setStyle(String.format("-fx-base: %s;", "red")); + stopButton.setOnAction(event -> mediator.cancelTestInstance()); + mediator.setStopButton(stopButton); + + projectToolbar.getItems().addAll( + newProjectButton, + new Separator(), + captureSetALabel, captureSetAChoiceBox, newCaptureSetAButton, + captureSetBLabel, captureSetBChoiceBox, newCaptureSetBButton, + new Separator() + ); + HBox spacer = new HBox(); + HBox.setHgrow(spacer, Priority.ALWAYS); + projectToolbar.getItems().addAll(spacer, runButton, stopButton); + + return new VBox(20, projectToolbar); + } + + /** + * Bottom frame of ui, shows progress of running test. + * + * @return toolbar containing status label and progress bar. + */ + private ToolBar buildStatusToolBar() { + ToolBar statusToolBar = new ToolBar(); + mediator.setStatusToolBar(statusToolBar); + Label statusLabel = new Label(Mediator.STATUS_TEXT); + mediator.setStatusLabel(statusLabel); + ProgressBar progressBar = new ProgressBar(); + progressBar.setVisible(false); + mediator.setProgressBar(progressBar); + HBox hBox = new HBox(); + hBox.getChildren().addAll(statusLabel, progressBar); + statusToolBar.getItems().addAll(hBox); + return statusToolBar; + } + + private Parent buildViewerPane() { + // tab set of various properties of the project. + ProjectPropertiesTabSet projectTabSet = new ProjectPropertiesTabSet(mediator); + mediator.setProjectTabSet(projectTabSet); + + // main compare view, contains diff implementation and console log. + ProjectCompareView projectCompareView = new ProjectCompareView(mediator); + mediator.setProjectCompareView(projectCompareView); + + // setup main split pain for project view. + SplitPane projectViewSplitPane = new SplitPane(); + projectViewSplitPane.getItems().addAll(projectTabSet, projectCompareView); + projectViewSplitPane.setDividerPosition(0, 0.35); + SplitPane.setResizableWithParent(projectTabSet, false); + mediator.setProjectViewSplitPane(projectViewSplitPane); + return projectViewSplitPane; + } + + private MenuBar buildMenuBar() { + MenuBar menuBar = new MenuBar(); + // file + Menu fileMenuItem = new Menu("File"); + MenuItem newProjectMenuItem = new MenuItem("New Project"); + newProjectMenuItem.setOnAction(new CreateProjectCommand(mediator)); + fileMenuItem.getItems().add(newProjectMenuItem); + MenuItem newCaptureSet = new MenuItem("New Capture Set"); + newCaptureSet.setOnAction(event -> mediator.showNewCaptureSetDialog(null)); + mediator.setNewCaptureSet(newProjectMenuItem); + fileMenuItem.getItems().add(newCaptureSet); + MenuItem newContentSetMenuItem = new MenuItem("New Content Set"); + newContentSetMenuItem.setOnAction(event -> mediator.showNewContentSetDialog()); + fileMenuItem.getItems().add(newContentSetMenuItem); + SeparatorMenuItem newSeparator = new SeparatorMenuItem(); + fileMenuItem.getItems().add(newSeparator); + MenuItem openProjectMenuItem = new MenuItem("Open Project"); + openProjectMenuItem.setOnAction(new OpenProjectCommand(mediator)); + fileMenuItem.getItems().add(openProjectMenuItem); + MenuItem closeProjectMenuItem = new MenuItem("Close Project"); + closeProjectMenuItem.setOnAction(event -> mediator.closeCurrentProject()); + mediator.setCloseProjectMenuItem(closeProjectMenuItem); + fileMenuItem.getItems().add(closeProjectMenuItem); + SeparatorMenuItem openSeparator = new SeparatorMenuItem(); + fileMenuItem.getItems().add(openSeparator); + MenuItem exitMenuItem = new MenuItem("Exit"); + exitMenuItem.setOnAction(new ExitCommand(primaryStage)); + fileMenuItem.getItems().add(exitMenuItem); + // edit. + Menu editMenuItem = new Menu("Edit"); + MenuItem copyFilePathMenuItem = new MenuItem("Copy File Path"); + mediator.setCopyFilePathMenuItem(copyFilePathMenuItem); + MenuItem resetCaptureAMenuItem = new MenuItem("Reset Capture A"); + resetCaptureAMenuItem.setOnAction(event -> mediator.clearCaptureSetResultsA()); + mediator.setResetCaptureAMenuItem(resetCaptureAMenuItem); + MenuItem resetCaptureBMenuItem = new MenuItem("Reset Capture B"); + resetCaptureBMenuItem.setOnAction(event -> mediator.clearCaptureSetResultsB()); + mediator.setResetCaptureBMenuItem(resetCaptureBMenuItem); + editMenuItem.getItems().addAll(resetCaptureAMenuItem, resetCaptureBMenuItem); + // view. + Menu viewMenuItem = new Menu("View"); + MenuItem fullScreenMenuItem = new MenuItem("Full Screen"); + fullScreenMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN)); + MenuItem highlightMenuItem = new MenuItem("Toggle Blending Mode"); + highlightMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.D, KeyCombination.CONTROL_DOWN)); + highlightMenuItem.setOnAction(new ToggleDiffFilterCommand(mediator)); + mediator.setHighlightMenuItem(highlightMenuItem); + MenuItem nextBlendingModeMenuItem = new MenuItem("Next Blending Mode"); + nextBlendingModeMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN)); + nextBlendingModeMenuItem.setOnAction(new NextDiffFilterCommand(mediator)); + mediator.setNextBlendingModeMenuItem(nextBlendingModeMenuItem); + viewMenuItem.getItems().addAll(fullScreenMenuItem, new SeparatorMenuItem(), highlightMenuItem, nextBlendingModeMenuItem); + // help + Menu helpMenuItem = new Menu("Help"); + MenuItem aboutMenuItem = new MenuItem("About QA Launcher"); + helpMenuItem.getItems().add(aboutMenuItem); + // put the menu together. + menuBar.getMenus().addAll(fileMenuItem, editMenuItem, viewMenuItem, helpMenuItem); + return menuBar; + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/Command.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/Command.java new file mode 100644 index 000000000..967a14a9b --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/Command.java @@ -0,0 +1,8 @@ +package org.icepdf.qa.viewer.commands; + +/** + * Command interface. + */ +public interface Command { + void execute(); +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/CreateProjectCommand.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/CreateProjectCommand.java new file mode 100644 index 000000000..985f6c65a --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/CreateProjectCommand.java @@ -0,0 +1,27 @@ +package org.icepdf.qa.viewer.commands; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * + */ +public class CreateProjectCommand implements EventHandler, Command { + + private Mediator mediator; + + public CreateProjectCommand(Mediator mediator) { + this.mediator = mediator; + } + + @Override + public void handle(ActionEvent event) { + execute(); + } + + public void execute() { + mediator.showNewProjectDialog(); + } + +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ExitCommand.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ExitCommand.java new file mode 100644 index 000000000..9f28dd555 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ExitCommand.java @@ -0,0 +1,22 @@ +package org.icepdf.qa.viewer.commands; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.stage.Stage; + +/** + * + */ +public class ExitCommand implements EventHandler { + + private Stage stage; + + public ExitCommand(Stage stage) { + this.stage = stage; + } + + @Override + public void handle(ActionEvent event) { + stage.close(); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/NextDiffFilterCommand.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/NextDiffFilterCommand.java new file mode 100644 index 000000000..20250b8ed --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/NextDiffFilterCommand.java @@ -0,0 +1,25 @@ +package org.icepdf.qa.viewer.commands; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import org.icepdf.qa.viewer.common.Mediator; + + +public class NextDiffFilterCommand implements EventHandler, Command { + + private Mediator mediator; + + public NextDiffFilterCommand(Mediator mediator) { + this.mediator = mediator; + } + + @Override + public void execute() { + mediator.nextDiffFilter(); + } + + @Override + public void handle(ActionEvent event) { + execute(); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/OpenProjectCommand.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/OpenProjectCommand.java new file mode 100644 index 000000000..08042fe95 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/OpenProjectCommand.java @@ -0,0 +1,46 @@ +package org.icepdf.qa.viewer.commands; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.stage.FileChooser; +import org.icepdf.qa.config.ConfigSerializer; +import org.icepdf.qa.config.Project; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.PreferencesController; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * + */ +public class OpenProjectCommand implements EventHandler, Command { + + private Mediator mediator; + + public OpenProjectCommand(Mediator mediator) { + this.mediator = mediator; + } + + @Override + public void handle(ActionEvent event) { + execute(); + } + + public void execute() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Project File"); + fileChooser.setInitialDirectory(new File(PreferencesController.getProjectDirectory())); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("QA Project Files", "*.json")); + File selectedFile = fileChooser.showOpenDialog(mediator.getPrimaryStage()); + if (selectedFile != null) { + Path projectPath = Paths.get(selectedFile.getAbsolutePath()); + Project project = ConfigSerializer.retrieveProject(projectPath); + PreferencesController.saveLastUedProject(projectPath); + mediator.loadProject(project); + } + } + +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ToggleDiffFilterCommand.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ToggleDiffFilterCommand.java new file mode 100644 index 000000000..db0cf5ef6 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/commands/ToggleDiffFilterCommand.java @@ -0,0 +1,27 @@ +package org.icepdf.qa.viewer.commands; + +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * + */ +public class ToggleDiffFilterCommand implements EventHandler, Command { + + private Mediator mediator; + + public ToggleDiffFilterCommand(Mediator mediator) { + this.mediator = mediator; + } + + @Override + public void execute() { + mediator.toggleDiffFilter(); + } + + @Override + public void handle(ActionEvent event) { + execute(); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Mediator.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Mediator.java new file mode 100644 index 000000000..e3d490c35 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Mediator.java @@ -0,0 +1,559 @@ +package org.icepdf.qa.viewer.common; + +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.stage.Stage; +import org.icepdf.qa.config.*; +import org.icepdf.qa.tests.AbstractTestTask; +import org.icepdf.qa.tests.TestFactory; +import org.icepdf.qa.viewer.comparitors.ComparatorPane; +import org.icepdf.qa.viewer.project.*; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Optional; + +/** + * Mediator/controller does most of lifting for the application. + */ +public class Mediator { + + public static final String TITLE_TEXT = "ICEpdf QA"; + public static final String STATUS_TEXT = "Status: "; + + private Stage primaryStage; + + // menu items we need to disable. + private MenuItem newCaptureSet; + private MenuItem closeProjectMenuItem; + private MenuItem copyFilePathMenuItem; + private MenuItem restCaptureSetAMenuItem; + private MenuItem restCaptureSetBMenuItem; + private MenuItem highlightMenuItem; + private MenuItem nextBlendingModeMenuItem; + + // toolbar controls. + private Button newProjectButton; + private Button runButton; + private Button stopButton; + private Label captureSetALabel; + private ChoiceBox captureSetAChoiceBox; + private Button newCaptureSetAButton; + private Label captureSetBLabel; + private ChoiceBox captureSetBChoiceBox; + private Button newCaptureSetBButton; + + // Project controls. + private SplitPane projectViewSplitPane; + private ToolBar statusToolBar; + private Label statusLabel; + + // status bar + private ProgressBar progressBar; + + // main layout panels. + private BorderPane projectBorderPane; + private ProjectPropertiesTabSet projectTabSet; + private ProjectCompareView projectCompareView; + private ComparatorPane comparatorPane; + private ProjectUtilityPane projectUtilityPane; + + // project structure + private Project currentProject; + private AbstractTestTask currentTestTask; + + public Mediator(Stage primaryStage) { + this.primaryStage = primaryStage; + } + + public Stage getPrimaryStage() { + return primaryStage; + } + + /** + * Load or create project dialog. + */ + public void loadOrCreateProject() { + // check if have stored the last opned project. + Path lastUsedProjectPath = PreferencesController.getLastUsedProjectPath(); + if (lastUsedProjectPath != null) { + Project project = ConfigSerializer.retrieveProject(lastUsedProjectPath); + loadProject(project); + PreferencesController.saveLastUedProject(lastUsedProjectPath); + } else { + showNewProjectDialog(); + } + } + + /** + * Executor for starting a test on the currently opened project. + */ + public void createTestInstanceAndRun() { + // clear the console + if (projectUtilityPane != null) + projectUtilityPane.clearConsole(); + if (comparatorPane != null) + comparatorPane.openResult(null); + // get the test and try to run + currentTestTask = TestFactory.getInstance().createTestInstance(this); + progressBar.progressProperty().bind(currentTestTask.progressProperty()); + new Thread(currentTestTask).start(); + } + + /** + * Called from AbstractTestTask to update the UI table view of results objects. + * + * @param results test result. + */ + public void addProjectResults(List results) { + // pass results off to project tabs. + projectTabSet.setProjectResults(results); + } + + /** + * Called just before a test starts to clear the results table. + */ + public void resetProjectResults() { + projectTabSet.clearProjectResults(); + } + + public void clearCaptureSetResultsA() { + if (currentProject != null && currentProject.getCaptureSetA() != null) { + clearCaptureSetResults(currentProject.getCaptureSetA()); + currentProject.getCaptureSetA().setComplete(false); + // reset class loader so we pick up on any changed jars. + currentProject.getCaptureSetA().setClassLoader(null); + ConfigSerializer.save(currentProject); + } + } + + public void clearCaptureSetResultsB() { + if (currentProject != null && currentProject.getCaptureSetB() != null) { + clearCaptureSetResults(currentProject.getCaptureSetB()); + currentProject.getCaptureSetB().setComplete(false); + // reset class loader so we pick up on any changed jars. + currentProject.getCaptureSetB().setClassLoader(null); + ConfigSerializer.save(currentProject); + } + } + + /** + * Clears/removes all results files associated with the proje + * + * @param captureSet + */ + private void clearCaptureSetResults(CaptureSet captureSet) { + Path resultsFolder = Paths.get(PreferencesController.getResultsPathDirectory(), + captureSet.getCaptureSetPath().getFileName().toString()); + if (Files.isDirectory(resultsFolder)) { + try (DirectoryStream stream = Files.newDirectoryStream(resultsFolder)) { + for (Path entry : stream) { + Files.delete(entry); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public void cancelTestInstance() { + if (currentTestTask != null && currentTestTask.isRunning()) { + currentTestTask.cancel(true); + statusLabel.setText(STATUS_TEXT + currentTestTask.getMessage()); + } + } + + /** + * Load the current project and populate the UI with project data. + * + * @param project + */ + public void loadProject(Project project) { + if (currentProject != null) { + closeCurrentProject(); + } + // setup the new project. + currentProject = project; + primaryStage.setTitle(TITLE_TEXT + " - " + project.getName()); + + setOpenProjectState(); + + // read all capture sets that are defined. + refreshCaptureSetChoices(); + + // update the tabset + projectTabSet.setProject(currentProject); + + // try and create a comparitor view + projectCompareView.setProject(project); + + // clear the comparitor. + if (comparatorPane != null) + comparatorPane.openResult(null); + } + + public void closeCurrentProject() { + + primaryStage.setTitle(TITLE_TEXT); + + // disable the view. + setClosedProjectState(); + } + + /** + * Toggles the diff view filter for the current comparitor view. + */ + public void toggleDiffFilter() { + comparatorPane.toggleDiffFilter(); + } + + /** + * Changes the diff filter to the next available one. + */ + public void nextDiffFilter() { + comparatorPane.nextDiffFilter(); + } + + /** + * Open a result in the comparitor view, enable menu items. + * + * @param result result to open. + */ + public void openResult(Result result) { + if (result != null) { + highlightMenuItem.setDisable(false); + nextBlendingModeMenuItem.setDisable(false); + } else { + highlightMenuItem.setDisable(true); + nextBlendingModeMenuItem.setDisable(true); + } + comparatorPane.openResult(result); + } + + /** + * Action call when a users selects a new capture set form the toolbar choice box. + */ + public void captureSetASelection() { + CaptureSet captureSet = captureSetAChoiceBox.getSelectionModel().getSelectedItem(); + if (captureSet != null) { + currentProject.setCaptureSetAConfigFile(captureSet.getCaptureSetPath().getFileName().toString()); + refreshCaptureSets(); + projectTabSet.setProject(currentProject); + ConfigSerializer.save(currentProject); + } + } + + /** + * Action call when a users selects a new capture set form the toolbar choice box. + */ + public void captureSetBSelection() { + CaptureSet captureSet = captureSetBChoiceBox.getSelectionModel().getSelectedItem(); + if (captureSet != null) { + currentProject.setCaptureSetBConfigFile(captureSet.getCaptureSetPath().getFileName().toString()); + refreshCaptureSets(); + projectTabSet.setProject(currentProject); + ConfigSerializer.save(currentProject); + } + } + + /** + * Utility to show new project dialog + */ + public void showNewProjectDialog() { + NewProjectDialog dialog = new NewProjectDialog(this); + Optional result = dialog.showAndWait(); + // load the project data and enable the ui + result.ifPresent(this::loadProject); + } + + /** + * Utility to show new captures set dialog. + * + * @param captureSetChoiceBox reference to choice box to update with new capture set. + */ + public void showNewCaptureSetDialog(ChoiceBox captureSetChoiceBox) { + NewCaptureSetDialog dialog = new NewCaptureSetDialog(this); + Optional result = dialog.showAndWait(); + // load the project data and enable the ui + result.ifPresent(captureSet -> updateProjectCaptureSet(captureSetChoiceBox, captureSet)); + } + + /** + * Utility for creating new content set. Called form file menu. + */ + public void showNewContentSetDialog() { + NewContentSetDialog dialog = new NewContentSetDialog(this); + Optional result = dialog.showAndWait(); + /* load the project data and enable the ui */ + result.ifPresent(this::addContent); + } + + public void addContent(ContentSet contentSet) { + // currently black as we only call this from the menu item execution. + } + + /** + * Utility to load refresh a projects content sets file association and updates the choice boxes. + */ + public void refreshCaptureSetChoices() { + CaptureSet captureSetA = null; + if (currentProject.getCaptureSetAConfigFile() != null) { + captureSetA = ConfigSerializer.retrieveCaptureSet(currentProject.getCaptureSetAConfigFile()); + currentProject.setCaptureSetA(captureSetA); + } + CaptureSet captureSetB = null; + if (currentProject.getCaptureSetBConfigFile() != null) { + captureSetB = ConfigSerializer.retrieveCaptureSet(currentProject.getCaptureSetBConfigFile()); + currentProject.setCaptureSetB(captureSetB); + } + refreshCaptureSetChoiceBox(captureSetAChoiceBox, captureSetA); + refreshCaptureSetChoiceBox(captureSetBChoiceBox, captureSetB); + } + + /** + * Retrieves the associated captures sets and assigns them to the project by object reference. + */ + public void refreshCaptureSets() { + if (currentProject.getCaptureSetAConfigFile() != null) { + CaptureSet captureSetA = ConfigSerializer.retrieveCaptureSet(currentProject.getCaptureSetAConfigFile()); + currentProject.setCaptureSetA(captureSetA); + } + if (currentProject.getCaptureSetBConfigFile() != null) { + CaptureSet captureSetB = ConfigSerializer.retrieveCaptureSet(currentProject.getCaptureSetBConfigFile()); + currentProject.setCaptureSetB(captureSetB); + } + } + + protected void refreshCaptureSetChoiceBox(ChoiceBox captureSetChoiceBox, CaptureSet selectedCaptureSet) { + List captureSets = ConfigSerializer.retrieveAllCaptureSets(); + if (captureSetChoiceBox == null) { + return; + } + if (captureSets != null) { + for (CaptureSet captureSet : captureSets) { + captureSetChoiceBox.getItems().add(captureSet); + } + if (selectedCaptureSet != null) { + captureSetChoiceBox.setValue(selectedCaptureSet); + } + } + } + + public void updateProjectCaptureSet(ChoiceBox captureSetChoiceBox, CaptureSet captureSet) { + if (captureSetChoiceBox != null) { + if (captureSetChoiceBox.equals(captureSetAChoiceBox)) { + if (captureSet.getCaptureSetPath() != null) { + currentProject.setCaptureSetAConfigFile(captureSet.getCaptureSetPath().getFileName().toString()); + } + } else { + if (captureSet.getCaptureSetPath() != null) { + currentProject.setCaptureSetBConfigFile(captureSet.getCaptureSetPath().getFileName().toString()); + } + } + projectTabSet.setProject(currentProject); + ConfigSerializer.save(currentProject); + } + refreshCaptureSetChoiceBox(captureSetChoiceBox, captureSet); + refreshCaptureSetChoices(); + } + + public void setStartTestTaskGuiState() { + projectUtilityPane.clearConsole(); + runButton.setDisable(true); + stopButton.setDisable(false); + // disable the project controls + projectTabSet.setDisableProjectTab(true); + // disable the toolbar + newProjectButton.setDisable(true); + captureSetALabel.setDisable(true); + captureSetBLabel.setDisable(true); + captureSetAChoiceBox.setDisable(true); + captureSetBChoiceBox.setDisable(true); + newCaptureSetAButton.setDisable(true); + newCaptureSetBButton.setDisable(true); + + progressBar.setVisible(true); + statusLabel.setText(STATUS_TEXT); + + } + + public void setStopTestTaskGuiState() { + runButton.setDisable(false); + stopButton.setDisable(true); + // disable the project controls + projectTabSet.setDisableProjectTab(false); + // disable the toolbar + newProjectButton.setDisable(false); + captureSetALabel.setDisable(false); + captureSetBLabel.setDisable(false); + captureSetAChoiceBox.setDisable(false); + captureSetBChoiceBox.setDisable(false); + newCaptureSetAButton.setDisable(false); + newCaptureSetBButton.setDisable(false); + progressBar.setVisible(false); + + statusLabel.setText(STATUS_TEXT + currentTestTask.getMessage()); + } + + public void setOpenProjectState() { + newProjectButton.setDisable(false); + + captureSetALabel.setDisable(false); + captureSetAChoiceBox.setDisable(false); + newCaptureSetAButton.setDisable(false); + + captureSetBLabel.setDisable(false); + captureSetBChoiceBox.setDisable(false); + newCaptureSetBButton.setDisable(false); + + runButton.setDisable(false); + stopButton.setDisable(true); + + projectViewSplitPane.setDisable(false); + statusToolBar.setDisable(false); + + // open project state. + newCaptureSet.setDisable(false); + closeProjectMenuItem.setDisable(false); + highlightMenuItem.setDisable(true); + nextBlendingModeMenuItem.setDisable(true); + + restCaptureSetAMenuItem.setDisable(false); + restCaptureSetBMenuItem.setDisable(false); + } + + public void setClosedProjectState() { + newProjectButton.setDisable(false); + + captureSetALabel.setDisable(false); + captureSetAChoiceBox.setDisable(false); + newCaptureSetAButton.setDisable(false); + + captureSetBLabel.setDisable(false); + captureSetBChoiceBox.setDisable(false); + newCaptureSetBButton.setDisable(false); + + runButton.setDisable(false); + stopButton.setDisable(false); + + projectViewSplitPane.setDisable(false); + statusToolBar.setDisable(false); + + // close project state. + newCaptureSet.setDisable(true); + closeProjectMenuItem.setDisable(true); + copyFilePathMenuItem.setDisable(true); + highlightMenuItem.setDisable(true); + nextBlendingModeMenuItem.setDisable(true); + restCaptureSetAMenuItem.setDisable(true); + restCaptureSetBMenuItem.setDisable(true); + } + + public Project getCurrentProject() { + return currentProject; + } + + public void setNewCaptureSet(MenuItem newCaptureSet) { + this.newCaptureSet = newCaptureSet; + } + + public void setCloseProjectMenuItem(MenuItem closeProjectMenuItem) { + this.closeProjectMenuItem = closeProjectMenuItem; + } + + public void setCopyFilePathMenuItem(MenuItem copyFilePathMenuItem) { + this.copyFilePathMenuItem = copyFilePathMenuItem; + } + + public void setResetCaptureAMenuItem(MenuItem restCaptureSetAMenuItem) { + this.restCaptureSetAMenuItem = restCaptureSetAMenuItem; + } + + public void setResetCaptureBMenuItem(MenuItem restCaptureSetBMenuItem) { + this.restCaptureSetBMenuItem = restCaptureSetBMenuItem; + } + + public void setHighlightMenuItem(MenuItem highlightMenuItem) { + this.highlightMenuItem = highlightMenuItem; + } + + public void setNextBlendingModeMenuItem(MenuItem nextBlendingModeMenuItem) { + this.nextBlendingModeMenuItem = nextBlendingModeMenuItem; + } + + public void setNewProjectButton(Button newProjectButton) { + this.newProjectButton = newProjectButton; + } + + public void setRunButton(Button runButton) { + this.runButton = runButton; + } + + public void setStopButton(Button stopButton) { + this.stopButton = stopButton; + } + + public void setCaptureSetALabel(Label captureSetALabel) { + this.captureSetALabel = captureSetALabel; + } + + public void setCaptureSetAChoiceBox(ChoiceBox captureSetAChoiceBox) { + this.captureSetAChoiceBox = captureSetAChoiceBox; + } + + public void setNewCaptureSetAButton(Button newCaptureSetAButton) { + this.newCaptureSetAButton = newCaptureSetAButton; + } + + public void setCaptureSetBLabel(Label captureSetBLabel) { + this.captureSetBLabel = captureSetBLabel; + } + + public void setCaptureSetBChoiceBox(ChoiceBox captureSetBChoiceBox) { + this.captureSetBChoiceBox = captureSetBChoiceBox; + } + + public void setNewCaptureSetBButton(Button newCaptureSetBButton) { + this.newCaptureSetBButton = newCaptureSetBButton; + } + + public void setProjectViewSplitPane(SplitPane projectViewSplitPane) { + this.projectViewSplitPane = projectViewSplitPane; + } + + public void setStatusToolBar(ToolBar statusToolBar) { + this.statusToolBar = statusToolBar; + } + + public void setStatusLabel(Label statusLabel) { + this.statusLabel = statusLabel; + } + + public void setProjectBorderPane(BorderPane projectBorderPane) { + this.projectBorderPane = projectBorderPane; + } + + public void setProjectTabSet(ProjectPropertiesTabSet projectTabSet) { + this.projectTabSet = projectTabSet; + } + + public void setProjectCompareView(ProjectCompareView projectCompareView) { + this.projectCompareView = projectCompareView; + } + + public void setComparatorPane(ComparatorPane comparatorPane) { + this.comparatorPane = comparatorPane; + } + + public void setProjectUtilityPane(ProjectUtilityPane projectUtilityPane) { + this.projectUtilityPane = projectUtilityPane; + } + + public void setProgressBar(ProgressBar progressBar) { + this.progressBar = progressBar; + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/PreferencesController.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/PreferencesController.java new file mode 100644 index 000000000..4b5946100 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/PreferencesController.java @@ -0,0 +1,156 @@ +package org.icepdf.qa.viewer.common; + +import org.icepdf.qa.viewer.comparitors.ImageComparePane; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.prefs.Preferences; + +/** + * Stores application specific data such as window position and relative project paths. + */ +public class PreferencesController { + + public static final String WINDOW_LOCATION_X_KEY = "windowLocationX"; + public static final String WINDOW_LOCATION_Y_KEY = "windowLocationY"; + + public static final String WINDOW_LOCATION_WIDTH_KEY = "windowLocationWidth"; + public static final String WINDOW_LOCATION_HEIGHT_KEY = "windowLocationHeight"; + + public static final String VIEW_DIVIDER_LOCATION_KEY = "dividerLocation"; + + public static final String PROJECT_BASE_PATH_KEY = "projectBasePath"; + public static final String CAPTURE_RESULTS_BASE_PATH_KEY = "captureResultBasePath"; + public static final String CONTENT_SET_BASE_PATH_KEY = "contentSetBasePath"; + public static final String IMAGE_COMPARE_DEFAULT_VIEW_KEY = "imageCompareDefaultView"; + public static final String IMAGE_COMPARE_DEFAULT_VIEW_VALUE = ImageComparePane.SINGLE_COMPARE_VIEW; + public static final String IMAGE_COMPARE_BLENDING_MODE_KEY = "imageCompareBlendingMode"; + public static final String IMAGE_COMPARE_BLENDING_MODE_VALUE = ImageComparePane.DIFFERENCE_BLENDING_MODE; + public static final String IMAGE_COMPARE_DIFF_ENABLED_KEY = "imageCompareDiffEnabled"; + public static final boolean IMAGE_COMPARE_DIFF_ENABLED_VALUE = true; + public static final String LAST_CONTENT_SET_KEY = "contentSetBasePath"; + public static final String APPLICATION_HOME_PATH_KEY = "applicationHomePath"; + + public static final String LAST_PROJECT_KEY = "lastProjectName"; + + private static final String HOME = System.getProperties().getProperty("user.home"); + private static String defaultApplicationHome = HOME + "/.icepdf/icepdf-qa/"; + private static String captureSetBasePath = defaultApplicationHome + "captures/"; + private static String contentSetBasePath = defaultApplicationHome + "contentSets/"; + private static String projectBasePath = defaultApplicationHome + "projects/"; + private static String lastContentSetFilesDirectory = HOME + "/dev/pdf-qa/metrics/"; + private static String productClassPathDirectory = HOME + "/dev/products/"; + // folder for each content set file name. + private static String resultsPathDirectory = HOME + "/dev/pdf-qa/results/"; + + private static Preferences prefs = Preferences.userNodeForPackage(PreferencesController.class); + + private static PreferencesController preferencesController; + + static { + // setup default directory's for storing project data. + defaultApplicationHome = prefs.get(APPLICATION_HOME_PATH_KEY, defaultApplicationHome); + } + + public static Path getLastUsedProjectPath() { + String lastProjectName = prefs.get(LAST_PROJECT_KEY, null); + if (lastProjectName != null) { + Path projectPath = Paths.get(projectBasePath, lastProjectName); + if (Files.exists(projectPath)) { + return projectPath; + } + } + return null; + } + + public static void saveLastUedProject(Path filePath) { + prefs.put(LAST_PROJECT_KEY, filePath.getFileName().toString()); + } + + public static Path getLastUsedContentSetPath() { + String lastContentSetPath = prefs.get(LAST_CONTENT_SET_KEY, lastContentSetFilesDirectory); + if (lastContentSetPath != null) { + Path projectPath = Paths.get(lastContentSetPath); + if (Files.exists(projectPath)) { + return projectPath; + } + } + return null; + } + + public static String getLastUsedImageCompareView() { + return prefs.get(IMAGE_COMPARE_DEFAULT_VIEW_KEY, IMAGE_COMPARE_DEFAULT_VIEW_VALUE); + } + + public static void saveLastUsedImageCompareView(String lastImageCompareName) { + prefs.put(IMAGE_COMPARE_DEFAULT_VIEW_KEY, lastImageCompareName); + } + + public static String getLastUsedImageCompareBlendingMode() { + return prefs.get(IMAGE_COMPARE_BLENDING_MODE_KEY, IMAGE_COMPARE_BLENDING_MODE_VALUE); + } + + public static void saveLastUsedImageCompareBlendingMode(String lastImageCompareName) { + prefs.put(IMAGE_COMPARE_BLENDING_MODE_KEY, lastImageCompareName); + } + + public static boolean getLastUsedImageCompareDiffEnabled() { + return prefs.getBoolean(IMAGE_COMPARE_DIFF_ENABLED_KEY, IMAGE_COMPARE_DIFF_ENABLED_VALUE); + } + + public static void saveLastUsedImageCompareDiffEnabled(boolean lastImageCompareName) { + prefs.putBoolean(IMAGE_COMPARE_DIFF_ENABLED_KEY, lastImageCompareName); + } + + public static void saveLastUsedContentSetPath(Path directoryPath) { + prefs.put(LAST_CONTENT_SET_KEY, directoryPath.getFileName().toString()); + } + + /** + * Path locations + */ + public static String getProjectDirectory() { + return projectBasePath; + } + + public static String getCaptureSetDirectory() { + return captureSetBasePath; + } + + public static String getContentSetDirectory() { + return contentSetBasePath; + } + + public static String getContentSetFilesDiretory() { + return lastContentSetFilesDirectory; + } + + public static String getProductClassPathDirectory() { + return productClassPathDirectory; + } + + public static String getResultsPathDirectory() { + return resultsPathDirectory; + } + + /** + * Viewer preferences. + */ + + public void setWindowLocation(int x, int y) { + + } + + public void setWindowSize(int width, int height) { + + } + + public void getWindowLocation() { + + } + + public void getWindowDimension() { + + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Viewer.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Viewer.java new file mode 100644 index 000000000..1649d4429 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/common/Viewer.java @@ -0,0 +1,119 @@ +package org.icepdf.qa.viewer.common; + +import javafx.application.Platform; +import org.icepdf.qa.config.CaptureSet; +import org.icepdf.qa.config.Result; + +import javax.swing.*; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.Locale; +import java.util.Properties; +import java.util.ResourceBundle; + +/** + * Launches the PDF viewer RI via the class path specified in the capture set and loads the sample file + * as specified in the Result object. + */ +public class Viewer { + + // font manager reflection + public static final String MESSAGE_BUNDLE_CLASS = "org.icepdf.ri.resources.MessageBundle"; + public static final String PROPERTIES_MANAGER_CLASS = "org.icepdf.ri.util.PropertiesManager"; + public static final String FONT_PROPERTIES_MANAGER_CLASS = "org.icepdf.ri.util.FontPropertiesManager"; + + public static final String SWING_CONTROLLER_CLASS = "org.icepdf.ri.common.SwingController"; + public static final String OPEN_DOCUMENT_METHOD = "openDocument"; + + public static final String SWING_VIEW_BUILDER_CLASS = "org.icepdf.ri.common.SwingViewBuilder"; + public static final String BUILD_VIEWER_PANEL_METHOD = "buildViewerFrame"; + + public static void launchViewer(Result result, CaptureSet captureSet) throws InvocationTargetException, InterruptedException { + Platform.runLater(() -> { + try { + SwingUtilities.invokeAndWait(() -> { + + String filePath = result.getDocumentName(); + + Path baseDir = Paths.get(captureSet.getClassPath()); + ArrayList classPath = new ArrayList<>(baseDir.getNameCount()); + try (DirectoryStream stream = Files.newDirectoryStream(baseDir, "*.{class,jar}")) { + for (Path file : stream) { + classPath.add(file.normalize().toUri().toURL()); + } + } catch (IOException | DirectoryIteratorException x) { + System.err.println(x); + } + URLClassLoader classLoader = URLClassLoader.newInstance(classPath.toArray(new URL[classPath.size()])); + + /** + * Create a new instance so we can view the modified file. + */ + try { + + // load the font manager + ResourceBundle messageBundle = ResourceBundle.getBundle(MESSAGE_BUNDLE_CLASS, + Locale.ENGLISH, classLoader); + Class propertiesManagerClass = classLoader.loadClass(PROPERTIES_MANAGER_CLASS); + Constructor propertiesManagerConstructor = propertiesManagerClass.getDeclaredConstructor( + Properties.class, ResourceBundle.class); + Object propertiesManagerObject = propertiesManagerConstructor.newInstance(System.getProperties(), messageBundle); + + Class fontPropertiesManagerClass = classLoader.loadClass(FONT_PROPERTIES_MANAGER_CLASS); + Constructor fontPropertiesManagerConstructor = fontPropertiesManagerClass.getDeclaredConstructor( + propertiesManagerClass, Properties.class, ResourceBundle.class); + fontPropertiesManagerConstructor.newInstance(propertiesManagerObject, System.getProperties(), messageBundle); + + Class swingControllerClass = classLoader.loadClass(SWING_CONTROLLER_CLASS); + Constructor swingControllerConstructor = swingControllerClass.getDeclaredConstructor(); + Object swingControllerObject = swingControllerConstructor.newInstance(); + + Class swingViewBuilderClass = classLoader.loadClass(SWING_VIEW_BUILDER_CLASS); + Constructor swingViewBuilderConstructor = swingViewBuilderClass.getDeclaredConstructor(swingControllerClass); + Object swingViewBuilderObject = swingViewBuilderConstructor.newInstance(swingControllerObject); + + // build the gui elements. + Method buildViewerPanelMethod = swingViewBuilderClass.getMethod(BUILD_VIEWER_PANEL_METHOD); + JFrame applicationFrame = (JFrame) buildViewerPanelMethod.invoke(swingViewBuilderObject); + + // open the document + Method openDocumentMethod = swingControllerClass.getMethod(OPEN_DOCUMENT_METHOD, String.class); + openDocumentMethod.invoke(swingControllerObject, filePath); + + applicationFrame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + + + // show the document and the new annotations. + applicationFrame.pack(); + applicationFrame.setVisible(true); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + + }); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + + ); + + } + +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPane.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPane.java new file mode 100644 index 000000000..f9c5fbb07 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPane.java @@ -0,0 +1,16 @@ +package org.icepdf.qa.viewer.comparitors; + +import javafx.scene.layout.BorderPane; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * ComparatorPane base class for visual compare panes. + */ +public abstract class ComparatorPane extends BorderPane implements ComparatorPaneInterface { + + protected Mediator mediator; + + public ComparatorPane(Mediator mediator) { + this.mediator = mediator; + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPaneInterface.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPaneInterface.java new file mode 100644 index 000000000..100713299 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorPaneInterface.java @@ -0,0 +1,16 @@ +package org.icepdf.qa.viewer.comparitors; + +import org.icepdf.qa.config.Result; + +/** + * Work in progress but a comparator needs to open the result and show some sort of comparator related + * data to the end user. + */ +public interface ComparatorPaneInterface { + + void openResult(Result result); + + void toggleDiffFilter(); + + void nextDiffFilter(); +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorsViewFactory.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorsViewFactory.java new file mode 100644 index 000000000..b5146f01d --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ComparatorsViewFactory.java @@ -0,0 +1,27 @@ +package org.icepdf.qa.viewer.comparitors; + +import org.icepdf.qa.config.CaptureSet; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * Factory for build compare panes based on the CaptureSet type. It's always assumed that + * comparisons will be done using two captures sets with the same type. + */ +public class ComparatorsViewFactory { + + private ComparatorsViewFactory() { + } + + public static ComparatorPane buildComparatorView(CaptureSet.Type type, Mediator mediator) { + switch (type) { + case capture: + return new ImageComparePane(mediator); + case metric: + return new MetricComparePane(mediator); + case textExtraction: + return new TextComparePane(mediator); + default: + return null; + } + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ImageComparePane.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ImageComparePane.java new file mode 100644 index 000000000..c007938fd --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/ImageComparePane.java @@ -0,0 +1,225 @@ +package org.icepdf.qa.viewer.comparitors; + +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.effect.BlendMode; +import javafx.scene.effect.DropShadow; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.ScrollEvent; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import org.icepdf.qa.config.Result; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.PreferencesController; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; + +/** + * Image compare pane allows the easy identification of changed + */ +public class ImageComparePane extends ComparatorPane { + + public static final String SINGLE_COMPARE_VIEW = "Single"; + public static final String SIDE_BY_SIDE_COMPARE_VIEW = "Side-by-side"; + public static final String SCALED_COMPARE_VIEW = "Scaled"; + + public static final String DIFFERENCE_BLENDING_MODE = "Difference"; + public static final String GREEN_BLENDING_MODE = "Green Subtraction"; + public static final String MULTIPLY_BLENDING_MODE = "Multiply"; + + // toggle value for enabling/disabling the compare effect. + private boolean enableBlendingMode; + + // image holders. + private ImageView imageViewA1; + private ImageView imageViewA2; + private ImageView imageViewB1; + private ImageView imageViewB2; + + // view type, side-by-side, blending mode etc. + private ChoiceBox viewTypesChoiceBox; + // effects that can be applied to show differences. + private ChoiceBox blendingModeChoiceBox; + private ToggleButton blendingToggleButton; + + private ScrollPane scrollPane; + + public ImageComparePane(Mediator mediator) { + super(mediator); + try { + imageViewA1 = new ImageView(); + imageViewB1 = new ImageView(); + imageViewA2 = new ImageView(); + imageViewB2 = new ImageView(); + + ToolBar viewTools = new ToolBar(); + // view type + Label viewLabel = new Label("View Type"); + viewTypesChoiceBox = new ChoiceBox<>(); + viewTypesChoiceBox.getItems().addAll(SINGLE_COMPARE_VIEW, SIDE_BY_SIDE_COMPARE_VIEW);//, SCALED_COMPARE_VIEW); + viewTypesChoiceBox.getSelectionModel().select(PreferencesController.getLastUsedImageCompareView()); + viewTypesChoiceBox.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + PreferencesController.saveLastUsedImageCompareView(newValue); + refreshView(); + }); + + // Blending Modes + Label modeLabel = new Label("Mode:"); + blendingModeChoiceBox = new ChoiceBox<>(); + blendingModeChoiceBox.getItems().addAll( + DIFFERENCE_BLENDING_MODE, GREEN_BLENDING_MODE);//, MULTIPLY_BLENDING_MODE); + blendingModeChoiceBox.getSelectionModel().select(PreferencesController.getLastUsedImageCompareBlendingMode()); + blendingModeChoiceBox.getSelectionModel().selectedItemProperty().addListener(( + observable, oldValue, newValue) -> { + PreferencesController.saveLastUsedImageCompareBlendingMode(newValue); + refreshView(); + }); + enableBlendingMode = PreferencesController.getLastUsedImageCompareDiffEnabled(); + blendingToggleButton = new ToggleButton("On"); + blendingToggleButton.setSelected(enableBlendingMode); + blendingToggleButton.setOnAction(event -> enableBlendingMode()); + + viewTools.getItems().addAll(viewLabel, viewTypesChoiceBox, modeLabel, blendingModeChoiceBox, blendingToggleButton); + setTop(new VBox(20, viewTools)); + scrollPane = new ScrollPane(); + setCenter(scrollPane); + + + enableBlendingMode(); + + + } catch (Throwable e) { + e.printStackTrace(); + } + } + + public void addMouseScrolling(Node node) { + node.setOnScroll((ScrollEvent event) -> { + // Adjust the zoom factor as per your requirement + double zoomFactor = 1.05; + double deltaY = event.getDeltaY(); + if (deltaY < 0) { + zoomFactor = 2.0 - zoomFactor; + } + node.setScaleX(node.getScaleX() * zoomFactor); + node.setScaleY(node.getScaleY() * zoomFactor); + }); + } + + private void enableBlendingMode() { + if (blendingToggleButton.isSelected()) { + blendingToggleButton.setText("On"); + enableBlendingMode = true; + } else { + blendingToggleButton.setText("Off"); + enableBlendingMode = false; + } + refreshView(); + PreferencesController.saveLastUsedImageCompareDiffEnabled(enableBlendingMode); + } + + public void nextDiffFilter() { + int size = blendingModeChoiceBox.getItems().size(); + int selectedIndex = blendingModeChoiceBox.getSelectionModel().getSelectedIndex() + 1; + if (selectedIndex >= size) { + selectedIndex = 0; + } + blendingModeChoiceBox.getSelectionModel().select(selectedIndex); + } + + public void toggleDiffFilter() { + blendingToggleButton.setSelected(!blendingToggleButton.isSelected()); + enableBlendingMode(); + } + + public void openResult(Result result) { + if (result != null) { + Image image1; + Image image2; + // build out the image resources. + try { + File file = new File(result.getCaptureNameA()); + File file2 = new File(result.getCaptureNameB()); + image1 = new Image(new FileInputStream(file)); + image2 = new Image(new FileInputStream(file2)); + imageViewA1.setImage(image1); + imageViewB1.setImage(image2); + imageViewA2.setImage(image1); + imageViewB2.setImage(image2); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } else { + imageViewA1.setImage(null); + imageViewB1.setImage(null); + imageViewA2.setImage(null); + imageViewB2.setImage(null); + } + } + + public void refreshView() { + + String viewType = viewTypesChoiceBox.getSelectionModel().getSelectedItem(); + String blendingMode = blendingModeChoiceBox.getSelectionModel().getSelectedItem(); + scrollPane.setContent(null); + clearBlending(imageViewA1, imageViewA2, imageViewB1, imageViewB2); + if (SINGLE_COMPARE_VIEW.equals(viewType)) { + applyBlending(imageViewB1, blendingMode); + StackPane singleViewPane = new StackPane(); + singleViewPane.setPadding(new Insets(25, 25, 25, 25)); + singleViewPane.setEffect(new DropShadow()); + singleViewPane.getChildren().removeAll(); + singleViewPane.getChildren().addAll(imageViewA1, imageViewB1); + scrollPane.setContent(singleViewPane); + } else if (SIDE_BY_SIDE_COMPARE_VIEW.equals(viewType)) { + GridPane sideBySidePane = new GridPane(); + sideBySidePane.setPadding(new Insets(25, 25, 25, 25)); + sideBySidePane.setHgap(25); + sideBySidePane.setVgap(25); + StackPane leftImagePane = new StackPane(); + leftImagePane.setEffect(new DropShadow()); + applyBlending(imageViewA1, blendingMode); + leftImagePane.getChildren().addAll(imageViewB1, imageViewA1); + StackPane rightImagePane = new StackPane(); + rightImagePane.setEffect(new DropShadow()); + applyBlending(imageViewB2, blendingMode); + rightImagePane.getChildren().addAll(imageViewA2, imageViewB2); + + sideBySidePane.add(leftImagePane, 0, 0); + sideBySidePane.add(rightImagePane, 1, 0); + + scrollPane.setContent(sideBySidePane); + + } else if (SCALED_COMPARE_VIEW.equals(viewType)) { +// gridPane.minWidthProperty().bind(Bindings.createDoubleBinding(() -> +// scrollPane.getViewportBounds().getWidth(), scrollPane.viewportBoundsProperty())); + } + + } + + private void applyBlending(ImageView imageView, String blendingMode) { + if (enableBlendingMode) { + if (DIFFERENCE_BLENDING_MODE.equals(blendingMode)) { + imageView.setBlendMode(BlendMode.DIFFERENCE); + } else if (GREEN_BLENDING_MODE.equals(blendingMode)) { + imageView.setBlendMode(BlendMode.GREEN); + } else if (MULTIPLY_BLENDING_MODE.equals(blendingMode)) { + imageView.setBlendMode(BlendMode.MULTIPLY); + } + } else { + imageView.setBlendMode(null); + } + } + + private void clearBlending(ImageView... imageViews) { + for (ImageView imageView : imageViews) { + imageView.setBlendMode(null); + } + } + +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/MetricComparePane.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/MetricComparePane.java new file mode 100644 index 000000000..9f85aa853 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/MetricComparePane.java @@ -0,0 +1,28 @@ +package org.icepdf.qa.viewer.comparitors; + +import org.icepdf.qa.config.Result; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * Todo, chart base comparison of capture times, hopefully showing potential performance regressions. + */ +public class MetricComparePane extends ComparatorPane { + + public MetricComparePane(Mediator mediator) { + super(mediator); + } + + @Override + public void openResult(Result result) { + + } + + public void toggleDiffFilter() { + + } + + public void nextDiffFilter() { + + } + +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/TextComparePane.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/TextComparePane.java new file mode 100644 index 000000000..190f722a9 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/comparitors/TextComparePane.java @@ -0,0 +1,27 @@ +package org.icepdf.qa.viewer.comparitors; + +import org.icepdf.qa.config.Result; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * Still not sure how this will look, but needs a nice diff tool for comparing extracted text. + */ +public class TextComparePane extends ComparatorPane { + + public TextComparePane(Mediator mediator) { + super(mediator); + } + + @Override + public void openResult(Result result) { + + } + + public void toggleDiffFilter() { + + } + + public void nextDiffFilter() { + + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/AbstractDialog.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/AbstractDialog.java new file mode 100644 index 000000000..e5935066c --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/AbstractDialog.java @@ -0,0 +1,24 @@ +package org.icepdf.qa.viewer.project; + +import javafx.scene.control.Dialog; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * Core functionality for custom dialogs. + */ +public class AbstractDialog extends Dialog { + + public AbstractDialog(Mediator mediator) { + + initStyle(StageStyle.UTILITY); + setResizable(true); + + Stage primaryStage = mediator.getPrimaryStage(); + double x = primaryStage.getX() + primaryStage.getWidth() / 2 - 100; + double y = primaryStage.getY() + primaryStage.getHeight() / 2 - 100; + setX(x); + setY(y); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/CaptureSetPropertyPane.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/CaptureSetPropertyPane.java new file mode 100644 index 000000000..6f82bb3d8 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/CaptureSetPropertyPane.java @@ -0,0 +1,325 @@ +package org.icepdf.qa.viewer.project; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.geometry.VPos; +import javafx.scene.control.*; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.stage.DirectoryChooser; +import org.icepdf.qa.config.CaptureSet; +import org.icepdf.qa.config.ConfigSerializer; +import org.icepdf.qa.config.ContentSet; +import org.icepdf.qa.config.Project; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.PreferencesController; + +import java.io.File; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * private String name; + *

+ * private Type type; + *

+ * private String version; + * private String classPath; + * private int capturePageCount; + *

+ * private String jdkVersion; + * private String systemProperties; + *

+ * private List contentSets; + *

+ * private String relativePath; + */ +public class CaptureSetPropertyPane extends TitledPane implements EventHandler { + + private Mediator mediator; + private Project currentProject; + private CaptureSet captureSet; + + private TextField captureCountTextField; + private TextField captureSetNameTextField; + private ChoiceBox captureSetTypes; + private TextField classPathTextField; + private ListView contentList; + + private Button addButton; + private Button editButton; + private Button removeButton; + + public CaptureSetPropertyPane(String name, Mediator mediator) { + super(name, null); + setCollapsible(false); + this.mediator = mediator; + + GridPane gridPane = new GridPane(); + gridPane.setPadding(new Insets(8, 8, 8, 8)); + gridPane.setHgap(4); + gridPane.setVgap(4); + + Insets labelInsets = new Insets(0, 10, 0, 0); + Insets inputInsets = new Insets(0, 0, 0, 0); + + ColumnConstraints lableColumn = new ColumnConstraints(); + lableColumn.setPrefWidth(75); + ColumnConstraints fieldColumn = new ColumnConstraints(); + fieldColumn.setPercentWidth(75); + gridPane.getColumnConstraints().add(lableColumn);//2*50 percent +// gridPane.getColumnConstraints().add(fieldColumn); + + // content name. + Label projectNameLabel = new Label("Name:"); + captureSetNameTextField = new TextField(); + captureSetNameTextField.focusedProperty().addListener((arg0, oldValue, newValue) -> { + // focus lost. + if (!newValue && captureSetNameTextField.getText().isEmpty()) { + // check if the file already exists + captureSetNameTextField.setText("Cannot be null."); + } else if (!newValue) { + // save the state. + if (captureSet != null) { + captureSet.setName(captureSetNameTextField.getText().trim()); + ConfigSerializer.save(captureSet); + } + } + }); + GridPane.setMargin(projectNameLabel, labelInsets); + GridPane.setMargin(captureSetNameTextField, inputInsets); + GridPane.setConstraints(projectNameLabel, 0, 0, 3, 1); + GridPane.setConstraints(captureSetNameTextField, 1, 0, 3, 1); + + // type. + Label captureTypeLabel = new Label("Type:"); + captureSetTypes = new ChoiceBox<>(); + for (CaptureSet.Type type : CaptureSet.Type.values()) { + captureSetTypes.getItems().add(type); + } + captureSetTypes.setDisable(true); + GridPane.setMargin(captureSetTypes, inputInsets); + GridPane.setConstraints(captureTypeLabel, 0, 1, 3, 1); + GridPane.setConstraints(captureSetTypes, 1, 1, 3, 1); + + // page capture count + + Label captureCountTypeLabel = new Label("Capture Count:"); + captureCountTextField = new TextField(); + captureCountTextField.focusedProperty().addListener((arg0, oldValue, newValue) -> { + // focus lost. + if (!newValue && captureCountTextField.getText().isEmpty()) { + // check if the file already exists + captureCountTextField.setText("Cannot be null."); + } else if (!newValue) { + // save the state. + captureSet.setCapturePageCount(Integer.parseInt(captureCountTextField.getText().trim())); + captureSet.setComplete(false); + ConfigSerializer.save(captureSet); + } + }); + captureCountTextField.textProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue.matches("\\d*")) { + captureCountTextField.setText(newValue.replaceAll("[^\\d]", "")); + } + }); + GridPane.setMargin(captureCountTextField, inputInsets); + GridPane.setConstraints(captureCountTypeLabel, 0, 2, 3, 1); + GridPane.setConstraints(captureCountTextField, 1, 2, 3, 1); + + // classpath. + Label classPathLabel = new Label("ClassPath:"); + classPathTextField = new TextField(); + GridPane.setMargin(classPathLabel, labelInsets); + GridPane.setMargin(classPathTextField, inputInsets); + GridPane.setConstraints(classPathLabel, 0, 3, 3, 1); + GridPane.setConstraints(classPathTextField, 1, 3, 3, 1); + // browse button + Button browseButton = new Button("Browse"); + browseButton.setOnAction(event -> { + DirectoryChooser fileChooser = new DirectoryChooser(); + fileChooser.setTitle("Version class path"); + fileChooser.setInitialDirectory(Paths.get(PreferencesController.getProductClassPathDirectory()).toFile()); + File selectedFile = fileChooser.showDialog(mediator.getPrimaryStage()); + if (selectedFile != null) { + classPathTextField.setText(selectedFile.getPath()); + captureSet.setClassLoader(null); + // save state. +// String baseDir = PreferencesController.getProductClassPathDirectory(); +// String classPath = selectedFile.getPath().replace(baseDir, ""); + captureSet.setClassPath(selectedFile.getPath()); + captureSet.setComplete(false); + ConfigSerializer.save(captureSet); + } + }); + GridPane.setMargin(browseButton, inputInsets); + GridPane.setHalignment(browseButton, HPos.RIGHT); + GridPane.setConstraints(browseButton, 3, 4, 1, 1); + + // content sets + Label contentSetLabel = new Label("Test Set:"); + contentList = new ListView<>(); + contentList.setMaxHeight(75); + contentList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + contentList.getSelectionModel().selectedIndexProperty().addListener(observable -> { + List selectedItems = contentList.getSelectionModel().getSelectedItems(); + if (selectedItems.size() > 0) { + removeButton.setDisable(false); + } else { + removeButton.setDisable(true); + } + if (selectedItems.size() == 1) { + editButton.setDisable(false); + } else { + editButton.setDisable(true); + } + }); + GridPane.setMargin(contentSetLabel, labelInsets); + GridPane.setMargin(contentList, inputInsets); + GridPane.setConstraints(contentSetLabel, 0, 5, 3, 1); + GridPane.setValignment(contentSetLabel, VPos.TOP); + GridPane.setConstraints(contentList, 1, 5, 3, 1); + + // add testAndAnalyze set controls, add, remove, edit + addButton = new Button("Add"); + addButton.setStyle(String.format("-fx-base: %s;", "indigo")); + addButton.setOnAction(this); + editButton = new Button("Edit"); + editButton.setStyle(String.format("-fx-base: %s;", "green")); + editButton.setDisable(true); + editButton.setOnAction(this); + removeButton = new Button("Remove"); + removeButton.setStyle(String.format("-fx-base: %s;", "red")); + removeButton.setDisable(true); + removeButton.setOnAction(this); + GridPane.setHalignment(addButton, HPos.RIGHT); + GridPane.setHalignment(editButton, HPos.RIGHT); + GridPane.setHalignment(removeButton, HPos.RIGHT); + GridPane.setConstraints(addButton, 1, 6, 1, 1); + GridPane.setConstraints(editButton, 2, 6, 1, 1); + GridPane.setConstraints(removeButton, 3, 6, 1, 1); + + gridPane.getChildren().addAll( + projectNameLabel, captureSetNameTextField, + captureTypeLabel, captureSetTypes, + captureCountTypeLabel, captureCountTextField, + classPathLabel, classPathTextField, browseButton, + contentSetLabel, contentList, + addButton, editButton, removeButton + ); + setContent(gridPane); + } + + public void setProject(Project project, CaptureSet captureSet) { + currentProject = project; + this.captureSet = captureSet; + // data injections + if (captureSet != null) { + captureSetNameTextField.setText(captureSet.getName()); + captureSetTypes.getSelectionModel().select(captureSet.getType()); + classPathTextField.setText(captureSet.getClassPath()); + captureCountTextField.setText(String.valueOf(captureSet.getCapturePageCount())); + // build out the content set's uses in this capture. + ObservableList stringList = FXCollections.observableArrayList(); + List contentSetNames = captureSet.getContentSets(); + // check if each is still a valid file + List contentSets = ConfigSerializer.retrieveAllContentSets(); + if (contentSets != null) { + for (ContentSet contentSet : contentSets) { + for (String name : contentSetNames) { + if (contentSet.getName().equals(name)) { + stringList.add(name); + } + } + } + } + contentList.getItems().clear(); + contentList.getItems().addAll(stringList); + } + } + + @Override + public void handle(ActionEvent event) { + Object source = event.getSource(); + if (source.equals(addButton)) { + ContentSetSelectorDialog dialog = new ContentSetSelectorDialog(mediator, contentList.getItems()); + Optional> result = dialog.showAndWait(); + // load the project data and enable the ui + result.ifPresent(contentSet -> addContent(contentSet)); + } else if (source.equals(removeButton)) { + List selectedItems = contentList.getSelectionModel().getSelectedItems(); + contentList.getItems().removeAll(selectedItems); + // save teh new state. + captureSet.getContentSets().clear(); + captureSet.getContentSets().addAll(contentList.getSelectionModel().getSelectedItems()); + captureSet.setComplete(false); + ConfigSerializer.save(captureSet); + } else if (source.equals(editButton)) { + String selectedName = contentList.getSelectionModel().getSelectedItem(); + List contentSets = ConfigSerializer.retrieveAllContentSets(); + ContentSet selectedContentSet = null; + if (contentSets != null) { + for (ContentSet contentSet : contentSets) { + if (contentSet.getName().equals(selectedName)) { + selectedContentSet = contentSet; + break; + } + } + } + if (selectedContentSet != null) { + NewContentSetDialog dialog = new NewContentSetDialog(mediator); + dialog.setContentSet(selectedContentSet); + String oldName = selectedContentSet.getName(); + Optional result = dialog.showAndWait(); + // load the project data and enable the ui + result.ifPresent(contentSet -> editContent(contentSet, oldName)); + } + } + } + + public void editContent(ContentSet selectedContentSet, String oldName) { + + // we only need to save the capture set if the name has changed. + if (!selectedContentSet.getName().equals(oldName)) { + List contentSetFileNames = new ArrayList<>(); + List contentSets = captureSet.getContentSets(); + for (String contentSet : contentSets) { + if (contentSet.equals(oldName)) { + contentSetFileNames.add(selectedContentSet.getName()); + } else { + contentSetFileNames.add(contentSet); + } + } + + captureSet.setContentSets(contentSetFileNames); + ConfigSerializer.save(captureSet); + + // update the list to reflect the change. + contentList.getItems().clear(); + contentList.getItems().addAll(contentSetFileNames); + } + // mark the set as changed, we just assume. + captureSet.setComplete(false); + } + + public void addContent(ObservableList selectedContentSets) { + List contentSetFileNames = new ArrayList<>(); + for (ContentSet contentSet : selectedContentSets) { + contentSetFileNames.add(contentSet.getName()); + } + captureSet.setContentSets(contentSetFileNames); + captureSet.setComplete(false); + ConfigSerializer.save(captureSet); + + // update the list to reflect the change. + contentList.getItems().addAll(contentSetFileNames); + } + + +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ContentSetSelectorDialog.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ContentSetSelectorDialog.java new file mode 100644 index 000000000..13ab08128 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ContentSetSelectorDialog.java @@ -0,0 +1,72 @@ +package org.icepdf.qa.viewer.project; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Insets; +import javafx.geometry.VPos; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import org.icepdf.qa.config.ConfigSerializer; +import org.icepdf.qa.config.ContentSet; +import org.icepdf.qa.viewer.common.Mediator; + +import java.util.List; + +/** + * Created by pcorl_000 on 2017-02-14. + */ +public class ContentSetSelectorDialog extends AbstractDialog> { + + private ListView contentList; + + public ContentSetSelectorDialog(Mediator mediator, List selectedContent) { + super(mediator); + + setTitle("Capture Set Selection"); + setHeaderText("Select content sets to add to capture set."); + + // read the available content sets + List contentSets = ConfigSerializer.retrieveAllContentSets(); + for (String contentName : selectedContent) { + for (ContentSet contentSet : contentSets) { + if (contentName.equals(contentSet.getName())) { + contentSets.remove(contentSet); + break; + } + } + } + final ObservableList stringList = FXCollections.observableArrayList(contentSets); + contentList = new ListView<>(stringList); + contentList.setMaxHeight(75); + contentList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + GridPane grid = new GridPane(); + grid.setPadding(new Insets(8, 8, 8, 8)); + grid.setHgap(4); + grid.setVgap(4); + Insets labelInsets = new Insets(0, 10, 0, 0); + Insets inputInsets = new Insets(0, 0, 0, 0); + + Label captureSetNameLabel = new Label("Content sets: "); + GridPane.setMargin(captureSetNameLabel, labelInsets); + GridPane.setMargin(contentList, inputInsets); + GridPane.setValignment(captureSetNameLabel, VPos.TOP); + GridPane.setConstraints(contentList, 1, 4, 3, 1); + + // setup create button mask and canel. + ButtonType buttonTypeOk = new ButtonType("Add", ButtonBar.ButtonData.OK_DONE); + ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + getDialogPane().getButtonTypes().addAll(buttonTypeOk, buttonTypeCancel); + setResultConverter(buttonType -> { + // validation passed, save the project file. + if (buttonType == buttonTypeOk) { + return contentList.getSelectionModel().getSelectedItems(); + } + return null; + }); + grid.add(captureSetNameLabel, 1, 1); + grid.add(contentList, 2, 1); + getDialogPane().setContent(grid); + } + +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/MetaDataTab.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/MetaDataTab.java new file mode 100644 index 000000000..ebe5fd21d --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/MetaDataTab.java @@ -0,0 +1,15 @@ +package org.icepdf.qa.viewer.project; + +import javafx.scene.control.Tab; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * Created by pcorl_000 on 2017-02-07. + */ +public class MetaDataTab extends Tab { + + public MetaDataTab(String title, Mediator mediator) { + super(title); + setClosable(false); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewCaptureSetDialog.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewCaptureSetDialog.java new file mode 100644 index 000000000..1fb41f299 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewCaptureSetDialog.java @@ -0,0 +1,89 @@ +package org.icepdf.qa.viewer.project; + +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import org.icepdf.qa.config.CaptureSet; +import org.icepdf.qa.config.ConfigSerializer; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.PreferencesController; + +/** + * Created by pcorl_000 on 2017-02-08. + */ +public class NewCaptureSetDialog extends AbstractDialog { + + private TextField projectName; + private Label errorLabel; + + public NewCaptureSetDialog(Mediator mediator) { + super(mediator); + + setTitle("Create New Capture Set"); + setHeaderText("Create a new QA capture set."); + + // error field for presenting validation result. + errorLabel = new Label(); + errorLabel.setTextFill(Color.RED); + errorLabel.setVisible(false); + + Label captureSetNameLabel = new Label("Capture set name: "); + projectName = new TextField(); + projectName.focusedProperty(); + projectName.focusedProperty().addListener((arg0, oldValue, newValue) -> { + // focus lost. + if (!newValue) { + // check if the file already exists + validate(); + } + }); + + // setup create button mask and canel. + ButtonType buttonTypeOk = new ButtonType("Create", ButtonBar.ButtonData.OK_DONE); + ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + getDialogPane().getButtonTypes().addAll(buttonTypeOk, buttonTypeCancel); + setResultConverter(buttonType -> { + // validation passed, save the project file. + if (buttonType == buttonTypeOk) { + CaptureSet captureSet = new CaptureSet(projectName.getText(), CaptureSet.Type.capture); + ConfigSerializer.save(captureSet); + return captureSet; + } + return null; + }); + // setup validation action, if validation fails we consume the actionEvent and keep the dialog open + final Button createButton = (Button) getDialogPane().lookupButton(buttonTypeOk); + createButton.addEventFilter(ActionEvent.ACTION, ae -> { + if (!validate()) { + ae.consume(); + } + }); + + GridPane grid = new GridPane(); + grid.add(captureSetNameLabel, 1, 1); + grid.add(projectName, 2, 1); + grid.add(errorLabel, 2, 2); + getDialogPane().setContent(grid); + } + + + private boolean validate() { + // check for empty project name as we base the file name on this. + if (projectName.getText().isEmpty()) { + errorLabel.setText("Please select a field name. "); + errorLabel.setVisible(true); + return false; + } // check if the project already exists. + else if (ConfigSerializer.exists( + PreferencesController.getProjectDirectory(), + projectName.getText())) { + errorLabel.setText("File name already exists. "); + errorLabel.setVisible(true); + return false; + } else { + errorLabel.setVisible(false); + return true; + } + } +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewContentSetDialog.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewContentSetDialog.java new file mode 100644 index 000000000..c6d065861 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewContentSetDialog.java @@ -0,0 +1,163 @@ +package org.icepdf.qa.viewer.project; + +import javafx.event.ActionEvent; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import javafx.stage.DirectoryChooser; +import org.icepdf.qa.config.ConfigSerializer; +import org.icepdf.qa.config.ContentSet; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.PreferencesController; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Created by pcorl_000 on 2017-02-13. + */ +public class NewContentSetDialog extends AbstractDialog { + + private TextField contentSetNameTextField; + private TextField contentSetPathTextFeild; + private Label errorLabel; + private ContentSet contentSet; + + private ButtonType buttonTypeOk; + private boolean skipFileNameCheck = false; + + public NewContentSetDialog(Mediator mediator) { + super(mediator); + setTitle("Create New Capture Set"); + setHeaderText("Create a new QA content set."); + + // error field for presenting validation result. + errorLabel = new Label(); + errorLabel.setTextFill(Color.RED); + errorLabel.setVisible(false); + + Label captureSetNameLabel = new Label("Content set name: "); + contentSetNameTextField = new TextField(); + contentSetNameTextField.focusedProperty(); + contentSetNameTextField.focusedProperty().addListener((arg0, oldValue, newValue) -> { + // focus lost. + if (!newValue) { + // check if the file already exists + validateName(); + } + }); + + Label contentSetPathLabel = new Label("Content set path: "); + contentSetPathTextFeild = new TextField(); + contentSetPathTextFeild.focusedProperty(); + contentSetPathTextFeild.focusedProperty().addListener((arg0, oldValue, newValue) -> { + // focus lost. + if (!newValue) { + // check if the file already exists + validatePath(); + } + }); + + Button browseButton = new Button("Browse"); + browseButton.setOnAction(event -> { + DirectoryChooser fileChooser = new DirectoryChooser(); + fileChooser.setTitle("Content set path"); + fileChooser.setInitialDirectory(PreferencesController.getLastUsedContentSetPath().toFile()); + File selectedFile = fileChooser.showDialog(mediator.getPrimaryStage()); + if (selectedFile != null) { + if (Files.isDirectory(Paths.get(selectedFile.getAbsolutePath()))) { + contentSetPathTextFeild.setText(selectedFile.getName()); + } else { + contentSetPathTextFeild.setText(selectedFile.getParent()); + } + } + }); + + // setup create button mask and cancel. + buttonTypeOk = new ButtonType("Create", ButtonBar.ButtonData.OK_DONE); + ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + getDialogPane().getButtonTypes().addAll(buttonTypeOk, buttonTypeCancel); + setResultConverter(buttonType -> { + // validation passed, save the project file. + if (buttonType == buttonTypeOk) { + if (contentSet == null) { + contentSet = new ContentSet(contentSetNameTextField.getText(), contentSetPathTextFeild.getText()); + } else { + contentSet.setName(contentSetNameTextField.getText()); + contentSet.setPath(contentSetPathTextFeild.getText()); + } + // read and add files names to the content set. + contentSet.refreshFiles(); + ConfigSerializer.save(contentSet); + return contentSet; + } + return null; + }); + // setup validation action, if validation fails we consume the actionEvent and keep the dialog open + final Button createButton = (Button) getDialogPane().lookupButton(buttonTypeOk); + createButton.addEventFilter(ActionEvent.ACTION, ae -> { + if (!validateName()) { + ae.consume(); + } + }); + + GridPane grid = new GridPane(); + grid.setPadding(new Insets(8, 8, 8, 8)); + grid.setHgap(4); + grid.setVgap(4); + GridPane.setHalignment(browseButton, HPos.RIGHT); + grid.add(captureSetNameLabel, 1, 1); + grid.add(contentSetNameTextField, 2, 1); + grid.add(contentSetPathLabel, 1, 2); + grid.add(contentSetPathTextFeild, 2, 2); + grid.add(browseButton, 2, 3); + grid.add(errorLabel, 2, 4); + getDialogPane().setContent(grid); + } + + public void setContentSet(ContentSet contentSet) { + this.contentSet = contentSet; + final Button createButton = (Button) getDialogPane().lookupButton(buttonTypeOk); + createButton.setText("Update"); + skipFileNameCheck = true; + contentSetNameTextField.setText(contentSet.getName()); + contentSetPathTextFeild.setText(contentSet.getPath()); + } + + private boolean validateName() { + // check for empty project name as we base the file name on this. + if (contentSetNameTextField.getText().isEmpty()) { + errorLabel.setText("Please select a field name. "); + errorLabel.setVisible(true); + return false; + } // check if the project already exists. + else if (!skipFileNameCheck) { + if (ConfigSerializer.exists( + PreferencesController.getContentSetDirectory(), + contentSetNameTextField.getText())) { + errorLabel.setText("File name already exists. "); + errorLabel.setVisible(true); + return false; + } + return true; + } else { + errorLabel.setVisible(false); + return true; + } + } + + private boolean validatePath() { + // check for empty project name as we base the file name on this. + if (contentSetNameTextField.getText().isEmpty()) { + errorLabel.setText("Please select a path. "); + errorLabel.setVisible(true); + return false; + } else { + errorLabel.setVisible(false); + return true; + } + } +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewProjectDialog.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewProjectDialog.java new file mode 100644 index 000000000..00f6585a7 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/NewProjectDialog.java @@ -0,0 +1,93 @@ +package org.icepdf.qa.viewer.project; + +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.layout.GridPane; +import javafx.scene.paint.Color; +import org.icepdf.qa.config.ConfigSerializer; +import org.icepdf.qa.config.Project; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.PreferencesController; + +import java.nio.file.Path; + +/** + * Create new Project dialog. + */ +public class NewProjectDialog extends AbstractDialog { + + private TextField projectName; + private Label errorLabel; + + public NewProjectDialog(Mediator mediator) { + super(mediator); + + setTitle("Create New QA Project"); + setHeaderText("Create a new QA project"); + + // error field for presenting validation result. + errorLabel = new Label(); + errorLabel.setTextFill(Color.RED); + errorLabel.setVisible(false); + + Label projectLabel = new Label("Project name: "); + projectName = new TextField(); + projectName.focusedProperty(); + projectName.focusedProperty().addListener((arg0, oldValue, newValue) -> { + // focus lost. + if (!newValue) { + // check if the file already exists + validate(); + } + }); + + // setup create button mask and canel. + ButtonType buttonTypeOk = new ButtonType("Create", ButtonBar.ButtonData.OK_DONE); + ButtonType buttonTypeCancel = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); + getDialogPane().getButtonTypes().addAll(buttonTypeOk, buttonTypeCancel); + setResultConverter(buttonType -> { + // validation passed, save the project file. + if (buttonType == buttonTypeOk) { + Project project = new Project(projectName.getText()); + Path projectPath = ConfigSerializer.save(project); + project.setProjectPath(projectPath); + PreferencesController.saveLastUedProject(projectPath); + return project; + } + return null; + }); + // setup validation action, if validation fails we consume the actionEvent and keep the dialog open + final Button createButton = (Button) getDialogPane().lookupButton(buttonTypeOk); + createButton.addEventFilter(ActionEvent.ACTION, ae -> { + if (!validate()) { + ae.consume(); + } + }); + + GridPane grid = new GridPane(); + grid.add(projectLabel, 1, 1); + grid.add(projectName, 2, 1); + grid.add(errorLabel, 2, 2); + getDialogPane().setContent(grid); + } + + + private boolean validate() { + // check for empty project name as we base the file name on this. + if (projectName.getText().isEmpty()) { + errorLabel.setText("Please select a field name. "); + errorLabel.setVisible(true); + return false; + } // check if the project already exists. + else if (ConfigSerializer.exists( + PreferencesController.getProjectDirectory(), + projectName.getText())) { + errorLabel.setText("File name already exists. "); + errorLabel.setVisible(true); + return false; + } else { + errorLabel.setVisible(false); + return true; + } + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectCompareView.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectCompareView.java new file mode 100644 index 000000000..7a139ed87 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectCompareView.java @@ -0,0 +1,54 @@ +package org.icepdf.qa.viewer.project; + +import javafx.geometry.Orientation; +import javafx.scene.control.SplitPane; +import javafx.scene.control.TextArea; +import javafx.scene.layout.HBox; +import org.icepdf.qa.config.Project; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.comparitors.ComparatorPane; +import org.icepdf.qa.viewer.comparitors.ComparatorsViewFactory; + +import java.io.PrintStream; + +/** + * Main project compare view. Top contains the comparator and the button shows the console area. + */ +public class ProjectCompareView extends SplitPane { + + private TextArea consoleTextArea; + private PrintStream printStream; + private Mediator mediator; + + public ProjectCompareView(Mediator mediator) { + super(); + this.mediator = mediator; + setOrientation(Orientation.VERTICAL); + + // get the correct view comparator for he project. + mediator.getCurrentProject(); + + ProjectUtilityPane utilityPane = new ProjectUtilityPane(mediator); + mediator.setProjectUtilityPane(utilityPane); + SplitPane.setResizableWithParent(utilityPane, false); + + getItems().addAll(new HBox(), utilityPane); + } + + /** + * Load the project and load the correct comparator view. + * + * @param currentProject + */ + public void setProject(Project currentProject) { + if (currentProject.getCaptureSetA() != null && currentProject.getCaptureSetB() != null && + currentProject.getCaptureSetA().getType() == currentProject.getCaptureSetB().getType()) { + ComparatorPane viewComparator = ComparatorsViewFactory.buildComparatorView( + currentProject.getCaptureSetA().getType(), mediator); + mediator.setComparatorPane(viewComparator); + getItems().remove(0); + getItems().add(0, viewComparator); + } + } + +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectPropertiesTabSet.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectPropertiesTabSet.java new file mode 100644 index 000000000..557f1468f --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectPropertiesTabSet.java @@ -0,0 +1,50 @@ +package org.icepdf.qa.viewer.project; + +import javafx.geometry.Side; +import javafx.scene.control.TabPane; +import org.icepdf.qa.config.Project; +import org.icepdf.qa.config.Result; +import org.icepdf.qa.viewer.common.Mediator; + +import java.util.List; + +/** + * Main tab set for project data view We show project summary data, results and secondary data. + */ +public class ProjectPropertiesTabSet extends TabPane { + + private ProjectTab projectTabSet; + private ResultsTab resultsTabSet; + private MetaDataTab metaDataTabSet; + + public ProjectPropertiesTabSet(Mediator mediator) { + super(); + setSide(Side.TOP); + + projectTabSet = new ProjectTab("Project", mediator); + resultsTabSet = new ResultsTab("Results", mediator); + metaDataTabSet = new MetaDataTab("Metadata", mediator); + + getTabs().addAll(projectTabSet, resultsTabSet, metaDataTabSet); + } + + public void setProject(Project project) { + projectTabSet.setProject(project); + resultsTabSet.setProject(project); +// metaDataTabSet.setProject()project + } + + public void setProjectResults(List results) { + resultsTabSet.setResults(results); + } + + public void clearProjectResults() { + resultsTabSet.clearResults(); + } + + + public void setDisableProjectTab(boolean disable) { + projectTabSet.setDisable(disable); + } + +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTab.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTab.java new file mode 100644 index 000000000..6246f4087 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTab.java @@ -0,0 +1,40 @@ +package org.icepdf.qa.viewer.project; + +import javafx.scene.control.Tab; +import javafx.scene.layout.VBox; +import org.icepdf.qa.config.Project; +import org.icepdf.qa.viewer.common.Mediator; + +/** + * Created by pcorl_000 on 2017-02-07. + */ +public class ProjectTab extends Tab { + + protected Mediator mediator; + + private ProjectTitledPane projectPane; + private CaptureSetPropertyPane captureSetAPane; + private CaptureSetPropertyPane captureSetBPane; + + + public ProjectTab(String title, Mediator mediator) { + super(title); + setClosable(false); + this.mediator = mediator; + + // build out the project sub panes. + projectPane = new ProjectTitledPane("Project"); + captureSetAPane = new CaptureSetPropertyPane("Capture Set A", mediator); + captureSetBPane = new CaptureSetPropertyPane("Project Set B", mediator); + + VBox projectDetails = new VBox(); + projectDetails.getChildren().addAll(projectPane, captureSetAPane, captureSetBPane); + setContent(projectDetails); + } + + public void setProject(Project project) { + projectPane.setProject(project); + captureSetAPane.setProject(project, project.getCaptureSetA()); + captureSetBPane.setProject(project, project.getCaptureSetB()); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTitledPane.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTitledPane.java new file mode 100644 index 000000000..88f651b86 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectTitledPane.java @@ -0,0 +1,71 @@ +package org.icepdf.qa.viewer.project; + +import javafx.geometry.Insets; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.control.TitledPane; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import org.icepdf.qa.config.ConfigSerializer; +import org.icepdf.qa.config.Project; + +/** + * Project name + */ +public class ProjectTitledPane extends TitledPane { + + private Project project; + + private TextField projectNameTextField; + + public ProjectTitledPane(String name) { + super(name, null); + setCollapsible(false); + + GridPane gridPane = new GridPane(); + gridPane.setPadding(new Insets(8, 8, 8, 8)); + gridPane.setHgap(4); + gridPane.setVgap(4); + + Insets labelInsets = new Insets(0, 10, 0, 0); + Insets inputInsets = new Insets(0, 0, 0, 0); + + ColumnConstraints lableColumn = new ColumnConstraints(); + lableColumn.setPrefWidth(75); + ColumnConstraints fieldColumn = new ColumnConstraints(); + fieldColumn.setPercentWidth(75); + gridPane.getColumnConstraints().add(lableColumn); + + // project name. + Label projectNameLabel = new Label("Name:"); + projectNameTextField = new TextField(); + projectNameTextField.focusedProperty().addListener((arg0, oldValue, newValue) -> { + // focus lost. + if (!newValue && projectNameTextField.getText().isEmpty()) { + // check if the file already exists + projectNameTextField.setText("Cannot be null."); + } else if (!newValue) { + // save the state. + project.setName(projectNameTextField.getText().trim()); + ConfigSerializer.save(project); + } + }); + GridPane.setMargin(projectNameLabel, labelInsets); + GridPane.setMargin(projectNameTextField, inputInsets); + GridPane.setConstraints(projectNameLabel, 0, 0); + GridPane.setConstraints(projectNameTextField, 1, 0); + + + gridPane.getChildren().addAll(projectNameLabel, projectNameTextField); + setContent(gridPane); + } + + public void setProject(Project project) { + this.project = project; + if (project != null) { + projectNameTextField.setText(project.getName().trim()); + } else { + projectNameTextField.setText(""); + } + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectUtilityPane.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectUtilityPane.java new file mode 100644 index 000000000..5ac1ceaf1 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ProjectUtilityPane.java @@ -0,0 +1,73 @@ +package org.icepdf.qa.viewer.project; + +import javafx.application.Platform; +import javafx.geometry.Side; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextArea; +import org.icepdf.qa.viewer.common.Mediator; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +/** + * Contains a simple console view that take System.out as a source. The TextArea has proven to be a little slow as + * it get busy. Sor the current work around is to do a little ascii art. + */ +public class ProjectUtilityPane extends TabPane { + + private Tab consoleTab; + + private TextArea consoleTextArea; + private PrintStream printStream; + private int count; + + public ProjectUtilityPane(Mediator mediator) { + super(); + setSide(Side.TOP); + + consoleTab = new Tab("Console"); + consoleTab.setClosable(false); + + consoleTextArea = new TextArea(); + printStream = new PrintStream(new Console(consoleTextArea)); + + System.setOut(printStream); +// System.setErr(printStream); + System.out.println("Console logger initialized"); + + consoleTab.setContent(consoleTextArea); + getTabs().addAll(consoleTab); + } + + public void clearConsole() { + consoleTextArea.clear(); + count = 0; + } + + public class Console extends OutputStream { + private TextArea console; + + public Console(TextArea console) { + this.console = console; + } + + public void appendText(String valueOf) { + Platform.runLater(() -> console.appendText(valueOf)); + } + + public void write(int b) throws IOException { + if (count % 80 == 0) { + appendText(String.valueOf('\n')); + } + appendText(String.valueOf((char) b)); + // line break our ascii art at 80. + if (b == '=' || b == '|' || b == '~') { + count++; + } else { + count = 1; + } + } + } +} \ No newline at end of file diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ResultsTab.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ResultsTab.java new file mode 100644 index 000000000..d2215547f --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/project/ResultsTab.java @@ -0,0 +1,160 @@ +package org.icepdf.qa.viewer.project; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import org.icepdf.qa.config.Project; +import org.icepdf.qa.config.Result; +import org.icepdf.qa.viewer.common.Mediator; +import org.icepdf.qa.viewer.common.Viewer; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by pcorl_000 on 2017-02-07. + */ +public class ResultsTab extends Tab { + + private TextField filterTextField; + + private ContextMenu openFileContextMenu; + private TableView resultsTable; + private ObservableList data; + + public ResultsTab(String title, Mediator mediator) { + super(title); + setClosable(false); + + filterTextField = new TextField("99.99"); + + resultsTable = new TableView<>(); + resultsTable.setEditable(false); + + data = FXCollections.observableArrayList(new ArrayList()); + + TableColumn fileNameColumn = new TableColumn<>("File"); + fileNameColumn.setSortType(TableColumn.SortType.ASCENDING); + fileNameColumn.setCellValueFactory(new PropertyValueFactory("documentFileName")); + + TableColumn captureNameColumn = new TableColumn<>("Capture "); + captureNameColumn.setCellValueFactory(new PropertyValueFactory("fileNameA")); + + TableColumn compareColumn = new TableColumn<>("Compare"); + compareColumn.setCellValueFactory(new PropertyValueFactory<>("difference")); + + resultsTable.getSortOrder().add(fileNameColumn); + resultsTable.getColumns().addAll(fileNameColumn, captureNameColumn, compareColumn); + + openFileContextMenu = new ContextMenu(); + MenuItem openClassPathA = new MenuItem("Open With classpath A"); + openClassPathA.setOnAction(e -> { + Result result = resultsTable.getSelectionModel().getSelectedItem(); + if (result != null) { + try { + Viewer.launchViewer(result, mediator.getCurrentProject().getCaptureSetA()); + } catch (InvocationTargetException e1) { + e1.printStackTrace(); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + + }); + MenuItem openClassPathB = new MenuItem("Open With classpath B"); + openClassPathB.setOnAction(e -> { + Result result = resultsTable.getSelectionModel().getSelectedItem(); + if (result != null) { + try { + Viewer.launchViewer(result, mediator.getCurrentProject().getCaptureSetB()); + } catch (InvocationTargetException e1) { + e1.printStackTrace(); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + }); + openFileContextMenu.getItems().addAll(openClassPathA, openClassPathB); + + // add row listener + resultsTable.setRowFactory(tv -> { + TableRow row = new TableRow<>(); + row.setOnMouseClicked(event -> { + if (event.getClickCount() == 2 && (!row.isEmpty())) { + mediator.openResult(row.getItem()); + } + if (event.getButton() == MouseButton.SECONDARY) { + openFileContextMenu.show(row, event.getScreenX(), event.getScreenY()); + } + }); + return row; + }); + + resultsTable.getSelectionModel().selectedItemProperty().addListener((observable, oldSelection, newSelection) -> { + mediator.openResult(newSelection); + }); + + FilteredList filteredData = new FilteredList<>(data, p -> true); + + filterTextField.textProperty().addListener((observable, oldValue, newValue) -> { + filteredData.setPredicate(result -> { + // If filter text is empty, display all persons. + if (newValue == null || newValue.isEmpty()) { + return true; + } + + if (result.getDifference() <= Double.parseDouble(newValue)) { + return true; + } + return false; // Does not match. + }); + }); + filterTextField.textProperty().addListener((observable, oldValue, newValue) -> { + if (!newValue.matches("\\d+(?:\\.\\d+)?")) { + filterTextField.setText(newValue.replaceAll("[^\\d.]", "")); + } + }); + + filteredData.setPredicate(result -> { + // If filter text is empty, display all persons. + if (result.getDifference() < Double.parseDouble(filterTextField.getText())) { + return true; + } + return false; + }); + + SortedList sortedData = new SortedList<>(filteredData); + sortedData.comparatorProperty().bind(resultsTable.comparatorProperty()); + resultsTable.setItems(sortedData); + + BorderPane borderPane = new BorderPane(); + ToolBar viewTools = new ToolBar(); + viewTools.getItems().addAll(new Label("Filter"), filterTextField); + borderPane.setTop(new VBox(20, viewTools)); + borderPane.setCenter(resultsTable); + this.setContent(borderPane); + } + + public void clearResults() { + data.clear(); + } + + public void setProject(Project project) { + clearResults(); + // load the results set. + if (project.getResults() != null) { + setResults(project.getResults()); + } + } + + public void setResults(List results) { + data.addAll(results); + } +} diff --git a/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/utilities/ImageLoader.java b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/utilities/ImageLoader.java new file mode 100644 index 000000000..e4cd06235 --- /dev/null +++ b/qa/viewer-jfx/src/main/java/org/icepdf/qa/viewer/utilities/ImageLoader.java @@ -0,0 +1,14 @@ +package org.icepdf.qa.viewer.utilities; + +import javafx.scene.image.Image; + +/** + * Aid for loading image from image resources path. + */ +public class ImageLoader { + + public static Image loadImage(String filename) { + return new Image(ImageLoader.class.getResourceAsStream( + "/org/icepdf/qa/viewer/images/" + filename)); + } +} diff --git a/qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/icepdf-app-icon-32x32.png b/qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/icepdf-app-icon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..d745bb2b0aff1748a26b9efb63c64f5234364ed2 GIT binary patch literal 2116 zcmV-K2)p-*P)0000PbVXQnQ*UN; zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU(=1D|BRCwC#8f|PGM|Ixp-ugc5;GECr z5Zm!3)WO(}Y%q4xM#WWIK169F71Mxd2o(}4P^lzWc6xXA-RpC&b*oT-n`G* zgYj)2X6w1h$kun`>zw}ee**stDg@%tk4UgAl!DLSyYcLFiQjQK9nkP+gtf5s9ggC)Jbzl_S6q&L}R!)76ka$#fzjx6@iL;=rlWw_!CU~7<`S(J5A^F zJJh~tsyHq9=l+2o|LAbX&W?TizhYHL5!yJ;9hN=BEyrpFqG@t+(tZyL9pBSaJ3)G$ zyE=f+>qe$S%DES$7q}8RcmD7ThqJZF9)to}kns-2N&H)ZH-og5aRQ?QJdFD0n(BPw zrww9=-`7)nxx5!->zXDHcOPk~=eZKQ(f`V^S1jURnl{lB2mvSfm9EAtFX>TryIr;c z;nj4q38c@N5z`9U0`@X^jw>hM_|rsmA`_#8PC$?Wg6BXXef^G>d>u7{r?n(KKJM|^ zP_OHHA{Kuti+VK4&m|`4M0owgiOjPY12j(zyo~(-l<#uIXQ)2=7I)F3&sJGy6Ehtk zk(TFJkQu)e;0dhn9|8I8w=dmUL4YcGxo#`O;H%(bCJ#$Gim_W`p*$(m1EveQV!E;> zOHxGhAlJHLK*S?8~>h4C3~5k zdGxJqJ&?Ucgy9vYPfKjHVxyO+AD^&}HkOuNA074CuCVM#EKZ!YRa690ec|lFm8_x* z|t#K=2O-=;Yk1#7g;x>xoD0>R3DUBg@y5Yewnyxe495e@C}+4o{b z_R(d8jwNJ7ZWNq=Jp&%E=R9g==*WS4!>xoub(m{(?s%6bfIC(pJ04_N_Lcfx!gTh~ zq%4iPpp{o|cx@A~3L`UMMqx?Dg}oz}+43m$lrtiwt0W+zVN^nBj2LUly;c?dt4Y zR^Gq%4D^?{u6Rl=`rLJm4g2@(nJ-*$tr$3i8fo?jYW23$PHylLP-m0Ts-9?RU8Fft zhQCK6*LWNXiK>N1;`_~P>=a09{yCjyTO!yoD2(*^6DaV4ZGhXTtp)ux9(^~tuj zwk!*Qf=#*bv)f|2;9&sGcG|rfd-(QjrhhIy_S&ysI{WvtPwsni{xqnPY;fSsS3Hne z<5YW6JvfVjYuA5x=+M;El=CQ7v`Dj}JKAceJ0Jsye zIGJI-)oF+(9GJt38lO5-UjXGg)1wP7TAV~;5YT)5A55)boK%@$$dqM9`RV28@W@0o zN(I+qtZ0;mi1bTLBdm8&^)Pr4T%d+%CRb7ly#Bkzog?0$K z|6!*629u=0v^{A~RM!Fx_1}Ev8L!vQlO__0pT78F^jcqlOH>97Go5h1jO0lTi4-AE z{F@j9lZN@~y=9)E>Y$o3BLD7B++SN;E4amVrr0000uVoN%Pr#0l8JSJt-dc%8(Kz4z|x_s!03*7hDV<5}C-pY-jUoq6xg`_A`!Z)WTu z+OlN}7XmAbsKtPBlI3$dDt65>Ms zukcSytegZ$X0{>Ds?7F8)Ju~(I4@&p63U_pWc2=;pC^S?pb0ONiZI3>As#%VT4@ze zX{`7D`gx|I?eoz*)4-ZlI6@Zq!0*G>B9~>B9S(=pvZ|mUYrO#)$NSR(I3p2kpmDej z-X|pljL}D03syj=vJ%6CL-?ew4(r$7YKa2;Bcp0LRkgsCLZLC96Qxlv{XGW_^V@YuS9`KJ?6fGq1UgAW1w)qd00(_&l51z{&RNq#ewU5@x zX|zsEsmGlZ%dEscDjUg~#hk2q`c;1I1^WcvkJC6Ma_M^0{G-rQ*j78KwzoyvnOQj&$A5){|+9!Mgy!5YE(Lpi)uasos?Co?J)v^r|5i z2n6PMfF4GA1qj2ujRy2GS?%Vzct4#|;v#wPw9l8*5@*TE?-Kt&DMUF9CAEKOoO;eh z{By+t^bO$HfBuV95T~EhDL@+cf<0_4I(Rx3QiZref7p$G6APo>y$Xtw8=YOaaRZN% z+m{;vV(j>j3cDI&K31|c<^`E^fQc;fJX!8#8Nm_aF;c)Zo%=q;u*YVHKSx{6@}-d| zcC$rg=2&ILGF7A?xCXM@27dtUAGRZ5K)vIa(xO=)L0=Vd9bOPIbzn>$9Xj><{AdtL z#ySasPijdvai0WjqxDQjdk2bWc9_pDhGHg>q~m%hSzRqHmdN7ziM7Cl)1mV0>ot^U zhlU1CjonfYnCH?o<9**F%a763OBHb_{L1Cwxn4vwLH8$JI35Y&e^mcI%1h-2SGD$h zY=T+Q9}DxTqYyU;4UUYA;GmcbSvs9XYUx%JZqz&yT#TK-g2FWS`xJ zl9Dy50@97IVbrUZs&X_1Ni#XX_*|Nu^^k+&`ZC@9g1Vkh-w%60C3!$5tisq~HhwTV zKE;Xzm}}D^t>(#X%iV7Lg>XZremaXB$<<)G>$@mSc4a}2x{=^w(H@4jRQ{82#B6%M z*oie&pG$f`zVg$e&H_w7RSM?}%&!Q%QR2q_d+$ZQb5vdwU}SAxq=k!25&jPqC8*oH z2^%-oS+0O3GYibVZ-4~9Krw%8wF@;{H{-y81C~#%dB*_^T)f0%G`*GRsQ-b>fgVQz zuGth^qbZN$eOF5hHf?I2Q7;yY;bU4d^FpX*o@pzuIlu@MZfW$L#I`dgad$9+Uq@`{dHWO&A3mHh2Ut0_yehy<9ahBm zVm6$A`y?)2yqK(;F9YRiEns}sNM#2p;X#`NufO`*!gv6dKnTp{trQNEqocTf{rbXq zz+!BOOk!{Y}b`J^QSh0{@OIEta+;ry=!q-8(|IsnkjdfD(oRYceHHFtZwlJ%~rAQyjC_;o183?^0!#V5k7X?eh1FZ5EA6;!3xvC!T(;=KRp99P* zV4PaE$q2d${O+LcHfeiwLg!$n>9?IMeFhQS2^)3_J4&hbn`Ci5_G-_v^12^j_6BE2 z@KV~(3|{ttU!gc81Ib*ngJOP=!eE^G<^J2Wykaeh z`6t2c)U1P)z}DKX4N!^@TI25@s|worg?rKp&&22Xde=K=@RW!@-#dz%?azt`e53^K zJKr$f^e)40a{t5RvTU(bIsx*Cvb8zi?^CZ+J+=Ox_UwS zRZFoVy5Yx=%YpAc@r15EzD@O>d-hp$`23piL7XM;;eLxOhvydK0IvTKC9A)cx#6v< zqNVEyZr@Ogz4z=yBo;+qf4{DNzWMlLXnUs({e8XguB$}x$Os;~GPz*j%rp*Qi(e%! zp;&c~j={~-R^I^55*sCs!&+`n{jl{fX*-IaS6}O2AP+D~Es5keKAB8jR?u6N3?Hzt zzr3#n)@WHTx3^;0ydAF@HKZ+2l8G0&Ri92y9Gs5ZHr`uLLST#jCJ+xGnHt%7hN6{IpZO z`A(n_4}(OsHDZVm0!fO(ctVok1`rcrOc2>EMPOV>NJ=aTW-gwod1gr|pzMqmTd91R zQdbd#YPA}zwnmB9FF;wALylN1!Ym^GkVEw#;`nQ83<&#%XSviO4hR|bK2o77&pe$D z!FKccSz^bZNfb+&5a_M|qoQEjVO&$zJ}uxcZp>-z7l&Ld6tGV!p25zew6+Fjx%;+I zXvn-#wtE&k6nzj8qhe!+=8(#>FHzGn6%^0L;Kpn^oy}+&2|_sqH>PEnilc;-jcKsP zF&8!=Ybc%3S~D6#5N0GfE22yiX>u|P6C!G@VRMd8b;rOpU5n|?U}N9JDw>CNO1vT= zgS86iEE7unGI4>{C4rV6%P~pS|0O)0>1Yx5tbN$bdc+1(`Bkhr^uHhXG}io)oC(Ys zAuNusilw&2DlnYRmOfj|HV<}K$9SwYoTtk#v9|g*hf>Ah$=lKU3+v$GgU5HtKR2Jb z-MwS%)Q++4)ww&D4)% zSdZ6~Mo%}kh%YBj?)l?#wINRS9bK|4op`qECu`&b7_ILfe_r?S+{3>ooXM`X`Ayt< W^#1CFNA3TFzk^(+1D#Fp{_Qi;x_XxY literal 0 HcmV?d00001 diff --git a/qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/run.png b/qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/run.png new file mode 100644 index 0000000000000000000000000000000000000000..b680e33f68bd01656871b098c248d52a47a50d27 GIT binary patch literal 1369 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`Gjk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+nA0*tB1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`kBtHuNWFoz#!AFNG#Ad)HBe}%?0@jth%@)C>7xhtg4GcDhpEegHnt0 zON)|$@sXws(+mtd{1$-}0$pR}Uz7=ql*AmD{N&Qy)VvZ;7h5Huj9yA+ij|9rn~|Y~ zo0F59tFx<_p`oRtp@oyBp`(e5shOLRvmwk3YHGxhOTUB)=#mKR*YS0s=DfOY(~| z@(UE4gUu8)!ZY(y^2>`gLD2*8txIZAW?5>ATTy(978H@y_st3!R#o|djH;2 zz3Km5Yy_MR*f%MtxMt-xwN7gii(>N>5Y#bY4BWVJ!{%UbZQWo^4-YQK+#nCBW(Bc0 zVMC>i((SM3wJIG`j+^8rQ*rlPjq%~zv5kgpeqIjkkL52&DQvVq7UpMmv(`6RmT?C& zV~kC>LHLP9A-2i6>=q*GBK!IEH}&0lYPPM*dd;^V2X3($?C4kQns7(Y;&0uPtwNWU zTTXeuE~1)Yiqmp7jfd=Wh1x3?EbJ>O6y5!tea*j+bX}$%{y;ZJ7sg4)Sud_mc>hbF zyOlR&t8|+50_kJnA3ImfR5?ANHZCMC->`a5%i^Mc?j47@94Z|*|8{efd1k_uGV{BJ zzzwDsA?*jQ8thAOCvaGjuLr`b*vLu6bjCfrrl8Jl+fUzuq*x z@R~E()~w$}#ckHc%*za$Z-u1D%v>nBBKGc*o7D-t53Vga)1cM5K`h>(sG#M~x0M@M z4EFtBV%7CDf=$rW=gl71BY&>_v!An{w?a69LF3?qvRFU%Vo+h{>FVdQ&MBb@03e&$ Axc~qF literal 0 HcmV?d00001 diff --git a/qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/stop.png b/qa/viewer-jfx/src/main/resources/org/icepdf/qa/viewer/images/stop.png new file mode 100644 index 0000000000000000000000000000000000000000..ecaed199cc563449dd37f6102480c24f07e9fe5e GIT binary patch literal 1122 zcmbVLOK8+U7>+Hd6stu&__&M_N<}xxY|%E<+Xd^vN1Dub(>9ryOx?5~ ziqu0rsEFuE5Csn&MDQX=sRyNq4^;HhQ>lU=iamJf!Gn{wTRoHth9vX)zVCm`|4;N3 z4mCFHYG4?qG1sLP>9`~QHm;}t*{ZTYhn+<4BfYpxs%C(gjE(yd$hqbKDk9S!J^cc8 zFid^fDfN-Qe79_2mosAw7r8!VGfYQkAaBlRa9}h z#sbtkRw!9xLzZN-od-cjBvSzw5feo2uouda!Y=B{bRKW>ELen)A%$IXsxRLIR2(3X z=2{b$03i@X4yMwg2oHdy0F%5xUoinwa$1y=ZD9FfskOi^%SFvtwnbM8TOq`kdA?e$ za@AH22Ln8mBq`QNCKD8q2uD3)MhP$6TT{?bXa$Z>9PEKu(d@?~L}96?OCh*^KEERD zh0BSeDdQv4=OHKXt{ca-hz>~+{c&SebXXeo5nn_h9tkXZALYF@GR@uEhGHP~Mm`ca z^is@W4O=5F@<>ioSbD#7tQEk(+LXDgALSlLrs@Zj@-DNIZlPjwMG6@b!7!TJfpr?Wf z4lCFPYOe@djyj%=tKq)*dX}QqP~e?#Eaq2sR{5-t8IR_+7XUZVwJV)c4fg z7~MRT@e+^UPcY358-8(}P2<`2r(K6Gy_s%du3TTYaQNxX%Jb5l-RHYDYm@bJt@`BL l*sYm!w>~`BHs2zyThDy%xjS{g`Q)*<>vMWRdyqYK;WwUTY3Tp} literal 0 HcmV?d00001 diff --git a/settings.gradle b/settings.gradle index a744a6bb6..f9347d09e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,6 @@ include 'core:core-awt', 'viewer:viewer-awt', +// 'qa:viewer-jfx', 'examples:annotation:callback', 'examples:annotation:creation', 'examples:capture:listener',