From 3f125e26b3bccc7c4fb8af49f6ad00d2e70972d8 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Fri, 19 Apr 2024 22:52:02 -0400 Subject: [PATCH 01/22] UI progress --- .../modding/game/file/kaitai/ResMonster.java | 2 +- .../excite/modding/concurrent/Batch.java | 143 ++++++ .../modding/concurrent/BatchContainer.java | 13 + .../modding/concurrent/BatchListener.java | 5 + .../modding/concurrent/BatchRunner.java | 100 ++++ .../modding/concurrent/BatchWorker.java | 22 + .../excite/modding/concurrent/Batcher.java | 22 + .../excite/modding/concurrent/Shutdown.java | 3 + .../modding/concurrent/package-info.java | 1 + .../modding/quicklz/DecodingException.java | 15 + .../excite/modding/quicklz/QuickLZ.java | 20 + .../excite/modding/quicklz/QuicklzDumper.java | 15 +- .../modding/ui/BatchOperationComponent.java | 43 ++ .../modding/ui/BatchedImageComponent.java | 73 +++ .../modding/ui/BatchedImageListener.java | 7 + .../excite/modding/ui/FixedCellGrid.java | 106 +++++ .../excite/modding/ui/FixedCellSizeGrid.java | 101 ++++ .../excite/modding/ui/ImageComponent.java | 33 ++ .../modding/ui/JTextAreaOutputStream.java | 30 ++ .../modding/ui/NonWrappedJEditorPane.java | 17 + .../modding/ui/StripedImageGenerator.java | 57 +++ .../excite/modding/ui/Window.java | 438 ++++++++++++++++++ .../excite/modding/ui/package-info.java | 1 + .../excite/modding/unarchiver/Archive.java | 25 +- .../modding/unarchiver/ArchivedFile.java | 76 ++- .../excite/modding/unarchiver/Unarchiver.java | 3 +- .../excite/modding/{ => util}/FileUtils.java | 6 +- .../modding/util/SplitOutputStream.java | 73 +++ .../excite/modding/util/package-info.java | 1 + src/main/resources/kaitai/monster_res.ksy | 2 +- 30 files changed, 1422 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/Shutdown.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/package-info.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/quicklz/DecodingException.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/JTextAreaOutputStream.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/StripedImageGenerator.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/Window.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/package-info.java rename src/main/java/com/gamebuster19901/excite/modding/{ => util}/FileUtils.java (95%) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/util/SplitOutputStream.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/util/package-info.java diff --git a/build/generated/kaitai/com/gamebuster19901/excite/modding/game/file/kaitai/ResMonster.java b/build/generated/kaitai/com/gamebuster19901/excite/modding/game/file/kaitai/ResMonster.java index 9aac540..32a7636 100644 --- a/build/generated/kaitai/com/gamebuster19901/excite/modding/game/file/kaitai/ResMonster.java +++ b/build/generated/kaitai/com/gamebuster19901/excite/modding/game/file/kaitai/ResMonster.java @@ -155,7 +155,7 @@ public Data(KaitaiStream _io, ResMonster _parent, ResMonster _root) { _read(); } private void _read() { - if (_root().header().compressed() == 128) { + if ( ((_root().header().compressed() == 128) || (_root().header().compressed() == 1152)) ) { this.compressedData = new QuicklzRcmp(this._io); } if (_root().header().compressed() == 0) { diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java new file mode 100644 index 0000000..51f57b9 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -0,0 +1,143 @@ +package com.gamebuster19901.excite.modding.concurrent; + +import java.lang.ref.SoftReference; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.Callable; +import static java.lang.Thread.State; + +public class Batch implements Batcher { + + private final Set runnables = new HashSet<>(); + private final LinkedHashSet listeners = new LinkedHashSet<>(); + private volatile boolean accepting = true; + + + public void addRunnable(Callable runnable) { + if(accepting) { + BatchedCallable b = new BatchedCallable(runnable); + runnables.add(new BatchedCallable(runnable)); + updateListeners(); + } + else { + notAccepting(); + } + } + + public void addRunnable(Runnable runnable) { + if(accepting) { + BatchedCallable b = new BatchedCallable(runnable); + runnables.add(new BatchedCallable(runnable)); + updateListeners(); + } + else { + notAccepting(); + } + } + + public void addListener(BatchListener listener) { + if(accepting) { + if(!listeners.add(listener)) { + System.out.println("Warning: duplicate batch listener ignored."); + } + } + else { + notAccepting(); + } + } + + @Override + public Collection getRunnables() { + return Set.copyOf(runnables); + } + + @Override + public Collection getListeners() { + return (Collection) listeners.clone(); + } + + public void shutdownNow() throws InterruptedException { + accepting = false; + Shutdown shutdown = new Shutdown(); + for(BatchedCallable r : runnables) { + if(r.getState() == State.NEW) { + r.shutdown(shutdown); + } + } + System.err.println(runnables.size()); + } + + public void updateListeners() { + for(BatchListener listener : listeners) { + listener.update(); + } + } + + private void notAccepting() { + throw new IllegalStateException("Batch is not accepting new tasks or listeners."); + } + + public class BatchedCallable implements Callable { + + private final Callable child; + private volatile SoftReference threadRef; + protected volatile Throwable thrown; + protected volatile boolean finished = false; + + public BatchedCallable(Runnable r) { + this(() -> { + r.run(); + return null; + }); + } + + public BatchedCallable(Callable c) { + this.child = c; + updateListeners(); + } + + @Override + public Void call() { + Thread thread; + try { + thread = Thread.currentThread(); + threadRef = new SoftReference<>(thread); + updateListeners(); + child.call(); + } + catch(Throwable t) { + this.thrown = t; + } + finally { + finished = true; + updateListeners(); + } + return null; + } + + public State getState() { + if(finished) { //the thread is no longer working on this runnable + return State.TERMINATED; + } + if(threadRef == null) { + return State.NEW; + } + Thread thread = threadRef.get(); + if(thread == null) { + return State.TERMINATED; //thread has been garbage collected, so it has been terminated. + } + return thread.getState(); + } + + public Throwable getThrown() { + return thrown; + } + + protected void shutdown(Shutdown shutdown) { + finished = true; + thrown = shutdown; + } + } + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java new file mode 100644 index 0000000..3a2ab37 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java @@ -0,0 +1,13 @@ +package com.gamebuster19901.excite.modding.concurrent; + +import java.util.Collection; + +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + +public interface BatchContainer { + + public Collection getRunnables(); + + public Collection getListeners(); + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java new file mode 100644 index 0000000..20df878 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java @@ -0,0 +1,5 @@ +package com.gamebuster19901.excite.modding.concurrent; + +public interface BatchListener { + public void update(); +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java new file mode 100644 index 0000000..3d224df --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -0,0 +1,100 @@ +package com.gamebuster19901.excite.modding.concurrent; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + +public class BatchRunner implements BatchWorker { + + private final ExecutorService executor; + private final LinkedHashSet batches = new LinkedHashSet(); + private volatile boolean started = false; + + public BatchRunner() { + this(Runtime.getRuntime().availableProcessors()); + } + + public BatchRunner(int threads) throws IllegalArgumentException { + this(Executors.newFixedThreadPool(threads)); + } + + public BatchRunner(ExecutorService executor) { + this.executor = executor; + } + + @Override + public void addBatch(Batcher batcher) { + synchronized(executor) { + if(executor.isShutdown()) { + throw new IllegalStateException("Cannot add more batches after the batch runner has shut down!"); + } + synchronized(batches) { + batches.add(batcher); + } + } + } + + @Override + public void startBatch() throws InterruptedException { + synchronized(executor) { + if(executor.isShutdown()) { + throw new IllegalStateException("BatchRunner has already been started!"); + } + synchronized(batches) { + started = true; + executor.invokeAll(getRunnables()); + } + } + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + return executor.awaitTermination(timeout, unit); + } + + @Override + public void shutdownNow() throws InterruptedException { + executor.shutdownNow(); // Force shutdown of any remaining tasks + try { + executor.awaitTermination(5, TimeUnit.SECONDS); + } + finally { + for(Batcher batch : batches) { + batch.shutdownNow(); + } + } + } + + @Override + public Collection getRunnables() { + synchronized(batches) { + LinkedHashSet ret = new LinkedHashSet<>(); + for(Batcher batch : batches) { + ret.addAll(batch.getRunnables()); + } + return ret; + } + } + + @Override + public Collection getListeners() { + synchronized(batches) { + LinkedHashSet ret = new LinkedHashSet<>(); + for(Batcher batch : batches) { + ret.addAll(batch.getListeners()); + } + return ret; + } + } + + public int getCompleted() { + int ret = 0; + Collection callables = getRunnables(); + for(BatchedCallable callable : callables) {ret++;} + return ret; + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java new file mode 100644 index 0000000..cbffafa --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java @@ -0,0 +1,22 @@ +package com.gamebuster19901.excite.modding.concurrent; + +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + +public interface BatchWorker extends BatchContainer { + + public abstract void addBatch(Batcher batcher); + + public abstract void startBatch() throws InterruptedException; + + public abstract boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; + + public void shutdownNow() throws InterruptedException; + + public Collection getRunnables(); + + public Collection getListeners(); + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java new file mode 100644 index 0000000..aaef965 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java @@ -0,0 +1,22 @@ +package com.gamebuster19901.excite.modding.concurrent; + +import java.util.Collection; +import java.util.concurrent.Callable; + +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + +public interface Batcher extends BatchContainer { + + public abstract void addRunnable(Callable runnable); + + public abstract void addRunnable(Runnable runnable); + + public abstract void addListener(BatchListener listener); + + public void shutdownNow() throws InterruptedException; + + public Collection getRunnables(); + + public Collection getListeners(); + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Shutdown.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Shutdown.java new file mode 100644 index 0000000..def151d --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Shutdown.java @@ -0,0 +1,3 @@ +package com.gamebuster19901.excite.modding.concurrent; + +class Shutdown extends Throwable {Shutdown(){}} \ No newline at end of file diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/package-info.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/package-info.java new file mode 100644 index 0000000..4d7194f --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/package-info.java @@ -0,0 +1 @@ +package com.gamebuster19901.excite.modding.concurrent; \ No newline at end of file diff --git a/src/main/java/com/gamebuster19901/excite/modding/quicklz/DecodingException.java b/src/main/java/com/gamebuster19901/excite/modding/quicklz/DecodingException.java new file mode 100644 index 0000000..fdcbee5 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/quicklz/DecodingException.java @@ -0,0 +1,15 @@ +package com.gamebuster19901.excite.modding.quicklz; + +import java.io.IOException; + +public class DecodingException extends IOException { + + public DecodingException(int exitCode) { + super("QuickLZ process failed with exit code (" + exitCode + "): " + QuickLZ.ExitCode.get(exitCode)); + } + + public DecodingException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java b/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java index 0a1ea82..eb79f0f 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java +++ b/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java @@ -19,6 +19,26 @@ public static enum Mode { decompress } + public static enum ExitCode { + SUCCESS, + HELP_SUCCESS, + UNKNOWN_MODE, + MISSING_MODE_ARG, + MISSING_SOURCE_ARG, + MISSING_DEST_ARG, + TOO_MANY_ARGS, + SOURCE_ERR, + DEST_ERR, + UNKNOWN; + + public static ExitCode get(int i) { + if(i < values().length && i >= 0) { + return values()[i]; + } + return UNKNOWN; + } + } + private static final QuickLZImpl impl = QuickLZImpl.get(); public static void compress(Path file, Path dest) { diff --git a/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuicklzDumper.java b/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuicklzDumper.java index eef767c..28adfab 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuicklzDumper.java +++ b/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuicklzDumper.java @@ -5,9 +5,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.concurrent.TimeUnit; -import com.gamebuster19901.excite.modding.FileUtils; +import com.gamebuster19901.excite.modding.quicklz.QuickLZ.ExitCode; +import com.gamebuster19901.excite.modding.util.FileUtils; public class QuicklzDumper { @@ -28,12 +28,17 @@ public byte[] decode(byte[] _raw_data) { try { Files.write(input, _raw_data, StandardOpenOption.CREATE); Process process = QuickLZ.decompress(input, output); - process.waitFor(5000, TimeUnit.SECONDS); + int exitCode = process.waitFor(); + if(exitCode != ExitCode.SUCCESS.ordinal()) { + throw new DecodingException(exitCode); + } return Files.readAllBytes(output); } catch(IOException | InterruptedException e) { - throw new IOError(e); + IOError err = new IOError(e); + err.printStackTrace(); + throw err; } } - + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java new file mode 100644 index 0000000..4d2d545 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java @@ -0,0 +1,43 @@ +package com.gamebuster19901.excite.modding.ui; + +import javax.swing.JPanel; + +import com.gamebuster19901.excite.modding.concurrent.BatchContainer; + +import java.awt.BorderLayout; +import javax.swing.JLabel; +import javax.swing.SwingConstants; + +public class BatchOperationComponent extends JPanel { + + private BatchedImageComponent batch; + private String name; + private JLabel fileName; + + public BatchOperationComponent(BatchContainer batch, String name) { + this(new BatchedImageComponent(batch), name); + } + + /** + @wbp.parser.constructor + **/ + public BatchOperationComponent(BatchedImageComponent batch, String name) { + this.batch = batch; + setLayout(new BorderLayout(0, 0)); + + add(batch, BorderLayout.CENTER); + + fileName = new JLabel(name); + fileName.setHorizontalAlignment(SwingConstants.CENTER); + add(fileName, BorderLayout.SOUTH); + this.setName(name); + } + + @Override + public void setName(String name) { + super.setName(name); + this.setToolTipText(name); + this.fileName.setText(name); + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java new file mode 100644 index 0000000..19f783b --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java @@ -0,0 +1,73 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Color; +import java.awt.Image; +import java.util.Collection; +import java.util.LinkedHashMap; +import com.gamebuster19901.excite.modding.concurrent.BatchListener; +import com.gamebuster19901.excite.modding.concurrent.Batch; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; +import com.gamebuster19901.excite.modding.concurrent.BatchContainer; + +public class BatchedImageComponent extends ImageComponent implements BatchContainer { + + private final BatchContainer batch; + + public BatchedImageComponent() { + this(new Batch()); + } + + public BatchedImageComponent(BatchContainer batch) { + this.batch = batch; + } + + @Override + public Image getImage() { + int _new = 0; + int working = 0; + int success = 0; + int failure = 0; + int other = 0; + + for (BatchedCallable runnable : getRunnables()) { + switch (runnable.getState()) { + case NEW: + _new++; + continue; + case RUNNABLE: + working++; + continue; + case TERMINATED: + if(runnable.getThrown() == null) { + success++; + continue; + } + failure++; + continue; + default: + other++; + } + } + + LinkedHashMap colors = new LinkedHashMap<>(); + colors.put(Color.GREEN, success); + colors.put(Color.RED, failure); + colors.put(Color.WHITE, working); + colors.put(Color.ORANGE, other); + colors.put(Color.GRAY, _new); + + return StripedImageGenerator.generateImage(getWidth(), getHeight(), (LinkedHashMap) colors); + } + + + @Override + public Collection getRunnables() { + return batch.getRunnables(); + } + + @Override + public Collection getListeners() { + return batch.getListeners(); + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java new file mode 100644 index 0000000..53d399d --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java @@ -0,0 +1,7 @@ +package com.gamebuster19901.excite.modding.ui; + +public interface BatchedImageListener { + + public void onBatchedImageUpdate(); + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java new file mode 100644 index 0000000..74e9480 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java @@ -0,0 +1,106 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Component; +import java.util.NavigableSet; +import java.util.TreeMap; + +import javax.swing.JComponent; +import java.awt.*; + +public abstract class FixedCellGrid extends JComponent { + + protected final int padding; + protected final TreeMap components = new TreeMap<>(); + protected final NavigableSet navigator = components.navigableKeySet(); + + public FixedCellGrid(int padding) { + this.padding = padding; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + + components.forEach((index, component) -> { + component.setBounds(calculateCellX(index), calculateCellY(index), getCellWidth(), getCellHeight()); + //System.out.println("Rendering component " + index + " at " + component.getX() + ", " + component.getY()); + }); + + } + + @Override + public void setPreferredSize(Dimension dimension) { + super.setPreferredSize(dimension); + } + + @Override + public void setBounds(int x, int y, int width, int height) { + super.setBounds(x, y, width, height); + this.setPreferredSize(new Dimension(width - x, height - y)); + } + + protected abstract void onGridUpdate(); + + protected abstract int calculateCellX(int index); + + protected abstract int getCellWidth(); + + protected abstract int calculateCellY(int index); + + protected abstract int getCellHeight(); + + public final Component getComponent(Integer index) { + return components.get(index); + } + + public final Component putComponent(Integer index, Component component) { + super.add(component); + System.out.println("Adding component " + component + " at index " + index); + return components.put(index, component); + } + + public final int getLowestFreeCell() { + Integer prevIndex = 0; + Integer index = -1; + while(index != null) { + index = navigator.higher(index); + if(index == null) { + return components.size(); + } + if(index - prevIndex > 1) { //we've found a gap in the defined cells + return prevIndex + 1; + } + prevIndex = index; + } + + return components.size(); + } + + public final Component getFirstComponent() { + return components.firstEntry().getValue(); + } + + public final Component getLastComponent() { + return components.lastEntry().getValue(); + } + + public final TreeMap getComponentMap() { + return components; + } + + @Override + @Deprecated + public Component add(Component component) { + //super.add(component); + System.out.println("Adding component at index " + getLowestFreeCell()); + putComponent(getLowestFreeCell(), component); + return component; + } + + @Override + @Deprecated + public void add(Component component, Object constraints) { + add(component); // Ignore constraints, components are positioned based on the cell location and size + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java new file mode 100644 index 0000000..1c35c5a --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java @@ -0,0 +1,101 @@ +package com.gamebuster19901.excite.modding.ui; +import java.awt.*; + +public class FixedCellSizeGrid extends FixedCellGrid { + + private int cellWidth; + private int cellHeight; + private int scroll; + + public FixedCellSizeGrid(Dimension gridDimension, Dimension cellDimension) { + this(gridDimension, cellDimension, 0); + } + + public FixedCellSizeGrid(Dimension gridDimension, Dimension cellDimension, int padding) { + super(padding); + this.cellWidth = cellDimension.width; + this.cellHeight = cellDimension.height; + this.setPreferredSize(gridDimension); + try { + calculateCellX(1); + } + catch(ArithmeticException e) { + throw new IllegalArgumentException("Grid size is too small, it cannot hold any cells!", e); + } + } + + @Override + public void setPreferredSize(Dimension dimension) { + super.setPreferredSize(dimension); + onGridUpdate(); + } + + @Override + protected void onGridUpdate() { + components.forEach((index, component) -> { + component.setBounds(calculateCellX(index), calculateCellY(index), getCellWidth(), getCellHeight()); + }); + System.out.println(this.getPreferredSize()); + System.out.println(cellWidth); + invalidate(); + } + + protected void setCellSize(Dimension dimension) { + this.cellWidth = dimension.width; + this.cellHeight = dimension.height; + onGridUpdate(); + } + + @Override + protected int getCellWidth() { + return cellWidth; + } + + @Override + protected int getCellHeight() { + return cellHeight; + } + + @Override + protected int calculateCellX(int index) { + return ((((index % (this.getPreferredSize().width / (cellWidth + padding))) * (cellWidth + padding))) + (padding / 2)); + } + + private int calculateCellYRaw(int index) { + int rowIndex = index / (this.getPreferredSize().width / (cellWidth + padding)); + return (rowIndex * (cellHeight + padding)) / (cellHeight + padding); + } + + private int getVisibleRows() { + return this.getPreferredSize().height / (cellHeight + padding); + } + + private int getMaxScroll() { + return Math.max(0, calculateCellYRaw(components.size()) - getVisibleRows()); + } + + protected int calculateCellY(int index) { + int rowIndex = index / (this.getPreferredSize().width / (cellWidth + padding)); + return (rowIndex * (cellHeight + padding)) - (scroll * (cellHeight + padding)); + } + + public int getScroll() { + return scroll; + } + + public void scroll(int offset) { + int newScroll = scroll + offset; + System.out.println("Max scroll: " + getMaxScroll()); + if(newScroll < 0) { + scroll = 0; + } + else if(newScroll > getMaxScroll()) { + scroll = getMaxScroll(); + } + else { + scroll = newScroll; + } + onGridUpdate(); + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java new file mode 100644 index 0000000..2c79444 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java @@ -0,0 +1,33 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Graphics; +import java.awt.Image; + +import javax.swing.JComponent; + +public class ImageComponent extends JComponent { + + Image image = null; + + public ImageComponent() { + this(null); + } + + public ImageComponent(Image image) { + this.image = image; + } + + public Image getImage() { + return image; + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Image image = getImage(); + if(image != null) { + g.drawImage(image, 0, 0, this); + } + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/JTextAreaOutputStream.java b/src/main/java/com/gamebuster19901/excite/modding/ui/JTextAreaOutputStream.java new file mode 100644 index 0000000..838e05b --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/JTextAreaOutputStream.java @@ -0,0 +1,30 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.io.IOException; +import java.io.OutputStream; + +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; + +public class JTextAreaOutputStream extends OutputStream { + + private final JTextArea dest; + + public JTextAreaOutputStream(JTextArea dest) { + this.dest = dest; + } + + @Override + public void write(int b) throws IOException { + write(new byte[]{(byte)b}, 0, 1); + } + + @Override + public void write(byte[] buf, int off, int len) throws IOException { + String text = new String(buf, off, len); + SwingUtilities.invokeLater(() -> { + dest.append(text); + }); + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java b/src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java new file mode 100644 index 0000000..9d3b71c --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java @@ -0,0 +1,17 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Component; +import javax.swing.JEditorPane; +import javax.swing.plaf.ComponentUI; + +public class NonWrappedJEditorPane extends JEditorPane { + + @Override + public boolean getScrollableTracksViewportWidth() { + Component parent = getParent(); + ComponentUI ui = getUI(); + + return true; + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/StripedImageGenerator.java b/src/main/java/com/gamebuster19901/excite/modding/ui/StripedImageGenerator.java new file mode 100644 index 0000000..95f9465 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/StripedImageGenerator.java @@ -0,0 +1,57 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.util.LinkedHashMap; +import java.util.Map; + +public class StripedImageGenerator { + + public static BufferedImage generateImage(int width, int height, LinkedHashMap colorWeights) { + // Check for valid input + if (width <= 0 || height <= 0 || colorWeights == null || colorWeights.isEmpty()) { + throw new IllegalArgumentException("Invalid input parameters"); + } + + int totalWeight = 0; + for (int weight : colorWeights.values()) { + if (weight < 0) { + throw new IllegalArgumentException("Color weights must be 0 or a positive integer"); + } + totalWeight += weight; + } + + if (totalWeight == 0) { + totalWeight = height; + colorWeights = new LinkedHashMap(); + colorWeights.put(Color.GRAY, totalWeight); + } + double scaleFactor = (double) height / totalWeight; + + LinkedHashMap colorHeights = new LinkedHashMap<>(); + for (Map.Entry entry : colorWeights.reversed().entrySet()) { + Color color = entry.getKey(); + int weight = entry.getValue(); + int rowHeight = (int) Math.ceil(weight * scaleFactor); + colorHeights.put(color, rowHeight); + } + + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + + Graphics2D graphics = image.createGraphics(); + + int currentY = 0; + for (Map.Entry entry : colorHeights.entrySet()) { + Color color = entry.getKey(); + int rowHeight = entry.getValue(); + graphics.setColor(color); + graphics.fillRect(0, currentY, width, rowHeight); + currentY += rowHeight; + } + + graphics.dispose(); + + return image; + } +} \ No newline at end of file diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java new file mode 100644 index 0000000..9d34ba5 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -0,0 +1,438 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Insets; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; +import java.io.PrintStream; + +import javax.swing.JFrame; +import com.gamebuster19901.excite.modding.concurrent.BatchListener; +import com.gamebuster19901.excite.modding.concurrent.BatchRunner; +import com.gamebuster19901.excite.modding.util.SplitOutputStream; +import com.gamebuster19901.excite.modding.concurrent.Batch; + +import javax.swing.JTextField; +import javax.swing.UIManager; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JSeparator; +import javax.swing.JTabbedPane; +import javax.swing.JTextArea; +import javax.swing.JSlider; +import javax.swing.JProgressBar; +import javax.swing.SwingConstants; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import javax.swing.JList; +import javax.swing.BoxLayout; +import com.jgoodies.forms.layout.FormLayout; +import com.jgoodies.forms.layout.ColumnSpec; +import com.jgoodies.forms.layout.RowSpec; +import java.awt.GridLayout; +import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.FlowLayout; +import javax.swing.JTable; + +public class Window implements BatchListener, MouseWheelListener { + + static { + UIManager.getDefaults().put("TabbedPane.contentBorderInsets", new Insets(0,0,0,0)); + } + + private JFrame frame; + private JTextField textField; + private JTextField textField_1; + private JSlider threadSlider; + private JLabel lblThreads; + private static Window window; + private final JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP); + private final JProgressBar progressBar = new JProgressBar(); + private JTable table; + + /** + * Launch the application. + * @throws InterruptedException + */ + public static void main(String[] args) throws InterruptedException { + + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + Window window = new Window(); + Window.window = window; + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + /** + * Create the application. + * @throws InterruptedException + */ + public Window() throws InterruptedException { + initialize(); + update(); + } + + /** + * Initialize the contents of the frame. + * @throws InterruptedException + */ + private void initialize() throws InterruptedException { + frame = new JFrame(); + frame.setTitle("ExciteModder"); + frame.setBounds(100, 100, 1000, 680); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.getContentPane().setLayout(null); + //frame.setResizable(false); + frame.setVisible(true); + + JLabel lblUnmoddedDirectory = new JLabel("Source Directory:"); + lblUnmoddedDirectory.setToolTipText("The directory of the unmodified, original ripped files."); + lblUnmoddedDirectory.setBounds(12, 12, 165, 15); + frame.getContentPane().add(lblUnmoddedDirectory); + + JLabel lblModdedDirectory = new JLabel("Destination Directory:"); + lblModdedDirectory.setToolTipText("Where ExciteModder will copy modified game files to."); + lblModdedDirectory.setBounds(12, 39, 165, 15); + frame.getContentPane().add(lblModdedDirectory); + + textField = new JTextField(); + textField.setBounds(171, 37, 578, 19); + frame.getContentPane().add(textField); + textField.setColumns(10); + + textField_1 = new JTextField(); + textField_1.setColumns(10); + textField_1.setBounds(171, 10, 578, 19); + frame.getContentPane().add(textField_1); + + JButton btnChangeSource = new JButton("Change"); + btnChangeSource.setToolTipText("Change where ExciteModder will copy the game files from"); + btnChangeSource.setBounds(761, 9, 117, 19); + frame.getContentPane().add(btnChangeSource); + + JButton btnChangeDest = new JButton("Change"); + btnChangeDest.setToolTipText("Change where ExciteModder will copy the game files to"); + btnChangeDest.setBounds(761, 36, 117, 19); + frame.getContentPane().add(btnChangeDest); + tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + + tabbedPane.setBounds(0, 129, 1000, 511); + frame.getContentPane().add(tabbedPane); + + + + + FixedCellGrid gridPanel = new FixedCellSizeGrid(new Dimension(385, 385), new Dimension(100, 100), 0); + + gridPanel.setVisible(true); + gridPanel.components.remove(-1); + + JTextArea textArea = new JTextArea(); + textArea.setBorder(BorderFactory.createLoweredBevelBorder()); + textArea.setOpaque(true); + JTextAreaOutputStream textPaneOutputStream = new JTextAreaOutputStream(textArea); + System.setOut(new PrintStream(SplitOutputStream.splitSysOut(textPaneOutputStream))); + System.setErr(new PrintStream(SplitOutputStream.splitErrOut(textPaneOutputStream))); + + JScrollPane scrollPane = new JScrollPane(textArea); + scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + tabbedPane.addTab("Console Output", null, scrollPane, null); + tabbedPane.addTab("Status", null, gridPanel, null); + + JPanel panel = new JPanel(); + tabbedPane.addTab("Progress", null, panel, null); + panel.setLayout(new BorderLayout(0, 0)); + + //gridPanel.setPreferredSize(new Dimension(814, 406)); + + BatchRunner batchRunner = new BatchRunner(); + for(int i = 0; i < 10; i++) { + Batch b = new Batch(); + batchRunner.addBatch(b); + BatchOperationComponent c = new BatchOperationComponent(b, "File " + i); + gridPanel.putComponent(i, c); + if(i == 5) { + c.setName("A very long file name"); + } + tabbedPane.addTab(c.getName(), null); + } + + JPanel panel_1 = new JPanel(); + panel.add(panel_1, BorderLayout.CENTER); + GridBagLayout gbl_panel_1 = new GridBagLayout(); + gbl_panel_1.columnWidths = new int[]{0, 0, 70, 90, 0}; + gbl_panel_1.rowHeights = new int[]{0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_panel_1.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + panel_1.setLayout(gbl_panel_1); + + JLabel lblCopyOperation = new JLabel("Copy Operation"); + GridBagConstraints gbc_lblCopyOperation = new GridBagConstraints(); + gbc_lblCopyOperation.gridwidth = 4; + gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 5); + gbc_lblCopyOperation.gridx = 0; + gbc_lblCopyOperation.gridy = 0; + panel_1.add(lblCopyOperation, gbc_lblCopyOperation); + BatchOperationComponent allBatches = new BatchOperationComponent(batchRunner, " All Batches "); + GridBagConstraints gbc_allBatches = new GridBagConstraints(); + gbc_allBatches.fill = GridBagConstraints.VERTICAL; + gbc_allBatches.gridheight = 9; + gbc_allBatches.insets = new Insets(0, 0, 5, 5); + gbc_allBatches.gridx = 0; + gbc_allBatches.gridy = 1; + panel_1.add(allBatches, gbc_allBatches); + + JSeparator separator_1 = new JSeparator(); + GridBagConstraints gbc_separator_1 = new GridBagConstraints(); + gbc_separator_1.fill = GridBagConstraints.BOTH; + gbc_separator_1.gridheight = 10; + gbc_separator_1.insets = new Insets(0, 0, 5, 5); + gbc_separator_1.gridx = 1; + gbc_separator_1.gridy = 0; + panel_1.add(separator_1, gbc_separator_1); + + JLabel lblTotalArchives = new JLabel("Total Archives:"); + lblTotalArchives.setHorizontalAlignment(SwingConstants.CENTER); + GridBagConstraints gbc_lblTotalArchives = new GridBagConstraints(); + gbc_lblTotalArchives.insets = new Insets(0, 0, 5, 5); + gbc_lblTotalArchives.anchor = GridBagConstraints.NORTHEAST; + gbc_lblTotalArchives.gridx = 2; + gbc_lblTotalArchives.gridy = 1; + panel_1.add(lblTotalArchives, gbc_lblTotalArchives); + + JLabel lblTotalArchivesCount = new JLabel("0"); + GridBagConstraints gbc_lblTotalArchivesCount = new GridBagConstraints(); + gbc_lblTotalArchivesCount.anchor = GridBagConstraints.WEST; + gbc_lblTotalArchivesCount.insets = new Insets(0, 0, 5, 0); + gbc_lblTotalArchivesCount.gridx = 3; + gbc_lblTotalArchivesCount.gridy = 1; + panel_1.add(lblTotalArchivesCount, gbc_lblTotalArchivesCount); + + JLabel lblTotalResources = new JLabel("Total Resources:"); + lblTotalResources.setHorizontalAlignment(SwingConstants.RIGHT); + GridBagConstraints gbc_lblTotalResources = new GridBagConstraints(); + gbc_lblTotalResources.anchor = GridBagConstraints.EAST; + gbc_lblTotalResources.insets = new Insets(0, 0, 5, 5); + gbc_lblTotalResources.gridx = 2; + gbc_lblTotalResources.gridy = 2; + panel_1.add(lblTotalResources, gbc_lblTotalResources); + + JLabel lblTotalResourcesCount = new JLabel("0"); + GridBagConstraints gbc_lblTotalResourcesCount = new GridBagConstraints(); + gbc_lblTotalResourcesCount.anchor = GridBagConstraints.WEST; + gbc_lblTotalResourcesCount.insets = new Insets(0, 0, 5, 0); + gbc_lblTotalResourcesCount.gridx = 3; + gbc_lblTotalResourcesCount.gridy = 2; + panel_1.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); + + JLabel lblArchivesCopied = new JLabel("Archives Copied:"); + GridBagConstraints gbc_lblArchivesCopied = new GridBagConstraints(); + gbc_lblArchivesCopied.anchor = GridBagConstraints.EAST; + gbc_lblArchivesCopied.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesCopied.gridx = 2; + gbc_lblArchivesCopied.gridy = 4; + panel_1.add(lblArchivesCopied, gbc_lblArchivesCopied); + + JLabel label_1 = new JLabel("0 (0%)"); + GridBagConstraints gbc_label_1 = new GridBagConstraints(); + gbc_label_1.anchor = GridBagConstraints.WEST; + gbc_label_1.insets = new Insets(0, 0, 5, 0); + gbc_label_1.gridx = 3; + gbc_label_1.gridy = 4; + panel_1.add(label_1, gbc_label_1); + + JLabel lblResourcesCopied = new JLabel("Resources Copied:"); + GridBagConstraints gbc_lblResourcesCopied = new GridBagConstraints(); + gbc_lblResourcesCopied.anchor = GridBagConstraints.EAST; + gbc_lblResourcesCopied.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesCopied.gridx = 2; + gbc_lblResourcesCopied.gridy = 5; + panel_1.add(lblResourcesCopied, gbc_lblResourcesCopied); + + JLabel label_2 = new JLabel("0 (0%)"); + GridBagConstraints gbc_label_2 = new GridBagConstraints(); + gbc_label_2.anchor = GridBagConstraints.WEST; + gbc_label_2.insets = new Insets(0, 0, 5, 0); + gbc_label_2.gridx = 3; + gbc_label_2.gridy = 5; + panel_1.add(label_2, gbc_label_2); + + JLabel lblArchivesProcessed = new JLabel("Archives Processed:"); + lblArchivesProcessed.setHorizontalAlignment(SwingConstants.RIGHT); + GridBagConstraints gbc_lblArchivesProcessed = new GridBagConstraints(); + gbc_lblArchivesProcessed.anchor = GridBagConstraints.EAST; + gbc_lblArchivesProcessed.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesProcessed.gridx = 2; + gbc_lblArchivesProcessed.gridy = 7; + panel_1.add(lblArchivesProcessed, gbc_lblArchivesProcessed); + + JLabel lblArchivesprocessed = new JLabel("0 (0%)"); + GridBagConstraints gbc_lblArchivesprocessed = new GridBagConstraints(); + gbc_lblArchivesprocessed.insets = new Insets(0, 0, 5, 0); + gbc_lblArchivesprocessed.anchor = GridBagConstraints.WEST; + gbc_lblArchivesprocessed.gridx = 3; + gbc_lblArchivesprocessed.gridy = 7; + panel_1.add(lblArchivesprocessed, gbc_lblArchivesprocessed); + + JLabel lblArchivesProcessed_1 = new JLabel("Archives Skipped:"); + lblArchivesProcessed_1.setHorizontalAlignment(SwingConstants.RIGHT); + GridBagConstraints gbc_lblArchivesProcessed_1 = new GridBagConstraints(); + gbc_lblArchivesProcessed_1.anchor = GridBagConstraints.EAST; + gbc_lblArchivesProcessed_1.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesProcessed_1.gridx = 2; + gbc_lblArchivesProcessed_1.gridy = 8; + panel_1.add(lblArchivesProcessed_1, gbc_lblArchivesProcessed_1); + + JLabel label_3 = new JLabel("0 (0%)"); + GridBagConstraints gbc_label_3 = new GridBagConstraints(); + gbc_label_3.anchor = GridBagConstraints.WEST; + gbc_label_3.insets = new Insets(0, 0, 5, 0); + gbc_label_3.gridx = 3; + gbc_label_3.gridy = 8; + panel_1.add(label_3, gbc_label_3); + + JLabel lblResourcesProcessed = new JLabel("Resources Processed:"); + GridBagConstraints gbc_lblResourcesProcessed = new GridBagConstraints(); + gbc_lblResourcesProcessed.anchor = GridBagConstraints.EAST; + gbc_lblResourcesProcessed.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesProcessed.gridx = 2; + gbc_lblResourcesProcessed.gridy = 9; + panel_1.add(lblResourcesProcessed, gbc_lblResourcesProcessed); + + JLabel label = new JLabel("100 (100%)"); + GridBagConstraints gbc_label = new GridBagConstraints(); + gbc_label.insets = new Insets(0, 0, 5, 0); + gbc_label.anchor = GridBagConstraints.WEST; + gbc_label.gridx = 3; + gbc_label.gridy = 9; + panel_1.add(label, gbc_label); + + JLabel lblResourcesSkipped = new JLabel("Resources Skipped"); + GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); + gbc_lblResourcesSkipped.anchor = GridBagConstraints.EAST; + gbc_lblResourcesSkipped.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesSkipped.gridx = 2; + gbc_lblResourcesSkipped.gridy = 10; + panel_1.add(lblResourcesSkipped, gbc_lblResourcesSkipped); + + JLabel label_4 = new JLabel("0 (0%)"); + GridBagConstraints gbc_label_4 = new GridBagConstraints(); + gbc_label_4.anchor = GridBagConstraints.WEST; + gbc_label_4.gridx = 3; + gbc_label_4.gridy = 10; + panel_1.add(label_4, gbc_label_4); + + JPanel panel_2 = new JPanel(); + panel.add(panel_2, BorderLayout.EAST); + GridBagLayout gbl_panel_2 = new GridBagLayout(); + gbl_panel_2.columnWidths = new int[]{0, 0}; + gbl_panel_2.rowHeights = new int[]{0, 0}; + gbl_panel_2.columnWeights = new double[]{0.0, Double.MIN_VALUE}; + gbl_panel_2.rowWeights = new double[]{0.0, Double.MIN_VALUE}; + panel_2.setLayout(gbl_panel_2); + + JLabel lblNewLabel = new JLabel("New label"); + GridBagConstraints gbc_lblNewLabel = new GridBagConstraints(); + gbc_lblNewLabel.gridx = 0; + gbc_lblNewLabel.gridy = 0; + panel_2.add(lblNewLabel, gbc_lblNewLabel); + + threadSlider = new JSlider(); + threadSlider.getSnapToTicks(); + threadSlider.setMinimum(1); + threadSlider.setMaximum(Runtime.getRuntime().availableProcessors()); + threadSlider.setValue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + threadSlider.setBounds(5, 90, 92, 16); + threadSlider.addChangeListener((s) -> { + if(lblThreads != null) { + lblThreads.setText("Threads: " + threadSlider.getValue()); + } + }); + frame.getContentPane().add(threadSlider); + + lblThreads = new JLabel("Threads: " + threadSlider.getValue()); + lblThreads.setBounds(12, 74, 132, 15); + frame.getContentPane().add(lblThreads); + + JSeparator separator = new JSeparator(); + separator.setBounds(12, 64, 971, 2); + frame.getContentPane().add(separator); + + progressBar.setVisible(false); + + JLabel lblStatus = new JLabel("Progress: 0%"); + lblStatus.setHorizontalAlignment(SwingConstants.CENTER); + lblStatus.setBounds(440, 110, 120, 15); + lblStatus.setVisible(false); + + frame.getContentPane().add(lblStatus); + progressBar.setBounds(12, 110, 971, 15); + frame.getContentPane().add(progressBar); + + JButton btnExtract = new JButton("Extract!"); + btnExtract.setBounds(890, 9, 93, 45); + btnExtract.setEnabled(false); + + frame.getContentPane().add(btnExtract); + frame.validate(); + frame.repaint(); + tabbedPane.addMouseWheelListener(this); + + } + + @Override + public void update() { + frame.repaint(); + } + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + if(e.getSource() instanceof JTabbedPane) { + JTabbedPane pane = (JTabbedPane) e.getSource(); + Component scrolledComponent = pane.getComponentAt(e.getPoint()); + if(!(scrolledComponent instanceof FixedCellSizeGrid) && scrolledComponent.getParent() instanceof JTabbedPane) { + int units = e.getWheelRotation(); + System.out.println(units); + int oldIndex = pane.getSelectedIndex(); + int newIndex = oldIndex + units; + if(newIndex < 0) { + pane.setSelectedIndex(0); + } + else if (newIndex >= pane.getTabCount()) { + pane.setSelectedIndex(pane.getTabCount() - 1); + } + else { + pane.setSelectedIndex(newIndex); + } + } + else if(scrolledComponent instanceof FixedCellSizeGrid) { + FixedCellSizeGrid grid = (FixedCellSizeGrid) scrolledComponent; + grid.scroll(e.getWheelRotation()); + } + } + else { + //System.out.println(e.getSource()); + } + } + + private int getWantedThreads() { + int ret = threadSlider.getValue(); + if(ret < 1) { + return 1; + } + return ret; + } +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/package-info.java b/src/main/java/com/gamebuster19901/excite/modding/ui/package-info.java new file mode 100644 index 0000000..b7d428a --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/package-info.java @@ -0,0 +1 @@ +package com.gamebuster19901.excite.modding.ui; \ No newline at end of file diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Archive.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Archive.java index 4af6f2e..0260593 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Archive.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Archive.java @@ -18,7 +18,7 @@ public class Archive { private final Path archiveFile; private final ResMonster archive; private final Toc toc; - private byte[] bytes; + private final byte[] bytes; private final LinkedHashMap files = new LinkedHashMap<>(); public Archive(Path archivePath, Path tocPath) throws IOException { @@ -29,6 +29,18 @@ public Archive(Path archivePath, Toc toc) throws IOException { this.archiveFile = archivePath; this.archive = ResMonster.fromFile(archivePath.toAbsolutePath().toString()); this.toc = toc; + if(isCompressed()) { + bytes = archive.data().compressedData().bytes(); + } + else { + bytes = archive.data().uncompressedData(); + } + if(bytes == null) { + throw new AssertionError(); + } + if(bytes.length == 0) { + throw new AssertionError(); + } for(TocMonster.Details fileDetails : getFileDetails()) { try { files.put(fileDetails.name(), new ArchivedFile(fileDetails, this)); @@ -37,6 +49,7 @@ public Archive(Path archivePath, Toc toc) throws IOException { //swallo } } + } public Toc getToc() { @@ -80,7 +93,7 @@ public long getHash() { } public boolean isCompressed() { - if (archive.header().compressed() == 128) { + if (((archive.header().compressed() >>> 7) & 1) != 0) { return true; } else if (archive.header().compressed() == 0) { @@ -92,14 +105,6 @@ else if (archive.header().compressed() == 0) { } public byte[] getBytes() { - if(bytes == null) { - if(isCompressed()) { - bytes = archive.data().compressedData().bytes(); - } - else { - bytes = archive.data().uncompressedData(); - } - } return Arrays.copyOf(bytes, bytes.length); } diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java index b956de5..aa0d6eb 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java @@ -4,7 +4,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.Arrays; import com.gamebuster19901.excite.modding.game.file.kaitai.TocMonster; @@ -18,22 +17,17 @@ public ArchivedFile(TocMonster.Details fileDetails, Archive archive) { try { this.fileDetails = fileDetails; this.archive = archive; + System.out.println("Archive size: " + archive.getUncompressedSize()); System.out.println("File offset: " + fileDetails.fileOffset()); System.out.println("File size: " + fileDetails.fileSize()); System.out.println("File end: " + ((int)fileDetails.fileOffset() + (int)fileDetails.fileSize())); - System.out.println("Thread: " + Thread.currentThread().getName()); - System.out.println(archive.getBytes().length); - this.bytes = Arrays.copyOfRange(archive.getBytes(), (int)fileDetails.fileOffset(), (int)(fileDetails.fileOffset() + (int)fileDetails.fileSize())); + byte[] bytes = archive.getBytes(); + System.out.println("Array size: " + bytes.length); + this.bytes = copyOfRange(archive.getBytes(), (int)fileDetails.fileOffset(), (int)(fileDetails.fileOffset() + (int)fileDetails.fileSize())); } catch(Throwable t) { System.err.println("Could not extract resource " + getName() + " from " + archive.getArchiveFile().getFileName()); - java.util.Collection a1 = java.lang.Thread.getAllStackTraces().values(); - for (java.lang.StackTraceElement[] a2 : a1){ - System.out.println("========== "); - for (java.lang.StackTraceElement a3 : a2){ - System.out.println(a3.toString()); - } - } + t.printStackTrace(); throw t; } } @@ -54,4 +48,64 @@ public void writeTo(Path directory) throws IOException { Files.write(f, getBytes(), StandardOpenOption.CREATE); } + /** + * Copies the specified range of the specified array into a new array. + * The initial index of the range ({@code from}) must lie between zero + * and {@code original.length}, inclusive. The value at + * {@code original[from]} is placed into the initial element of the copy + * (unless {@code from == original.length} or {@code from == to}). + * Values from subsequent elements in the original array are placed into + * subsequent elements in the copy. The final index of the range + * ({@code to}), which must be greater than or equal to {@code from}, + * may be greater than {@code original.length}, in which case + * {@code (byte)0} is placed in all elements of the copy whose index is + * greater than or equal to {@code original.length - from}. The length + * of the returned array will be {@code to - from}. + * + * @param original the array from which a range is to be copied + * @param from the initial index of the range to be copied, inclusive + * @param to the final index of the range to be copied, exclusive. + * (This index may lie outside the array.) + * @return a new array containing the specified range from the original array, + * truncated or padded with zeros to obtain the required length + * @throws ArrayIndexOutOfBoundsException if {@code from < 0} + * or {@code from > original.length} + * @throws IllegalArgumentException if {@code from > to} + * @throws NullPointerException if {@code original} is null + * @since 1.6 + */ + public static byte[] copyOfRange(byte[] original, int from, int to) { + // Tickle the JIT to fold special cases optimally + if (from != 0 || to != original.length) + return copyOfRangeByte(original, from, to); + else // from == 0 && to == original.length + return original.clone(); + } + + private static byte[] copyOfRangeByte(byte[] original, int from, int to) { + checkLength(from, to); + int newLength = to - from; + int calcedLength = original.length - from; + System.out.println("Original Length: " + original.length); + System.out.println("From: " + from); + System.out.println("To: " + to); + byte[] copy = new byte[newLength]; + try { + System.arraycopy(original, from, copy, 0, + Math.min(original.length - from, newLength)); + } + catch(ArrayIndexOutOfBoundsException e) { + e.printStackTrace(); + System.arraycopy(original, from, copy, 0, + Math.min(original.length - from, newLength)); + } + return copy; + } + + private static void checkLength(int from, int to) { + if (to < from) { + throw new IllegalArgumentException(from + " > " + to); + } + } + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java index 88d5ad7..c4db3ac 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java @@ -15,6 +15,7 @@ public class Unarchiver { private static final Path runDir = Path.of(".").resolve("run"); + private static final Path ripDir = runDir.resolve("rip"); public LinkedHashSet tocs = new LinkedHashSet<>(); public LinkedHashSet archives = new LinkedHashSet<>(); @@ -49,7 +50,7 @@ public void unarchive(Path tocFile) throws IOException { throw new FileNotFoundException("Resource file for toc " + tocFile); } - archive.writeTo(runDir); + archive.writeTo(ripDir); } @SuppressWarnings("resource") diff --git a/src/main/java/com/gamebuster19901/excite/modding/FileUtils.java b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java similarity index 95% rename from src/main/java/com/gamebuster19901/excite/modding/FileUtils.java rename to src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java index 2b20c6b..3923d4f 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/FileUtils.java +++ b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java @@ -1,4 +1,4 @@ -package com.gamebuster19901.excite.modding; +package com.gamebuster19901.excite.modding.util; import java.io.File; import java.io.IOError; @@ -65,7 +65,9 @@ public static String readNullTerminatedString(ByteBuffer buffer, Charset charset } public static Path createTempFile() throws IOException { - return Files.createTempFile(TEMP, null, null); + Path f = Files.createTempFile(TEMP, null, null); + System.out.println("Created temporary file " + f); + return f; } public static Path createTempFile(String name) throws IOException { diff --git a/src/main/java/com/gamebuster19901/excite/modding/util/SplitOutputStream.java b/src/main/java/com/gamebuster19901/excite/modding/util/SplitOutputStream.java new file mode 100644 index 0000000..97b322c --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/util/SplitOutputStream.java @@ -0,0 +1,73 @@ +package com.gamebuster19901.excite.modding.util; + +import java.io.IOException; +import java.io.OutputStream; + +public class SplitOutputStream extends OutputStream { + + private OutputStream[] outputs; + + public SplitOutputStream(OutputStream... consumers) { + this.outputs = consumers; + } + + public static SplitOutputStream splitSysOut(OutputStream... consumers) { + OutputStream original = System.out; + OutputStream[] outputs = new OutputStream[consumers.length + 1]; + outputs[0] = original; + + for(int i = 0; i < consumers.length; i++) { + outputs[i + 1] = consumers[i]; + } + + return new SplitOutputStream(outputs); + } + + public static SplitOutputStream splitErrOut(OutputStream... consumers) { + OutputStream original = System.err; + OutputStream[] outputs = new OutputStream[consumers.length + 1]; + outputs[0] = original; + + for(int i = 0; i < consumers.length; i++) { + outputs[i + 1] = consumers[i]; + } + + return new SplitOutputStream(outputs); + } + + @Override + public void write(int b) throws IOException { + for(OutputStream o : outputs) { + o.write(b); + } + } + + @Override + public void write(byte[] b) throws IOException { + for(OutputStream o : outputs) { + o.write(b); + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException { + for(OutputStream o : outputs) { + o.write(b, off, len); + } + } + + @Override + public void flush() throws IOException{ + for(OutputStream o : outputs) { + o.flush(); + } + } + + @Override + public void close() throws IOException { + for(OutputStream o : outputs) { + o.close(); + } + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/util/package-info.java b/src/main/java/com/gamebuster19901/excite/modding/util/package-info.java new file mode 100644 index 0000000..908c39e --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/util/package-info.java @@ -0,0 +1 @@ +package com.gamebuster19901.excite.modding.util; \ No newline at end of file diff --git a/src/main/resources/kaitai/monster_res.ksy b/src/main/resources/kaitai/monster_res.ksy index d92ce62..b7b5813 100644 --- a/src/main/resources/kaitai/monster_res.ksy +++ b/src/main/resources/kaitai/monster_res.ksy @@ -59,7 +59,7 @@ types: seq: - id: compressed_data type: quicklz_rcmp - if: _root.header.compressed == 128 + if: _root.header.compressed == 128 or _root.header.compressed == 1152 - id: uncompressed_data size-eos: true if: _root.header.compressed == 0 From a7e886b84a0f0487e595ca946e1c0b687195bb13 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Fri, 19 Apr 2024 22:53:53 -0400 Subject: [PATCH 02/22] rm debug code --- .../modding/unarchiver/ArchivedFile.java | 63 +------------------ 1 file changed, 2 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java index aa0d6eb..aaa5f5a 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java @@ -4,6 +4,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.Arrays; import com.gamebuster19901.excite.modding.game.file.kaitai.TocMonster; @@ -23,7 +24,7 @@ public ArchivedFile(TocMonster.Details fileDetails, Archive archive) { System.out.println("File end: " + ((int)fileDetails.fileOffset() + (int)fileDetails.fileSize())); byte[] bytes = archive.getBytes(); System.out.println("Array size: " + bytes.length); - this.bytes = copyOfRange(archive.getBytes(), (int)fileDetails.fileOffset(), (int)(fileDetails.fileOffset() + (int)fileDetails.fileSize())); + this.bytes = Arrays.copyOfRange(archive.getBytes(), (int)fileDetails.fileOffset(), (int)(fileDetails.fileOffset() + (int)fileDetails.fileSize())); } catch(Throwable t) { System.err.println("Could not extract resource " + getName() + " from " + archive.getArchiveFile().getFileName()); @@ -48,64 +49,4 @@ public void writeTo(Path directory) throws IOException { Files.write(f, getBytes(), StandardOpenOption.CREATE); } - /** - * Copies the specified range of the specified array into a new array. - * The initial index of the range ({@code from}) must lie between zero - * and {@code original.length}, inclusive. The value at - * {@code original[from]} is placed into the initial element of the copy - * (unless {@code from == original.length} or {@code from == to}). - * Values from subsequent elements in the original array are placed into - * subsequent elements in the copy. The final index of the range - * ({@code to}), which must be greater than or equal to {@code from}, - * may be greater than {@code original.length}, in which case - * {@code (byte)0} is placed in all elements of the copy whose index is - * greater than or equal to {@code original.length - from}. The length - * of the returned array will be {@code to - from}. - * - * @param original the array from which a range is to be copied - * @param from the initial index of the range to be copied, inclusive - * @param to the final index of the range to be copied, exclusive. - * (This index may lie outside the array.) - * @return a new array containing the specified range from the original array, - * truncated or padded with zeros to obtain the required length - * @throws ArrayIndexOutOfBoundsException if {@code from < 0} - * or {@code from > original.length} - * @throws IllegalArgumentException if {@code from > to} - * @throws NullPointerException if {@code original} is null - * @since 1.6 - */ - public static byte[] copyOfRange(byte[] original, int from, int to) { - // Tickle the JIT to fold special cases optimally - if (from != 0 || to != original.length) - return copyOfRangeByte(original, from, to); - else // from == 0 && to == original.length - return original.clone(); - } - - private static byte[] copyOfRangeByte(byte[] original, int from, int to) { - checkLength(from, to); - int newLength = to - from; - int calcedLength = original.length - from; - System.out.println("Original Length: " + original.length); - System.out.println("From: " + from); - System.out.println("To: " + to); - byte[] copy = new byte[newLength]; - try { - System.arraycopy(original, from, copy, 0, - Math.min(original.length - from, newLength)); - } - catch(ArrayIndexOutOfBoundsException e) { - e.printStackTrace(); - System.arraycopy(original, from, copy, 0, - Math.min(original.length - from, newLength)); - } - return copy; - } - - private static void checkLength(int from, int to) { - if (to < from) { - throw new IllegalArgumentException(from + " > " + to); - } - } - } From 929a55fb568f80b21a761311495cfeb83f3e89d8 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Fri, 19 Apr 2024 22:54:31 -0400 Subject: [PATCH 03/22] more ui progress --- .../excite/modding/ui/Window.java | 169 +++++++++++------- 1 file changed, 104 insertions(+), 65 deletions(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 9d34ba5..53510f1 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -28,18 +28,9 @@ import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; import javax.swing.JPanel; -import java.awt.BorderLayout; -import javax.swing.JList; -import javax.swing.BoxLayout; -import com.jgoodies.forms.layout.FormLayout; -import com.jgoodies.forms.layout.ColumnSpec; -import com.jgoodies.forms.layout.RowSpec; import java.awt.GridLayout; -import javax.swing.GroupLayout; -import javax.swing.GroupLayout.Alignment; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; -import java.awt.FlowLayout; import javax.swing.JTable; public class Window implements BatchListener, MouseWheelListener { @@ -154,7 +145,6 @@ private void initialize() throws InterruptedException { JPanel panel = new JPanel(); tabbedPane.addTab("Progress", null, panel, null); - panel.setLayout(new BorderLayout(0, 0)); //gridPanel.setPreferredSize(new Dimension(814, 406)); @@ -169,28 +159,30 @@ private void initialize() throws InterruptedException { } tabbedPane.addTab(c.getName(), null); } + panel.setLayout(new GridLayout(0, 2, 0, 0)); JPanel panel_1 = new JPanel(); - panel.add(panel_1, BorderLayout.CENTER); + panel.add(panel_1); GridBagLayout gbl_panel_1 = new GridBagLayout(); - gbl_panel_1.columnWidths = new int[]{0, 0, 70, 90, 0}; + gbl_panel_1.columnWidths = new int[]{100, 0, 70, 90, 0, 0}; gbl_panel_1.rowHeights = new int[]{0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; gbl_panel_1.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; panel_1.setLayout(gbl_panel_1); JLabel lblCopyOperation = new JLabel("Copy Operation"); GridBagConstraints gbc_lblCopyOperation = new GridBagConstraints(); - gbc_lblCopyOperation.gridwidth = 4; - gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 5); + gbc_lblCopyOperation.gridwidth = 5; + gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 0); gbc_lblCopyOperation.gridx = 0; gbc_lblCopyOperation.gridy = 0; panel_1.add(lblCopyOperation, gbc_lblCopyOperation); - BatchOperationComponent allBatches = new BatchOperationComponent(batchRunner, " All Batches "); + BatchOperationComponent allBatches = new BatchOperationComponent(batchRunner, "All Batches"); + allBatches.setToolTipText("All Batches"); GridBagConstraints gbc_allBatches = new GridBagConstraints(); - gbc_allBatches.fill = GridBagConstraints.VERTICAL; - gbc_allBatches.gridheight = 9; - gbc_allBatches.insets = new Insets(0, 0, 5, 5); + gbc_allBatches.fill = GridBagConstraints.BOTH; + gbc_allBatches.gridheight = 10; + gbc_allBatches.insets = new Insets(0, 0, 0, 5); gbc_allBatches.gridx = 0; gbc_allBatches.gridy = 1; panel_1.add(allBatches, gbc_allBatches); @@ -216,11 +208,18 @@ private void initialize() throws InterruptedException { JLabel lblTotalArchivesCount = new JLabel("0"); GridBagConstraints gbc_lblTotalArchivesCount = new GridBagConstraints(); gbc_lblTotalArchivesCount.anchor = GridBagConstraints.WEST; - gbc_lblTotalArchivesCount.insets = new Insets(0, 0, 5, 0); + gbc_lblTotalArchivesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalArchivesCount.gridx = 3; gbc_lblTotalArchivesCount.gridy = 1; panel_1.add(lblTotalArchivesCount, gbc_lblTotalArchivesCount); + JLabel label_5 = new JLabel("100%"); + GridBagConstraints gbc_label_5 = new GridBagConstraints(); + gbc_label_5.insets = new Insets(0, 0, 5, 0); + gbc_label_5.gridx = 4; + gbc_label_5.gridy = 1; + panel_1.add(label_5, gbc_label_5); + JLabel lblTotalResources = new JLabel("Total Resources:"); lblTotalResources.setHorizontalAlignment(SwingConstants.RIGHT); GridBagConstraints gbc_lblTotalResources = new GridBagConstraints(); @@ -233,11 +232,18 @@ private void initialize() throws InterruptedException { JLabel lblTotalResourcesCount = new JLabel("0"); GridBagConstraints gbc_lblTotalResourcesCount = new GridBagConstraints(); gbc_lblTotalResourcesCount.anchor = GridBagConstraints.WEST; - gbc_lblTotalResourcesCount.insets = new Insets(0, 0, 5, 0); + gbc_lblTotalResourcesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalResourcesCount.gridx = 3; gbc_lblTotalResourcesCount.gridy = 2; panel_1.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); + JLabel label_6 = new JLabel("100%"); + GridBagConstraints gbc_label_6 = new GridBagConstraints(); + gbc_label_6.insets = new Insets(0, 0, 5, 0); + gbc_label_6.gridx = 4; + gbc_label_6.gridy = 2; + panel_1.add(label_6, gbc_label_6); + JLabel lblArchivesCopied = new JLabel("Archives Copied:"); GridBagConstraints gbc_lblArchivesCopied = new GridBagConstraints(); gbc_lblArchivesCopied.anchor = GridBagConstraints.EAST; @@ -246,14 +252,21 @@ private void initialize() throws InterruptedException { gbc_lblArchivesCopied.gridy = 4; panel_1.add(lblArchivesCopied, gbc_lblArchivesCopied); - JLabel label_1 = new JLabel("0 (0%)"); + JLabel label_1 = new JLabel("99"); GridBagConstraints gbc_label_1 = new GridBagConstraints(); gbc_label_1.anchor = GridBagConstraints.WEST; - gbc_label_1.insets = new Insets(0, 0, 5, 0); + gbc_label_1.insets = new Insets(0, 0, 5, 5); gbc_label_1.gridx = 3; gbc_label_1.gridy = 4; panel_1.add(label_1, gbc_label_1); + JLabel label_7 = new JLabel("0%"); + GridBagConstraints gbc_label_7 = new GridBagConstraints(); + gbc_label_7.insets = new Insets(0, 0, 5, 0); + gbc_label_7.gridx = 4; + gbc_label_7.gridy = 4; + panel_1.add(label_7, gbc_label_7); + JLabel lblResourcesCopied = new JLabel("Resources Copied:"); GridBagConstraints gbc_lblResourcesCopied = new GridBagConstraints(); gbc_lblResourcesCopied.anchor = GridBagConstraints.EAST; @@ -262,30 +275,20 @@ private void initialize() throws InterruptedException { gbc_lblResourcesCopied.gridy = 5; panel_1.add(lblResourcesCopied, gbc_lblResourcesCopied); - JLabel label_2 = new JLabel("0 (0%)"); + JLabel label_2 = new JLabel("0"); GridBagConstraints gbc_label_2 = new GridBagConstraints(); gbc_label_2.anchor = GridBagConstraints.WEST; - gbc_label_2.insets = new Insets(0, 0, 5, 0); + gbc_label_2.insets = new Insets(0, 0, 5, 5); gbc_label_2.gridx = 3; gbc_label_2.gridy = 5; panel_1.add(label_2, gbc_label_2); - JLabel lblArchivesProcessed = new JLabel("Archives Processed:"); - lblArchivesProcessed.setHorizontalAlignment(SwingConstants.RIGHT); - GridBagConstraints gbc_lblArchivesProcessed = new GridBagConstraints(); - gbc_lblArchivesProcessed.anchor = GridBagConstraints.EAST; - gbc_lblArchivesProcessed.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesProcessed.gridx = 2; - gbc_lblArchivesProcessed.gridy = 7; - panel_1.add(lblArchivesProcessed, gbc_lblArchivesProcessed); - - JLabel lblArchivesprocessed = new JLabel("0 (0%)"); - GridBagConstraints gbc_lblArchivesprocessed = new GridBagConstraints(); - gbc_lblArchivesprocessed.insets = new Insets(0, 0, 5, 0); - gbc_lblArchivesprocessed.anchor = GridBagConstraints.WEST; - gbc_lblArchivesprocessed.gridx = 3; - gbc_lblArchivesprocessed.gridy = 7; - panel_1.add(lblArchivesprocessed, gbc_lblArchivesprocessed); + JLabel label_8 = new JLabel("0%"); + GridBagConstraints gbc_label_8 = new GridBagConstraints(); + gbc_label_8.insets = new Insets(0, 0, 5, 0); + gbc_label_8.gridx = 4; + gbc_label_8.gridy = 5; + panel_1.add(label_8, gbc_label_8); JLabel lblArchivesProcessed_1 = new JLabel("Archives Skipped:"); lblArchivesProcessed_1.setHorizontalAlignment(SwingConstants.RIGHT); @@ -296,29 +299,20 @@ private void initialize() throws InterruptedException { gbc_lblArchivesProcessed_1.gridy = 8; panel_1.add(lblArchivesProcessed_1, gbc_lblArchivesProcessed_1); - JLabel label_3 = new JLabel("0 (0%)"); + JLabel label_3 = new JLabel("0"); GridBagConstraints gbc_label_3 = new GridBagConstraints(); gbc_label_3.anchor = GridBagConstraints.WEST; - gbc_label_3.insets = new Insets(0, 0, 5, 0); + gbc_label_3.insets = new Insets(0, 0, 5, 5); gbc_label_3.gridx = 3; gbc_label_3.gridy = 8; panel_1.add(label_3, gbc_label_3); - JLabel lblResourcesProcessed = new JLabel("Resources Processed:"); - GridBagConstraints gbc_lblResourcesProcessed = new GridBagConstraints(); - gbc_lblResourcesProcessed.anchor = GridBagConstraints.EAST; - gbc_lblResourcesProcessed.insets = new Insets(0, 0, 5, 5); - gbc_lblResourcesProcessed.gridx = 2; - gbc_lblResourcesProcessed.gridy = 9; - panel_1.add(lblResourcesProcessed, gbc_lblResourcesProcessed); - - JLabel label = new JLabel("100 (100%)"); - GridBagConstraints gbc_label = new GridBagConstraints(); - gbc_label.insets = new Insets(0, 0, 5, 0); - gbc_label.anchor = GridBagConstraints.WEST; - gbc_label.gridx = 3; - gbc_label.gridy = 9; - panel_1.add(label, gbc_label); + JLabel label_10 = new JLabel("0%"); + GridBagConstraints gbc_label_10 = new GridBagConstraints(); + gbc_label_10.insets = new Insets(0, 0, 5, 0); + gbc_label_10.gridx = 4; + gbc_label_10.gridy = 8; + panel_1.add(label_10, gbc_label_10); JLabel lblResourcesSkipped = new JLabel("Resources Skipped"); GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); @@ -328,28 +322,73 @@ private void initialize() throws InterruptedException { gbc_lblResourcesSkipped.gridy = 10; panel_1.add(lblResourcesSkipped, gbc_lblResourcesSkipped); - JLabel label_4 = new JLabel("0 (0%)"); + JLabel label_4 = new JLabel("0"); GridBagConstraints gbc_label_4 = new GridBagConstraints(); + gbc_label_4.insets = new Insets(0, 0, 0, 5); gbc_label_4.anchor = GridBagConstraints.WEST; gbc_label_4.gridx = 3; gbc_label_4.gridy = 10; panel_1.add(label_4, gbc_label_4); JPanel panel_2 = new JPanel(); - panel.add(panel_2, BorderLayout.EAST); + panel.add(panel_2); GridBagLayout gbl_panel_2 = new GridBagLayout(); - gbl_panel_2.columnWidths = new int[]{0, 0}; - gbl_panel_2.rowHeights = new int[]{0, 0}; - gbl_panel_2.columnWeights = new double[]{0.0, Double.MIN_VALUE}; - gbl_panel_2.rowWeights = new double[]{0.0, Double.MIN_VALUE}; + gbl_panel_2.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0}; + gbl_panel_2.rowHeights = new int[]{0, 0, 0, 0, 0, 0}; + gbl_panel_2.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_panel_2.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; panel_2.setLayout(gbl_panel_2); - JLabel lblNewLabel = new JLabel("New label"); + JLabel lblNewLabel = new JLabel("Process Operation"); GridBagConstraints gbc_lblNewLabel = new GridBagConstraints(); + gbc_lblNewLabel.insets = new Insets(0, 0, 5, 5); gbc_lblNewLabel.gridx = 0; gbc_lblNewLabel.gridy = 0; panel_2.add(lblNewLabel, gbc_lblNewLabel); + JLabel lblArchivesProcessed = new JLabel("Archives Processed:"); + GridBagConstraints gbc_lblArchivesProcessed = new GridBagConstraints(); + gbc_lblArchivesProcessed.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesProcessed.gridx = 1; + gbc_lblArchivesProcessed.gridy = 3; + panel_2.add(lblArchivesProcessed, gbc_lblArchivesProcessed); + lblArchivesProcessed.setHorizontalAlignment(SwingConstants.RIGHT); + + JLabel lblArchivesprocessed = new JLabel("0"); + GridBagConstraints gbc_lblArchivesprocessed = new GridBagConstraints(); + gbc_lblArchivesprocessed.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesprocessed.gridx = 2; + gbc_lblArchivesprocessed.gridy = 3; + panel_2.add(lblArchivesprocessed, gbc_lblArchivesprocessed); + + JLabel label_9 = new JLabel("0%"); + GridBagConstraints gbc_label_9 = new GridBagConstraints(); + gbc_label_9.insets = new Insets(0, 0, 5, 5); + gbc_label_9.gridx = 3; + gbc_label_9.gridy = 3; + panel_2.add(label_9, gbc_label_9); + + JLabel lblResourcesProcessed = new JLabel("Resources Processed:"); + GridBagConstraints gbc_lblResourcesProcessed = new GridBagConstraints(); + gbc_lblResourcesProcessed.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesProcessed.gridx = 1; + gbc_lblResourcesProcessed.gridy = 4; + panel_2.add(lblResourcesProcessed, gbc_lblResourcesProcessed); + + JLabel label = new JLabel("100"); + GridBagConstraints gbc_label = new GridBagConstraints(); + gbc_label.insets = new Insets(0, 0, 0, 5); + gbc_label.gridx = 2; + gbc_label.gridy = 4; + panel_2.add(label, gbc_label); + + JLabel label_11 = new JLabel("0%"); + GridBagConstraints gbc_label_11 = new GridBagConstraints(); + gbc_label_11.insets = new Insets(0, 0, 0, 5); + gbc_label_11.gridx = 3; + gbc_label_11.gridy = 4; + panel_2.add(label_11, gbc_label_11); + threadSlider = new JSlider(); threadSlider.getSnapToTicks(); threadSlider.setMinimum(1); From 88a65387c05837ecc72ef7ef212550b5060b8704 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sun, 21 Apr 2024 20:01:05 -0400 Subject: [PATCH 04/22] Organize the window initialization code --- .../excite/modding/concurrent/Batch.java | 12 + .../modding/concurrent/BatchContainer.java | 2 + .../modding/concurrent/BatchRunner.java | 21 +- .../modding/ui/BatchOperationComponent.java | 27 +- .../modding/ui/BatchedImageComponent.java | 6 +- .../excite/modding/ui/FixedCellGrid.java | 44 +++- .../excite/modding/ui/ImageComponent.java | 7 +- .../excite/modding/ui/Window.java | 238 +++++++++++------- 8 files changed, 247 insertions(+), 110 deletions(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java index 51f57b9..4da026d 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -10,11 +10,21 @@ public class Batch implements Batcher { + private final String name; private final Set runnables = new HashSet<>(); private final LinkedHashSet listeners = new LinkedHashSet<>(); private volatile boolean accepting = true; + public Batch(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + @Override public void addRunnable(Callable runnable) { if(accepting) { BatchedCallable b = new BatchedCallable(runnable); @@ -26,6 +36,7 @@ public void addRunnable(Callable runnable) { } } + @Override public void addRunnable(Runnable runnable) { if(accepting) { BatchedCallable b = new BatchedCallable(runnable); @@ -37,6 +48,7 @@ public void addRunnable(Runnable runnable) { } } + @Override public void addListener(BatchListener listener) { if(accepting) { if(!listeners.add(listener)) { diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java index 3a2ab37..c2332dd 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java @@ -5,6 +5,8 @@ import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; public interface BatchContainer { + + public abstract String getName(); public Collection getRunnables(); diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index 3d224df..a369f27 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -9,21 +9,28 @@ public class BatchRunner implements BatchWorker { + private final String name; private final ExecutorService executor; private final LinkedHashSet batches = new LinkedHashSet(); private volatile boolean started = false; - public BatchRunner() { - this(Runtime.getRuntime().availableProcessors()); + public BatchRunner(String name) { + this(name, Runtime.getRuntime().availableProcessors()); } - public BatchRunner(int threads) throws IllegalArgumentException { - this(Executors.newFixedThreadPool(threads)); + public BatchRunner(String name, int threads) throws IllegalArgumentException { + this(name, Executors.newFixedThreadPool(threads)); } - public BatchRunner(ExecutorService executor) { + public BatchRunner(String name, ExecutorService executor) { + this.name = name; this.executor = executor; } + + @Override + public String getName() { + return name; + } @Override public void addBatch(Batcher batcher) { @@ -89,6 +96,10 @@ public Collection getListeners() { return ret; } } + + public Collection getBatches() { + return (Collection) batches.clone(); + } public int getCompleted() { int ret = 0; diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java index 4d2d545..94c7be9 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java @@ -2,31 +2,36 @@ import javax.swing.JPanel; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; import com.gamebuster19901.excite.modding.concurrent.BatchContainer; - +import com.gamebuster19901.excite.modding.concurrent.BatchListener; import java.awt.BorderLayout; +import java.util.Collection; + import javax.swing.JLabel; import javax.swing.SwingConstants; -public class BatchOperationComponent extends JPanel { +public class BatchOperationComponent extends JPanel implements BatchContainer { private BatchedImageComponent batch; - private String name; private JLabel fileName; - public BatchOperationComponent(BatchContainer batch, String name) { - this(new BatchedImageComponent(batch), name); + public BatchOperationComponent(BatchContainer batch) { + this(new BatchedImageComponent(batch)); + setName(batch.getName()); } /** @wbp.parser.constructor **/ - public BatchOperationComponent(BatchedImageComponent batch, String name) { + public BatchOperationComponent(BatchedImageComponent batch) { this.batch = batch; setLayout(new BorderLayout(0, 0)); add(batch, BorderLayout.CENTER); + String name = batch.getName(); + fileName = new JLabel(name); fileName.setHorizontalAlignment(SwingConstants.CENTER); add(fileName, BorderLayout.SOUTH); @@ -39,5 +44,15 @@ public void setName(String name) { this.setToolTipText(name); this.fileName.setText(name); } + + @Override + public Collection getRunnables() { + return batch.getRunnables(); + } + + @Override + public Collection getListeners() { + return batch.getListeners(); + } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java index 19f783b..6f834e6 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java @@ -5,7 +5,6 @@ import java.util.Collection; import java.util.LinkedHashMap; import com.gamebuster19901.excite.modding.concurrent.BatchListener; -import com.gamebuster19901.excite.modding.concurrent.Batch; import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; import com.gamebuster19901.excite.modding.concurrent.BatchContainer; @@ -13,11 +12,8 @@ public class BatchedImageComponent extends ImageComponent implements BatchContai private final BatchContainer batch; - public BatchedImageComponent() { - this(new Batch()); - } - public BatchedImageComponent(BatchContainer batch) { + super(batch.getName()); this.batch = batch; } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java index 74e9480..27c6fc7 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java @@ -1,6 +1,8 @@ package com.gamebuster19901.excite.modding.ui; import java.awt.Component; +import java.util.Iterator; +import java.util.Map.Entry; import java.util.NavigableSet; import java.util.TreeMap; @@ -23,7 +25,6 @@ protected void paintComponent(Graphics g) { components.forEach((index, component) -> { component.setBounds(calculateCellX(index), calculateCellY(index), getCellWidth(), getCellHeight()); - //System.out.println("Rendering component " + index + " at " + component.getX() + ", " + component.getY()); }); } @@ -59,6 +60,20 @@ public final Component putComponent(Integer index, Component component) { return components.put(index, component); } + public final void removeComponent(Integer index) { + Component toRemove = getComponent(index); + if(toRemove != null) { + components.remove(index); //we want to remove from the grid directly since it will be much faster than the overridden FixedCellGrid.remove() + super.remove(toRemove); //Then use super to remove the component reference from swing + } + } + + @Override + public void removeAll() { + components.clear(); + super.removeAll(); + } + public final int getLowestFreeCell() { Integer prevIndex = 0; Integer index = -1; @@ -96,6 +111,33 @@ public Component add(Component component) { putComponent(getLowestFreeCell(), component); return component; } + + @Override + @Deprecated + public Component add(Component component, int index) { + putComponent(index, component); + return component; + } + + @Override + @Deprecated + public void remove(Component component) { + Iterator> set = components.entrySet().iterator(); + while(set.hasNext()) { + Entry e = set.next(); + if(e.getValue() == component) { + set.remove(); + break; + } + } + super.remove(component); + } + + @Override + @Deprecated + public void remove(int index) { + removeComponent(index); + } @Override @Deprecated diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java index 2c79444..fe910d0 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/ImageComponent.java @@ -9,11 +9,12 @@ public class ImageComponent extends JComponent { Image image = null; - public ImageComponent() { - this(null); + public ImageComponent(String name) { + this(name, null); } - public ImageComponent(Image image) { + public ImageComponent(String name, Image image) { + this.setName(name); this.image = image; } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 53510f1..06cc76c 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -7,31 +7,36 @@ import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.io.PrintStream; +import java.util.Collection; +import java.util.Iterator; import javax.swing.JFrame; import com.gamebuster19901.excite.modding.concurrent.BatchListener; import com.gamebuster19901.excite.modding.concurrent.BatchRunner; +import com.gamebuster19901.excite.modding.concurrent.Batcher; import com.gamebuster19901.excite.modding.util.SplitOutputStream; import com.gamebuster19901.excite.modding.concurrent.Batch; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; +import com.gamebuster19901.excite.modding.concurrent.BatchContainer; import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; import javax.swing.UIManager; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JSeparator; import javax.swing.JTabbedPane; -import javax.swing.JTextArea; import javax.swing.JSlider; import javax.swing.JProgressBar; -import javax.swing.SwingConstants; import javax.swing.JScrollPane; -import javax.swing.ScrollPaneConstants; +import javax.swing.SwingConstants; import javax.swing.JPanel; import java.awt.GridLayout; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import javax.swing.JTable; +import javax.swing.JTextArea; public class Window implements BatchListener, MouseWheelListener { @@ -45,9 +50,13 @@ public class Window implements BatchListener, MouseWheelListener { private JSlider threadSlider; private JLabel lblThreads; private static Window window; - private final JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP); private final JProgressBar progressBar = new JProgressBar(); + private final JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP); + private final FixedCellGrid gridPanel = genGridPanel(); private JTable table; + + private BatchRunner copyOperations; + private BatchRunner processOperations; /** * Launch the application. @@ -81,6 +90,14 @@ public Window() throws InterruptedException { * @throws InterruptedException */ private void initialize() throws InterruptedException { + + copyOperations = genCopyBatches(); + + setupFrame(); + setupTabbedPane(); + } + + private void setupFrame() { frame = new JFrame(); frame.setTitle("ExciteModder"); frame.setBounds(100, 100, 1000, 680); @@ -118,19 +135,68 @@ private void initialize() throws InterruptedException { btnChangeDest.setToolTipText("Change where ExciteModder will copy the game files to"); btnChangeDest.setBounds(761, 36, 117, 19); frame.getContentPane().add(btnChangeDest); + + threadSlider = new JSlider(); + threadSlider.getSnapToTicks(); + threadSlider.setMinimum(1); + threadSlider.setMaximum(Runtime.getRuntime().availableProcessors()); + threadSlider.setValue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); + threadSlider.setBounds(5, 90, 92, 16); + threadSlider.addChangeListener((s) -> { + if(lblThreads != null) { + lblThreads.setText("Threads: " + threadSlider.getValue()); + } + }); + frame.getContentPane().add(threadSlider); + + lblThreads = new JLabel("Threads: " + threadSlider.getValue()); + lblThreads.setBounds(12, 74, 132, 15); + frame.getContentPane().add(lblThreads); + + JSeparator separator = new JSeparator(); + separator.setBounds(12, 64, 971, 2); + frame.getContentPane().add(separator); + + progressBar.setVisible(false); + + JLabel lblStatus = new JLabel("Progress: 0%"); + lblStatus.setHorizontalAlignment(SwingConstants.CENTER); + lblStatus.setBounds(440, 110, 120, 15); + lblStatus.setVisible(false); + + frame.getContentPane().add(lblStatus); + progressBar.setBounds(12, 110, 971, 15); + frame.getContentPane().add(progressBar); + + JButton btnExtract = new JButton("Extract!"); + btnExtract.setBounds(890, 9, 93, 45); + btnExtract.setEnabled(false); + + frame.getContentPane().add(btnExtract); + frame.validate(); + frame.repaint(); + tabbedPane.addMouseWheelListener(this); + } + + private void setupTabbedPane() { tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); tabbedPane.setBounds(0, 129, 1000, 511); frame.getContentPane().add(tabbedPane); - - + setupConsoleOutputTab(); + setupStatusTab(); + setupProgressTab(); - FixedCellGrid gridPanel = new FixedCellSizeGrid(new Dimension(385, 385), new Dimension(100, 100), 0); - - gridPanel.setVisible(true); - gridPanel.components.remove(-1); + for(Batcher b : copyOperations.getBatches()) { + tabbedPane.addTab(b.getName(), null); + } + } + + private void setupConsoleOutputTab() { + JPanel consolePanel = new JPanel(); + consolePanel.setLayout(new GridLayout(0, 2, 0, 0)); JTextArea textArea = new JTextArea(); textArea.setBorder(BorderFactory.createLoweredBevelBorder()); textArea.setOpaque(true); @@ -141,51 +207,55 @@ private void initialize() throws InterruptedException { JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); tabbedPane.addTab("Console Output", null, scrollPane, null); + } + + public void setupStatusTab() { tabbedPane.addTab("Status", null, gridPanel, null); - - JPanel panel = new JPanel(); - tabbedPane.addTab("Progress", null, panel, null); - - //gridPanel.setPreferredSize(new Dimension(814, 406)); - - BatchRunner batchRunner = new BatchRunner(); - for(int i = 0; i < 10; i++) { - Batch b = new Batch(); - batchRunner.addBatch(b); - BatchOperationComponent c = new BatchOperationComponent(b, "File " + i); - gridPanel.putComponent(i, c); - if(i == 5) { - c.setName("A very long file name"); - } - tabbedPane.addTab(c.getName(), null); + Iterator batches = copyOperations.getBatches().iterator(); + int i = 0; + while(batches.hasNext()) { + gridPanel.putComponent(i, new BatchOperationComponent(batches.next())); + i++; } - panel.setLayout(new GridLayout(0, 2, 0, 0)); + } + + public void setupProgressTab() { + JPanel progressPanel = new JPanel(); + tabbedPane.addTab("Progress", null, progressPanel, null); + + progressPanel.setLayout(new GridLayout(0, 2, 0, 0)); + setupLeftProgressPane(progressPanel); + setupRightProgressPane(progressPanel); + + } + + private void setupLeftProgressPane(JPanel progressPanel) { JPanel panel_1 = new JPanel(); - panel.add(panel_1); + progressPanel.add(panel_1); GridBagLayout gbl_panel_1 = new GridBagLayout(); - gbl_panel_1.columnWidths = new int[]{100, 0, 70, 90, 0, 0}; - gbl_panel_1.rowHeights = new int[]{0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + gbl_panel_1.columnWidths = new int[] {100, 0, 70, 90, 0, 0, 0}; + gbl_panel_1.rowHeights = new int[] {0, 15, 0, 30, 0, 0, 30, 30, 0, 0, 30, 0}; gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; gbl_panel_1.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; panel_1.setLayout(gbl_panel_1); JLabel lblCopyOperation = new JLabel("Copy Operation"); GridBagConstraints gbc_lblCopyOperation = new GridBagConstraints(); - gbc_lblCopyOperation.gridwidth = 5; + gbc_lblCopyOperation.gridwidth = 6; gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 0); gbc_lblCopyOperation.gridx = 0; gbc_lblCopyOperation.gridy = 0; panel_1.add(lblCopyOperation, gbc_lblCopyOperation); - BatchOperationComponent allBatches = new BatchOperationComponent(batchRunner, "All Batches"); - allBatches.setToolTipText("All Batches"); + BatchOperationComponent allBatchesCopy = new BatchOperationComponent(copyOperations); + allBatchesCopy.setToolTipText("All Batches"); GridBagConstraints gbc_allBatches = new GridBagConstraints(); gbc_allBatches.fill = GridBagConstraints.BOTH; - gbc_allBatches.gridheight = 10; + gbc_allBatches.gridheight = 9; gbc_allBatches.insets = new Insets(0, 0, 0, 5); gbc_allBatches.gridx = 0; gbc_allBatches.gridy = 1; - panel_1.add(allBatches, gbc_allBatches); + panel_1.add(allBatchesCopy, gbc_allBatches); JSeparator separator_1 = new JSeparator(); GridBagConstraints gbc_separator_1 = new GridBagConstraints(); @@ -314,33 +384,36 @@ private void initialize() throws InterruptedException { gbc_label_10.gridy = 8; panel_1.add(label_10, gbc_label_10); - JLabel lblResourcesSkipped = new JLabel("Resources Skipped"); + JLabel lblResourcesSkipped = new JLabel("Resources Skipped:"); GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); gbc_lblResourcesSkipped.anchor = GridBagConstraints.EAST; - gbc_lblResourcesSkipped.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkipped.gridx = 2; - gbc_lblResourcesSkipped.gridy = 10; + gbc_lblResourcesSkipped.gridy = 9; panel_1.add(lblResourcesSkipped, gbc_lblResourcesSkipped); JLabel label_4 = new JLabel("0"); GridBagConstraints gbc_label_4 = new GridBagConstraints(); - gbc_label_4.insets = new Insets(0, 0, 0, 5); + gbc_label_4.insets = new Insets(0, 0, 5, 5); gbc_label_4.anchor = GridBagConstraints.WEST; gbc_label_4.gridx = 3; - gbc_label_4.gridy = 10; + gbc_label_4.gridy = 9; panel_1.add(label_4, gbc_label_4); - + } + + private void setupRightProgressPane(JPanel progressPanel) { JPanel panel_2 = new JPanel(); - panel.add(panel_2); + progressPanel.add(panel_2); GridBagLayout gbl_panel_2 = new GridBagLayout(); - gbl_panel_2.columnWidths = new int[]{0, 0, 0, 0, 0, 0, 0}; - gbl_panel_2.rowHeights = new int[]{0, 0, 0, 0, 0, 0}; + gbl_panel_2.columnWidths = new int[] {30, 0, 0, 0, 30, 30, 0, 0}; + gbl_panel_2.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0}; gbl_panel_2.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - gbl_panel_2.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_panel_2.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; panel_2.setLayout(gbl_panel_2); JLabel lblNewLabel = new JLabel("Process Operation"); GridBagConstraints gbc_lblNewLabel = new GridBagConstraints(); + gbc_lblNewLabel.gridwidth = 7; gbc_lblNewLabel.insets = new Insets(0, 0, 5, 5); gbc_lblNewLabel.gridx = 0; gbc_lblNewLabel.gridy = 0; @@ -370,66 +443,44 @@ private void initialize() throws InterruptedException { JLabel lblResourcesProcessed = new JLabel("Resources Processed:"); GridBagConstraints gbc_lblResourcesProcessed = new GridBagConstraints(); - gbc_lblResourcesProcessed.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesProcessed.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessed.gridx = 1; gbc_lblResourcesProcessed.gridy = 4; panel_2.add(lblResourcesProcessed, gbc_lblResourcesProcessed); JLabel label = new JLabel("100"); GridBagConstraints gbc_label = new GridBagConstraints(); - gbc_label.insets = new Insets(0, 0, 0, 5); + gbc_label.insets = new Insets(0, 0, 5, 5); gbc_label.gridx = 2; gbc_label.gridy = 4; panel_2.add(label, gbc_label); JLabel label_11 = new JLabel("0%"); GridBagConstraints gbc_label_11 = new GridBagConstraints(); - gbc_label_11.insets = new Insets(0, 0, 0, 5); + gbc_label_11.insets = new Insets(0, 0, 5, 5); gbc_label_11.gridx = 3; gbc_label_11.gridy = 4; panel_2.add(label_11, gbc_label_11); - - threadSlider = new JSlider(); - threadSlider.getSnapToTicks(); - threadSlider.setMinimum(1); - threadSlider.setMaximum(Runtime.getRuntime().availableProcessors()); - threadSlider.setValue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); - threadSlider.setBounds(5, 90, 92, 16); - threadSlider.addChangeListener((s) -> { - if(lblThreads != null) { - lblThreads.setText("Threads: " + threadSlider.getValue()); - } - }); - frame.getContentPane().add(threadSlider); - - lblThreads = new JLabel("Threads: " + threadSlider.getValue()); - lblThreads.setBounds(12, 74, 132, 15); - frame.getContentPane().add(lblThreads); - - JSeparator separator = new JSeparator(); - separator.setBounds(12, 64, 971, 2); - frame.getContentPane().add(separator); - - progressBar.setVisible(false); - - JLabel lblStatus = new JLabel("Progress: 0%"); - lblStatus.setHorizontalAlignment(SwingConstants.CENTER); - lblStatus.setBounds(440, 110, 120, 15); - lblStatus.setVisible(false); - - frame.getContentPane().add(lblStatus); - progressBar.setBounds(12, 110, 971, 15); - frame.getContentPane().add(progressBar); - - JButton btnExtract = new JButton("Extract!"); - btnExtract.setBounds(890, 9, 93, 45); - btnExtract.setEnabled(false); - - frame.getContentPane().add(btnExtract); - frame.validate(); - frame.repaint(); - tabbedPane.addMouseWheelListener(this); - + } + + private BatchRunner genCopyBatches() { + BatchRunner batchRunner = new BatchRunner("Copy Operations"); + for(int i = 0; i < 10; i++) { + Batch b = new Batch("File " + i); + batchRunner.addBatch(b); + } + return batchRunner; + } + + private void setGridBatches(BatchContainer batch, FixedCellGrid grid) { + Collection batches = batch.getRunnables(); + for(int i = 0; i < batches.size(); i++) { + tabbedPane.addTab("File " + i, new BatchOperationComponent(batch)); + } + } + + private BatchRunner getProcessBatches() { + throw new UnsupportedOperationException("Not yet implemented"); } @Override @@ -467,6 +518,13 @@ else if(scrolledComponent instanceof FixedCellSizeGrid) { } } + private FixedCellGrid genGridPanel() { + FixedCellGrid gridPanel = new FixedCellSizeGrid(new Dimension(385, 385), new Dimension(100, 100), 0); + gridPanel.setVisible(true); + + return gridPanel; + } + private int getWantedThreads() { int ret = threadSlider.getValue(); if(ret < 1) { From 5dbad9d2b094081ef03860f025ca430a78be2404 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sun, 21 Apr 2024 20:30:07 -0400 Subject: [PATCH 05/22] More organization and renaming varialbes --- .../excite/modding/ui/Window.java | 404 +++++++++++++----- 1 file changed, 286 insertions(+), 118 deletions(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 06cc76c..72c18ce 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -235,15 +235,15 @@ private void setupLeftProgressPane(JPanel progressPanel) { progressPanel.add(panel_1); GridBagLayout gbl_panel_1 = new GridBagLayout(); gbl_panel_1.columnWidths = new int[] {100, 0, 70, 90, 0, 0, 0}; - gbl_panel_1.rowHeights = new int[] {0, 15, 0, 30, 0, 0, 30, 30, 0, 0, 30, 0}; + gbl_panel_1.rowHeights = new int[] {0, 15, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0}; gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - gbl_panel_1.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_panel_1.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; panel_1.setLayout(gbl_panel_1); JLabel lblCopyOperation = new JLabel("Copy Operation"); GridBagConstraints gbc_lblCopyOperation = new GridBagConstraints(); gbc_lblCopyOperation.gridwidth = 6; - gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 0); + gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 5); gbc_lblCopyOperation.gridx = 0; gbc_lblCopyOperation.gridy = 0; panel_1.add(lblCopyOperation, gbc_lblCopyOperation); @@ -251,8 +251,8 @@ private void setupLeftProgressPane(JPanel progressPanel) { allBatchesCopy.setToolTipText("All Batches"); GridBagConstraints gbc_allBatches = new GridBagConstraints(); gbc_allBatches.fill = GridBagConstraints.BOTH; - gbc_allBatches.gridheight = 9; - gbc_allBatches.insets = new Insets(0, 0, 0, 5); + gbc_allBatches.gridheight = 11; + gbc_allBatches.insets = new Insets(0, 0, 0, 0); gbc_allBatches.gridx = 0; gbc_allBatches.gridy = 1; panel_1.add(allBatchesCopy, gbc_allBatches); @@ -283,21 +283,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalArchivesCount.gridy = 1; panel_1.add(lblTotalArchivesCount, gbc_lblTotalArchivesCount); - JLabel label_5 = new JLabel("100%"); - GridBagConstraints gbc_label_5 = new GridBagConstraints(); - gbc_label_5.insets = new Insets(0, 0, 5, 0); - gbc_label_5.gridx = 4; - gbc_label_5.gridy = 1; - panel_1.add(label_5, gbc_label_5); - - JLabel lblTotalResources = new JLabel("Total Resources:"); - lblTotalResources.setHorizontalAlignment(SwingConstants.RIGHT); - GridBagConstraints gbc_lblTotalResources = new GridBagConstraints(); - gbc_lblTotalResources.anchor = GridBagConstraints.EAST; - gbc_lblTotalResources.insets = new Insets(0, 0, 5, 5); - gbc_lblTotalResources.gridx = 2; - gbc_lblTotalResources.gridy = 2; - panel_1.add(lblTotalResources, gbc_lblTotalResources); + JLabel lblFoundResources = new JLabel("Total Resources:"); + lblFoundResources.setHorizontalAlignment(SwingConstants.RIGHT); + GridBagConstraints gbc_lblFoundResources = new GridBagConstraints(); + gbc_lblFoundResources.anchor = GridBagConstraints.EAST; + gbc_lblFoundResources.insets = new Insets(0, 0, 5, 5); + gbc_lblFoundResources.gridx = 2; + gbc_lblFoundResources.gridy = 2; + panel_1.add(lblFoundResources, gbc_lblFoundResources); JLabel lblTotalResourcesCount = new JLabel("0"); GridBagConstraints gbc_lblTotalResourcesCount = new GridBagConstraints(); @@ -307,13 +300,6 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalResourcesCount.gridy = 2; panel_1.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); - JLabel label_6 = new JLabel("100%"); - GridBagConstraints gbc_label_6 = new GridBagConstraints(); - gbc_label_6.insets = new Insets(0, 0, 5, 0); - gbc_label_6.gridx = 4; - gbc_label_6.gridy = 2; - panel_1.add(label_6, gbc_label_6); - JLabel lblArchivesCopied = new JLabel("Archives Copied:"); GridBagConstraints gbc_lblArchivesCopied = new GridBagConstraints(); gbc_lblArchivesCopied.anchor = GridBagConstraints.EAST; @@ -322,20 +308,20 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesCopied.gridy = 4; panel_1.add(lblArchivesCopied, gbc_lblArchivesCopied); - JLabel label_1 = new JLabel("99"); - GridBagConstraints gbc_label_1 = new GridBagConstraints(); - gbc_label_1.anchor = GridBagConstraints.WEST; - gbc_label_1.insets = new Insets(0, 0, 5, 5); - gbc_label_1.gridx = 3; - gbc_label_1.gridy = 4; - panel_1.add(label_1, gbc_label_1); - - JLabel label_7 = new JLabel("0%"); - GridBagConstraints gbc_label_7 = new GridBagConstraints(); - gbc_label_7.insets = new Insets(0, 0, 5, 0); - gbc_label_7.gridx = 4; - gbc_label_7.gridy = 4; - panel_1.add(label_7, gbc_label_7); + JLabel lblArchivesCopiedCount = new JLabel("0"); + GridBagConstraints gbc_lblArchivesCopiedCount = new GridBagConstraints(); + gbc_lblArchivesCopiedCount.anchor = GridBagConstraints.WEST; + gbc_lblArchivesCopiedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesCopiedCount.gridx = 3; + gbc_lblArchivesCopiedCount.gridy = 4; + panel_1.add(lblArchivesCopiedCount, gbc_lblArchivesCopiedCount); + + JLabel lblArchivesCopiedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblArchivesCopiedPercent = new GridBagConstraints(); + gbc_lblArchivesCopiedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesCopiedPercent.gridx = 4; + gbc_lblArchivesCopiedPercent.gridy = 4; + panel_1.add(lblArchivesCopiedPercent, gbc_lblArchivesCopiedPercent); JLabel lblResourcesCopied = new JLabel("Resources Copied:"); GridBagConstraints gbc_lblResourcesCopied = new GridBagConstraints(); @@ -345,70 +331,124 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesCopied.gridy = 5; panel_1.add(lblResourcesCopied, gbc_lblResourcesCopied); - JLabel label_2 = new JLabel("0"); - GridBagConstraints gbc_label_2 = new GridBagConstraints(); - gbc_label_2.anchor = GridBagConstraints.WEST; - gbc_label_2.insets = new Insets(0, 0, 5, 5); - gbc_label_2.gridx = 3; - gbc_label_2.gridy = 5; - panel_1.add(label_2, gbc_label_2); - - JLabel label_8 = new JLabel("0%"); - GridBagConstraints gbc_label_8 = new GridBagConstraints(); - gbc_label_8.insets = new Insets(0, 0, 5, 0); - gbc_label_8.gridx = 4; - gbc_label_8.gridy = 5; - panel_1.add(label_8, gbc_label_8); - - JLabel lblArchivesProcessed_1 = new JLabel("Archives Skipped:"); - lblArchivesProcessed_1.setHorizontalAlignment(SwingConstants.RIGHT); - GridBagConstraints gbc_lblArchivesProcessed_1 = new GridBagConstraints(); - gbc_lblArchivesProcessed_1.anchor = GridBagConstraints.EAST; - gbc_lblArchivesProcessed_1.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesProcessed_1.gridx = 2; - gbc_lblArchivesProcessed_1.gridy = 8; - panel_1.add(lblArchivesProcessed_1, gbc_lblArchivesProcessed_1); - - JLabel label_3 = new JLabel("0"); - GridBagConstraints gbc_label_3 = new GridBagConstraints(); - gbc_label_3.anchor = GridBagConstraints.WEST; - gbc_label_3.insets = new Insets(0, 0, 5, 5); - gbc_label_3.gridx = 3; - gbc_label_3.gridy = 8; - panel_1.add(label_3, gbc_label_3); - - JLabel label_10 = new JLabel("0%"); - GridBagConstraints gbc_label_10 = new GridBagConstraints(); - gbc_label_10.insets = new Insets(0, 0, 5, 0); - gbc_label_10.gridx = 4; - gbc_label_10.gridy = 8; - panel_1.add(label_10, gbc_label_10); + JLabel lblResourcesCopiedCount = new JLabel("0"); + GridBagConstraints gbc_lblResourcesCopiedCount = new GridBagConstraints(); + gbc_lblResourcesCopiedCount.anchor = GridBagConstraints.WEST; + gbc_lblResourcesCopiedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesCopiedCount.gridx = 3; + gbc_lblResourcesCopiedCount.gridy = 5; + panel_1.add(lblResourcesCopiedCount, gbc_lblResourcesCopiedCount); + + JLabel lblResourcesCopiedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblResourcesCopiedPercent = new GridBagConstraints(); + gbc_lblResourcesCopiedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesCopiedPercent.gridx = 4; + gbc_lblResourcesCopiedPercent.gridy = 5; + panel_1.add(lblResourcesCopiedPercent, gbc_lblResourcesCopiedPercent); + + JLabel lblArchivesSkipped = new JLabel("Archives Skipped:"); + lblArchivesSkipped.setHorizontalAlignment(SwingConstants.RIGHT); + GridBagConstraints gbc_lblArchivesSkipped = new GridBagConstraints(); + gbc_lblArchivesSkipped.anchor = GridBagConstraints.EAST; + gbc_lblArchivesSkipped.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesSkipped.gridx = 2; + gbc_lblArchivesSkipped.gridy = 7; + panel_1.add(lblArchivesSkipped, gbc_lblArchivesSkipped); + + JLabel lblArchivesSkippedCount = new JLabel("0"); + GridBagConstraints gbc_lblArchivesSkippedCount = new GridBagConstraints(); + gbc_lblArchivesSkippedCount.anchor = GridBagConstraints.WEST; + gbc_lblArchivesSkippedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesSkippedCount.gridx = 3; + gbc_lblArchivesSkippedCount.gridy = 7; + panel_1.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); + + JLabel lblArchivesSkippedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblArchivesSkippedPercent = new GridBagConstraints(); + gbc_lblArchivesSkippedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesSkippedPercent.gridx = 4; + gbc_lblArchivesSkippedPercent.gridy = 7; + panel_1.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); JLabel lblResourcesSkipped = new JLabel("Resources Skipped:"); GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); gbc_lblResourcesSkipped.anchor = GridBagConstraints.EAST; gbc_lblResourcesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkipped.gridx = 2; - gbc_lblResourcesSkipped.gridy = 9; + gbc_lblResourcesSkipped.gridy = 8; panel_1.add(lblResourcesSkipped, gbc_lblResourcesSkipped); - JLabel label_4 = new JLabel("0"); - GridBagConstraints gbc_label_4 = new GridBagConstraints(); - gbc_label_4.insets = new Insets(0, 0, 5, 5); - gbc_label_4.anchor = GridBagConstraints.WEST; - gbc_label_4.gridx = 3; - gbc_label_4.gridy = 9; - panel_1.add(label_4, gbc_label_4); + JLabel lblResourcesSkippedCount = new JLabel("0"); + GridBagConstraints gbc_lblResourcesSkippedCount = new GridBagConstraints(); + gbc_lblResourcesSkippedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesSkippedCount.anchor = GridBagConstraints.WEST; + gbc_lblResourcesSkippedCount.gridx = 3; + gbc_lblResourcesSkippedCount.gridy = 8; + panel_1.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); + + JLabel labelResourcesSkippedPercent = new JLabel("0%"); + GridBagConstraints gbc_labelResourcesSkippedPercent = new GridBagConstraints(); + gbc_labelResourcesSkippedPercent.insets = new Insets(0, 0, 5, 5); + gbc_labelResourcesSkippedPercent.gridx = 4; + gbc_labelResourcesSkippedPercent.gridy = 8; + panel_1.add(labelResourcesSkippedPercent, gbc_labelResourcesSkippedPercent); + + JLabel lblArchivesFailed = new JLabel("Archives Failed:"); + lblArchivesFailed.setHorizontalAlignment(SwingConstants.RIGHT); + GridBagConstraints gbc_lblArchivesFailed = new GridBagConstraints(); + gbc_lblArchivesFailed.anchor = GridBagConstraints.EAST; + gbc_lblArchivesFailed.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesFailed.gridx = 2; + gbc_lblArchivesFailed.gridy = 10; + panel_1.add(lblArchivesFailed, gbc_lblArchivesFailed); + + JLabel lblArchivesFailedCount = new JLabel("0"); + GridBagConstraints gbc_lblArchivesFailedCount = new GridBagConstraints(); + gbc_lblArchivesFailedCount.anchor = GridBagConstraints.WEST; + gbc_lblArchivesFailedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesFailedCount.gridx = 3; + gbc_lblArchivesFailedCount.gridy = 10; + panel_1.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); + + JLabel lblArchivesFailedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblArchivesFailedPercent = new GridBagConstraints(); + gbc_lblArchivesFailedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesFailedPercent.gridx = 4; + gbc_lblArchivesFailedPercent.gridy = 10; + panel_1.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); + + JLabel lblResourcesFailed = new JLabel("Resources Failed:"); + GridBagConstraints gbc_lblResourcesFailed = new GridBagConstraints(); + gbc_lblResourcesFailed.anchor = GridBagConstraints.EAST; + gbc_lblResourcesFailed.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailed.gridx = 2; + gbc_lblResourcesFailed.gridy = 11; + panel_1.add(lblResourcesFailed, gbc_lblResourcesFailed); + + JLabel lblResourcesFailedCount = new JLabel("0"); + GridBagConstraints gbc_lblResourcesFailedCount = new GridBagConstraints(); + gbc_lblResourcesFailedCount.anchor = GridBagConstraints.WEST; + gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedCount.gridx = 3; + gbc_lblResourcesFailedCount.gridy = 11; + panel_1.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); + + JLabel lblResourcesFailedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblResourcesFailedPercent = new GridBagConstraints(); + gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedPercent.gridx = 4; + gbc_lblResourcesFailedPercent.gridy = 11; + panel_1.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); } private void setupRightProgressPane(JPanel progressPanel) { JPanel panel_2 = new JPanel(); progressPanel.add(panel_2); GridBagLayout gbl_panel_2 = new GridBagLayout(); - gbl_panel_2.columnWidths = new int[] {30, 0, 0, 0, 30, 30, 0, 0}; - gbl_panel_2.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0}; + gbl_panel_2.columnWidths = new int[] {30, 0, 90, 0, 30, 30, 0, 0}; + gbl_panel_2.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; gbl_panel_2.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - gbl_panel_2.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_panel_2.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; panel_2.setLayout(gbl_panel_2); JLabel lblNewLabel = new JLabel("Process Operation"); @@ -419,48 +459,176 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblNewLabel.gridy = 0; panel_2.add(lblNewLabel, gbc_lblNewLabel); + JLabel lblTotalArchives = new JLabel("Total Archives:"); + GridBagConstraints gbc_lblTotalArchives = new GridBagConstraints(); + gbc_lblTotalArchives.anchor = GridBagConstraints.EAST; + gbc_lblTotalArchives.insets = new Insets(0, 0, 5, 5); + gbc_lblTotalArchives.gridx = 1; + gbc_lblTotalArchives.gridy = 1; + panel_2.add(lblTotalArchives, gbc_lblTotalArchives); + + JLabel lblTotalArchvesCount = new JLabel("0"); + GridBagConstraints gbc_lblTotalArchvesCount = new GridBagConstraints(); + gbc_lblTotalArchvesCount.anchor = GridBagConstraints.WEST; + gbc_lblTotalArchvesCount.insets = new Insets(0, 0, 5, 5); + gbc_lblTotalArchvesCount.gridx = 2; + gbc_lblTotalArchvesCount.gridy = 1; + panel_2.add(lblTotalArchvesCount, gbc_lblTotalArchvesCount); + + JLabel lblTotalResources = new JLabel("Total Resources:"); + GridBagConstraints gbc_lblTotalResources = new GridBagConstraints(); + gbc_lblTotalResources.anchor = GridBagConstraints.EAST; + gbc_lblTotalResources.insets = new Insets(0, 0, 5, 5); + gbc_lblTotalResources.gridx = 1; + gbc_lblTotalResources.gridy = 2; + panel_2.add(lblTotalResources, gbc_lblTotalResources); + + JLabel lblTotalResourcesCount = new JLabel("0"); + GridBagConstraints gbc_lblTotalResourcesCount = new GridBagConstraints(); + gbc_lblTotalResourcesCount.anchor = GridBagConstraints.WEST; + gbc_lblTotalResourcesCount.insets = new Insets(0, 0, 5, 5); + gbc_lblTotalResourcesCount.gridx = 2; + gbc_lblTotalResourcesCount.gridy = 2; + panel_2.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); + JLabel lblArchivesProcessed = new JLabel("Archives Processed:"); GridBagConstraints gbc_lblArchivesProcessed = new GridBagConstraints(); + gbc_lblArchivesProcessed.anchor = GridBagConstraints.EAST; gbc_lblArchivesProcessed.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesProcessed.gridx = 1; - gbc_lblArchivesProcessed.gridy = 3; + gbc_lblArchivesProcessed.gridy = 4; panel_2.add(lblArchivesProcessed, gbc_lblArchivesProcessed); lblArchivesProcessed.setHorizontalAlignment(SwingConstants.RIGHT); - JLabel lblArchivesprocessed = new JLabel("0"); - GridBagConstraints gbc_lblArchivesprocessed = new GridBagConstraints(); - gbc_lblArchivesprocessed.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesprocessed.gridx = 2; - gbc_lblArchivesprocessed.gridy = 3; - panel_2.add(lblArchivesprocessed, gbc_lblArchivesprocessed); - - JLabel label_9 = new JLabel("0%"); - GridBagConstraints gbc_label_9 = new GridBagConstraints(); - gbc_label_9.insets = new Insets(0, 0, 5, 5); - gbc_label_9.gridx = 3; - gbc_label_9.gridy = 3; - panel_2.add(label_9, gbc_label_9); + JLabel lblArchivesProcessedCount = new JLabel("0"); + GridBagConstraints gbc_lblArchivesProcessedCount = new GridBagConstraints(); + gbc_lblArchivesProcessedCount.anchor = GridBagConstraints.WEST; + gbc_lblArchivesProcessedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesProcessedCount.gridx = 2; + gbc_lblArchivesProcessedCount.gridy = 4; + panel_2.add(lblArchivesProcessedCount, gbc_lblArchivesProcessedCount); + + JLabel lblArchivesProcessedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblArchivesProcessedPercent = new GridBagConstraints(); + gbc_lblArchivesProcessedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesProcessedPercent.gridx = 3; + gbc_lblArchivesProcessedPercent.gridy = 4; + panel_2.add(lblArchivesProcessedPercent, gbc_lblArchivesProcessedPercent); JLabel lblResourcesProcessed = new JLabel("Resources Processed:"); GridBagConstraints gbc_lblResourcesProcessed = new GridBagConstraints(); + gbc_lblResourcesProcessed.anchor = GridBagConstraints.EAST; gbc_lblResourcesProcessed.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessed.gridx = 1; - gbc_lblResourcesProcessed.gridy = 4; + gbc_lblResourcesProcessed.gridy = 5; panel_2.add(lblResourcesProcessed, gbc_lblResourcesProcessed); - JLabel label = new JLabel("100"); - GridBagConstraints gbc_label = new GridBagConstraints(); - gbc_label.insets = new Insets(0, 0, 5, 5); - gbc_label.gridx = 2; - gbc_label.gridy = 4; - panel_2.add(label, gbc_label); - - JLabel label_11 = new JLabel("0%"); - GridBagConstraints gbc_label_11 = new GridBagConstraints(); - gbc_label_11.insets = new Insets(0, 0, 5, 5); - gbc_label_11.gridx = 3; - gbc_label_11.gridy = 4; - panel_2.add(label_11, gbc_label_11); + JLabel lblResourcesProcessedCount = new JLabel("0"); + GridBagConstraints gbc_lblResourcesProcessedCount = new GridBagConstraints(); + gbc_lblResourcesProcessedCount.anchor = GridBagConstraints.WEST; + gbc_lblResourcesProcessedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesProcessedCount.gridx = 2; + gbc_lblResourcesProcessedCount.gridy = 5; + panel_2.add(lblResourcesProcessedCount, gbc_lblResourcesProcessedCount); + + JLabel lblResourcesProcessedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblResourcesProcessedPercent = new GridBagConstraints(); + gbc_lblResourcesProcessedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesProcessedPercent.gridx = 3; + gbc_lblResourcesProcessedPercent.gridy = 5; + panel_2.add(lblResourcesProcessedPercent, gbc_lblResourcesProcessedPercent); + + JLabel lblArchivesSkipped = new JLabel("Archives Skipped:"); + GridBagConstraints gbc_lblArchivesSkipped = new GridBagConstraints(); + gbc_lblArchivesSkipped.anchor = GridBagConstraints.EAST; + gbc_lblArchivesSkipped.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesSkipped.gridx = 1; + gbc_lblArchivesSkipped.gridy = 7; + panel_2.add(lblArchivesSkipped, gbc_lblArchivesSkipped); + + JLabel lblArchivesSkippedCount = new JLabel("0"); + GridBagConstraints gbc_lblArchivesSkippedCount = new GridBagConstraints(); + gbc_lblArchivesSkippedCount.anchor = GridBagConstraints.WEST; + gbc_lblArchivesSkippedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesSkippedCount.gridx = 2; + gbc_lblArchivesSkippedCount.gridy = 7; + panel_2.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); + + JLabel lblArchivesSkippedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblArchivesSkippedPercent = new GridBagConstraints(); + gbc_lblArchivesSkippedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesSkippedPercent.gridx = 3; + gbc_lblArchivesSkippedPercent.gridy = 7; + panel_2.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); + + JLabel lblResourcesSkipped = new JLabel("Resources Skipped:"); + GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); + gbc_lblResourcesSkipped.anchor = GridBagConstraints.EAST; + gbc_lblResourcesSkipped.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesSkipped.gridx = 1; + gbc_lblResourcesSkipped.gridy = 8; + panel_2.add(lblResourcesSkipped, gbc_lblResourcesSkipped); + + JLabel lblResourcesSkippedCount = new JLabel("0"); + GridBagConstraints gbc_lblResourcesSkippedCount = new GridBagConstraints(); + gbc_lblResourcesSkippedCount.anchor = GridBagConstraints.WEST; + gbc_lblResourcesSkippedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesSkippedCount.gridx = 2; + gbc_lblResourcesSkippedCount.gridy = 8; + panel_2.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); + + JLabel lblResourcesSkippedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblResourcesSkippedPercent = new GridBagConstraints(); + gbc_lblResourcesSkippedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblResourcesSkippedPercent.gridx = 3; + gbc_lblResourcesSkippedPercent.gridy = 8; + panel_2.add(lblResourcesSkippedPercent, gbc_lblResourcesSkippedPercent); + + JLabel lblArchivesFailed = new JLabel("Archives Failed:"); + GridBagConstraints gbc_lblArchivesFailed = new GridBagConstraints(); + gbc_lblArchivesFailed.anchor = GridBagConstraints.EAST; + gbc_lblArchivesFailed.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesFailed.gridx = 1; + gbc_lblArchivesFailed.gridy = 10; + panel_2.add(lblArchivesFailed, gbc_lblArchivesFailed); + + JLabel lblArchivesFailedCount = new JLabel("0"); + GridBagConstraints gbc_lblArchivesFailedCount = new GridBagConstraints(); + gbc_lblArchivesFailedCount.anchor = GridBagConstraints.WEST; + gbc_lblArchivesFailedCount.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesFailedCount.gridx = 2; + gbc_lblArchivesFailedCount.gridy = 10; + panel_2.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); + + JLabel lblArchivesFailedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblArchivesFailedPercent = new GridBagConstraints(); + gbc_lblArchivesFailedPercent.insets = new Insets(0, 0, 5, 5); + gbc_lblArchivesFailedPercent.gridx = 3; + gbc_lblArchivesFailedPercent.gridy = 10; + panel_2.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); + + JLabel lblResourcesFailed = new JLabel("Resources Failed:"); + GridBagConstraints gbc_lblResourcesFailed = new GridBagConstraints(); + gbc_lblResourcesFailed.anchor = GridBagConstraints.EAST; + gbc_lblResourcesFailed.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailed.gridx = 1; + gbc_lblResourcesFailed.gridy = 11; + panel_2.add(lblResourcesFailed, gbc_lblResourcesFailed); + + JLabel lblResourcesFailedCount = new JLabel("0"); + GridBagConstraints gbc_lblResourcesFailedCount = new GridBagConstraints(); + gbc_lblResourcesFailedCount.anchor = GridBagConstraints.WEST; + gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedCount.gridx = 2; + gbc_lblResourcesFailedCount.gridy = 11; + panel_2.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); + + JLabel lblResourcesFailedPercent = new JLabel("0%"); + GridBagConstraints gbc_lblResourcesFailedPercent = new GridBagConstraints(); + gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedPercent.gridx = 3; + gbc_lblResourcesFailedPercent.gridy = 11; + panel_2.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); } private BatchRunner genCopyBatches() { From 2aaad7b60284211b0cdc94a30066756f28a19ea7 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Tue, 23 Apr 2024 20:56:04 -0400 Subject: [PATCH 06/22] More progress --- .../excite/modding/concurrent/Batch.java | 12 +- .../modding/concurrent/BatchContainer.java | 6 +- .../modding/concurrent/BatchListener.java | 1 + .../modding/concurrent/BatchRunner.java | 39 ++- .../modding/concurrent/BatchWorker.java | 10 +- .../excite/modding/concurrent/Batcher.java | 8 +- .../modding/ui/NonWrappedJEditorPane.java | 17 -- .../excite/modding/ui/Window.java | 257 +++++++++++------- 8 files changed, 203 insertions(+), 147 deletions(-) delete mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java index 4da026d..75d71b0 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -8,7 +8,7 @@ import java.util.concurrent.Callable; import static java.lang.Thread.State; -public class Batch implements Batcher { +public class Batch implements Batcher { private final String name; private final Set runnables = new HashSet<>(); @@ -25,7 +25,7 @@ public String getName() { } @Override - public void addRunnable(Callable runnable) { + public void addRunnable(Callable runnable) { if(accepting) { BatchedCallable b = new BatchedCallable(runnable); runnables.add(new BatchedCallable(runnable)); @@ -91,9 +91,9 @@ private void notAccepting() { throw new IllegalStateException("Batch is not accepting new tasks or listeners."); } - public class BatchedCallable implements Callable { + public class BatchedCallable implements Callable { - private final Callable child; + private final Callable child; private volatile SoftReference threadRef; protected volatile Throwable thrown; protected volatile boolean finished = false; @@ -105,13 +105,13 @@ public BatchedCallable(Runnable r) { }); } - public BatchedCallable(Callable c) { + public BatchedCallable(Callable c) { this.child = c; updateListeners(); } @Override - public Void call() { + public T call() { Thread thread; try { thread = Thread.currentThread(); diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java index c2332dd..51a6a10 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java @@ -2,13 +2,11 @@ import java.util.Collection; -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; - -public interface BatchContainer { +public interface BatchContainer { public abstract String getName(); - public Collection getRunnables(); + public Collection.BatchedCallable> getRunnables(); public Collection getListeners(); diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java index 20df878..ba66094 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchListener.java @@ -1,5 +1,6 @@ package com.gamebuster19901.excite.modding.concurrent; +@FunctionalInterface public interface BatchListener { public void update(); } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index a369f27..811a05c 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -5,14 +5,14 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; -public class BatchRunner implements BatchWorker { +public class BatchRunner implements BatchWorker { private final String name; private final ExecutorService executor; - private final LinkedHashSet batches = new LinkedHashSet(); + private final LinkedHashSet> batches = new LinkedHashSet>(); private volatile boolean started = false; + private volatile boolean listenerAdded = false; public BatchRunner(String name) { this(name, Runtime.getRuntime().availableProcessors()); @@ -33,8 +33,11 @@ public String getName() { } @Override - public void addBatch(Batcher batcher) { + public void addBatch(Batcher batcher) { synchronized(executor) { + if(listenerAdded) { + throw new IllegalStateException("Cannot add a batch after a BatchListener has been added! Add all batches before adding listeners!"); + } if(executor.isShutdown()) { throw new IllegalStateException("Cannot add more batches after the batch runner has shut down!"); } @@ -69,17 +72,17 @@ public void shutdownNow() throws InterruptedException { executor.awaitTermination(5, TimeUnit.SECONDS); } finally { - for(Batcher batch : batches) { + for(Batcher batch : batches) { batch.shutdownNow(); } } } @Override - public Collection getRunnables() { + public Collection.BatchedCallable> getRunnables() { synchronized(batches) { - LinkedHashSet ret = new LinkedHashSet<>(); - for(Batcher batch : batches) { + LinkedHashSet.BatchedCallable> ret = new LinkedHashSet<>(); + for(Batcher batch : batches) { ret.addAll(batch.getRunnables()); } return ret; @@ -90,21 +93,31 @@ public Collection getRunnables() { public Collection getListeners() { synchronized(batches) { LinkedHashSet ret = new LinkedHashSet<>(); - for(Batcher batch : batches) { + for(Batcher batch : batches) { ret.addAll(batch.getListeners()); } return ret; } } - public Collection getBatches() { - return (Collection) batches.clone(); + @Override + public void addBatchListener(BatchListener listener) { + if(!listenerAdded) { + listenerAdded = true; + } + for(Batcher batch : batches) { + batch.addListener(listener); + } + } + + public Collection> getBatches() { + return (Collection>) batches.clone(); } public int getCompleted() { int ret = 0; - Collection callables = getRunnables(); - for(BatchedCallable callable : callables) {ret++;} + Collection.BatchedCallable> callables = getRunnables(); + for(Batch.BatchedCallable callable : callables) {ret++;} return ret; } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java index cbffafa..aace9af 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java @@ -3,11 +3,9 @@ import java.util.Collection; import java.util.concurrent.TimeUnit; -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; - -public interface BatchWorker extends BatchContainer { +public interface BatchWorker extends BatchContainer { - public abstract void addBatch(Batcher batcher); + public abstract void addBatch(Batcher batcher); public abstract void startBatch() throws InterruptedException; @@ -15,8 +13,10 @@ public interface BatchWorker extends BatchContainer { public void shutdownNow() throws InterruptedException; - public Collection getRunnables(); + public Collection.BatchedCallable> getRunnables(); public Collection getListeners(); + public void addBatchListener(BatchListener listener); + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java index aaef965..0a3e795 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java @@ -3,11 +3,9 @@ import java.util.Collection; import java.util.concurrent.Callable; -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; - -public interface Batcher extends BatchContainer { +public interface Batcher extends BatchContainer { - public abstract void addRunnable(Callable runnable); + public abstract void addRunnable(Callable runnable); public abstract void addRunnable(Runnable runnable); @@ -15,7 +13,7 @@ public interface Batcher extends BatchContainer { public void shutdownNow() throws InterruptedException; - public Collection getRunnables(); + public Collection.BatchedCallable> getRunnables(); public Collection getListeners(); diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java b/src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java deleted file mode 100644 index 9d3b71c..0000000 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/NonWrappedJEditorPane.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.gamebuster19901.excite.modding.ui; - -import java.awt.Component; -import javax.swing.JEditorPane; -import javax.swing.plaf.ComponentUI; - -public class NonWrappedJEditorPane extends JEditorPane { - - @Override - public boolean getScrollableTracksViewportWidth() { - Component parent = getParent(); - ComponentUI ui = getUI(); - - return true; - } - -} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 72c18ce..4deaa9c 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -6,7 +6,13 @@ import java.awt.Insets; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; +import java.io.File; import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collection; import java.util.Iterator; @@ -24,6 +30,7 @@ import javax.swing.UIManager; import javax.swing.BorderFactory; import javax.swing.JButton; +import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JSeparator; import javax.swing.JTabbedPane; @@ -31,6 +38,7 @@ import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; import javax.swing.JPanel; import java.awt.GridLayout; import java.awt.GridBagLayout; @@ -45,8 +53,8 @@ public class Window implements BatchListener, MouseWheelListener { } private JFrame frame; - private JTextField textField; - private JTextField textField_1; + private JTextField textFieldDestDir; + private JTextField textFieldSourceDir; private JSlider threadSlider; private JLabel lblThreads; private static Window window; @@ -55,12 +63,12 @@ public class Window implements BatchListener, MouseWheelListener { private final FixedCellGrid gridPanel = genGridPanel(); private JTable table; - private BatchRunner copyOperations; + private BatchRunner copyOperations; private BatchRunner processOperations; + /** - * Launch the application. - * @throws InterruptedException + * @wbp.parser.entryPoint */ public static void main(String[] args) throws InterruptedException { @@ -86,24 +94,21 @@ public Window() throws InterruptedException { } /** - * Initialize the contents of the frame. - * @throws InterruptedException + * @wbp.parser.entryPoint */ private void initialize() throws InterruptedException { - copyOperations = genCopyBatches(); - + copyOperations = genCopyBatches(null); setupFrame(); - setupTabbedPane(); } private void setupFrame() { - frame = new JFrame(); + frame = new JFrame(); // @wbp.parser.preferredRoot frame.setTitle("ExciteModder"); frame.setBounds(100, 100, 1000, 680); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().setLayout(null); - //frame.setResizable(false); + frame.setResizable(false); frame.setVisible(true); JLabel lblUnmoddedDirectory = new JLabel("Source Directory:"); @@ -116,19 +121,40 @@ private void setupFrame() { lblModdedDirectory.setBounds(12, 39, 165, 15); frame.getContentPane().add(lblModdedDirectory); - textField = new JTextField(); - textField.setBounds(171, 37, 578, 19); - frame.getContentPane().add(textField); - textField.setColumns(10); + textFieldSourceDir = new JTextField(); + textFieldSourceDir.setColumns(10); + textFieldSourceDir.setBounds(171, 10, 578, 19); + frame.getContentPane().add(textFieldSourceDir); - textField_1 = new JTextField(); - textField_1.setColumns(10); - textField_1.setBounds(171, 10, 578, 19); - frame.getContentPane().add(textField_1); + textFieldDestDir = new JTextField(); + textFieldDestDir.setBounds(171, 37, 578, 19); + frame.getContentPane().add(textFieldDestDir); + textFieldDestDir.setColumns(10); JButton btnChangeSource = new JButton("Change"); btnChangeSource.setToolTipText("Change where ExciteModder will copy the game files from"); btnChangeSource.setBounds(761, 9, 117, 19); + btnChangeSource.addActionListener((e) -> { + File f; + Path path = Path.of(textFieldSourceDir.getText().trim()).toAbsolutePath(); + if(Files.exists(path)) { + f = path.toAbsolutePath().toFile(); + } + else { + f = null; + } + + JFileChooser chooser = new JFileChooser(f); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int status = chooser.showOpenDialog(frame); + if(status == JFileChooser.APPROVE_OPTION) { + try { + selectSourceDirectory(chooser.getSelectedFile()); + } catch (InvocationTargetException | InterruptedException e1) { + e1.printStackTrace(); + } + } + }); frame.getContentPane().add(btnChangeSource); JButton btnChangeDest = new JButton("Change"); @@ -175,23 +201,40 @@ private void setupFrame() { frame.getContentPane().add(btnExtract); frame.validate(); frame.repaint(); - tabbedPane.addMouseWheelListener(this); + //tabbedPane = setupTabbedPane(true); + //frame.add(setupTabbedPane(true)); //this line is necessary here so that the wbp parser can see that the tabbed pane is a subcomponent of the frame. It doesn't seem to recognize it as being so if it isn't manually added here, even though it's added in setupTabbedPane } - private void setupTabbedPane() { - tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + private void selectSourceDirectory(File selectedDir) throws InvocationTargetException, InterruptedException { + if(selectedDir != null) { + textFieldSourceDir.setText(selectedDir.getAbsolutePath()); + } + else { + textFieldSourceDir.setText(""); + } - tabbedPane.setBounds(0, 129, 1000, 511); - frame.getContentPane().add(tabbedPane); + copyOperations.shutdownNow(); + copyOperations = genCopyBatches(selectedDir); + setupTabbedPane(false); + update(); + } + + private void setupTabbedPane(boolean initialSetup) { + tabbedPane.removeAll(); + if(initialSetup) { + tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + tabbedPane.setBounds(0, 129, 1000, 511); + frame.getContentPane().add(tabbedPane); + } setupConsoleOutputTab(); setupStatusTab(); setupProgressTab(); - for(Batcher b : copyOperations.getBatches()) { + for(Batcher b : copyOperations.getBatches()) { tabbedPane.addTab(b.getName(), null); } - + return; } private void setupConsoleOutputTab() { @@ -204,14 +247,22 @@ private void setupConsoleOutputTab() { System.setOut(new PrintStream(SplitOutputStream.splitSysOut(textPaneOutputStream))); System.setErr(new PrintStream(SplitOutputStream.splitErrOut(textPaneOutputStream))); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + new Throwable().printStackTrace(pw); + textArea.setText(sw.toString()); + JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - tabbedPane.addTab("Console Output", null, scrollPane, null); + tabbedPane.addTab("Console OutputT", null, scrollPane, null); + + + } public void setupStatusTab() { tabbedPane.addTab("Status", null, gridPanel, null); - Iterator batches = copyOperations.getBatches().iterator(); + Iterator> batches = copyOperations.getBatches().iterator(); int i = 0; while(batches.hasNext()) { gridPanel.putComponent(i, new BatchOperationComponent(batches.next())); @@ -231,14 +282,14 @@ public void setupProgressTab() { } private void setupLeftProgressPane(JPanel progressPanel) { - JPanel panel_1 = new JPanel(); - progressPanel.add(panel_1); - GridBagLayout gbl_panel_1 = new GridBagLayout(); - gbl_panel_1.columnWidths = new int[] {100, 0, 70, 90, 0, 0, 0}; - gbl_panel_1.rowHeights = new int[] {0, 15, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0}; - gbl_panel_1.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - gbl_panel_1.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - panel_1.setLayout(gbl_panel_1); + JPanel leftPanel = new JPanel(); + progressPanel.add(leftPanel); + GridBagLayout gbl_leftPanel = new GridBagLayout(); + gbl_leftPanel.columnWidths = new int[] {100, 0, 70, 90, 0, 0, 0}; + gbl_leftPanel.rowHeights = new int[] {0, 15, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0}; + gbl_leftPanel.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_leftPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + leftPanel.setLayout(gbl_leftPanel); JLabel lblCopyOperation = new JLabel("Copy Operation"); GridBagConstraints gbc_lblCopyOperation = new GridBagConstraints(); @@ -246,7 +297,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 5); gbc_lblCopyOperation.gridx = 0; gbc_lblCopyOperation.gridy = 0; - panel_1.add(lblCopyOperation, gbc_lblCopyOperation); + leftPanel.add(lblCopyOperation, gbc_lblCopyOperation); BatchOperationComponent allBatchesCopy = new BatchOperationComponent(copyOperations); allBatchesCopy.setToolTipText("All Batches"); GridBagConstraints gbc_allBatches = new GridBagConstraints(); @@ -255,7 +306,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_allBatches.insets = new Insets(0, 0, 0, 0); gbc_allBatches.gridx = 0; gbc_allBatches.gridy = 1; - panel_1.add(allBatchesCopy, gbc_allBatches); + leftPanel.add(allBatchesCopy, gbc_allBatches); JSeparator separator_1 = new JSeparator(); GridBagConstraints gbc_separator_1 = new GridBagConstraints(); @@ -264,7 +315,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_separator_1.insets = new Insets(0, 0, 5, 5); gbc_separator_1.gridx = 1; gbc_separator_1.gridy = 0; - panel_1.add(separator_1, gbc_separator_1); + leftPanel.add(separator_1, gbc_separator_1); JLabel lblTotalArchives = new JLabel("Total Archives:"); lblTotalArchives.setHorizontalAlignment(SwingConstants.CENTER); @@ -273,7 +324,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalArchives.anchor = GridBagConstraints.NORTHEAST; gbc_lblTotalArchives.gridx = 2; gbc_lblTotalArchives.gridy = 1; - panel_1.add(lblTotalArchives, gbc_lblTotalArchives); + leftPanel.add(lblTotalArchives, gbc_lblTotalArchives); JLabel lblTotalArchivesCount = new JLabel("0"); GridBagConstraints gbc_lblTotalArchivesCount = new GridBagConstraints(); @@ -281,7 +332,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalArchivesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalArchivesCount.gridx = 3; gbc_lblTotalArchivesCount.gridy = 1; - panel_1.add(lblTotalArchivesCount, gbc_lblTotalArchivesCount); + leftPanel.add(lblTotalArchivesCount, gbc_lblTotalArchivesCount); JLabel lblFoundResources = new JLabel("Total Resources:"); lblFoundResources.setHorizontalAlignment(SwingConstants.RIGHT); @@ -290,7 +341,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblFoundResources.insets = new Insets(0, 0, 5, 5); gbc_lblFoundResources.gridx = 2; gbc_lblFoundResources.gridy = 2; - panel_1.add(lblFoundResources, gbc_lblFoundResources); + leftPanel.add(lblFoundResources, gbc_lblFoundResources); JLabel lblTotalResourcesCount = new JLabel("0"); GridBagConstraints gbc_lblTotalResourcesCount = new GridBagConstraints(); @@ -298,7 +349,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalResourcesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalResourcesCount.gridx = 3; gbc_lblTotalResourcesCount.gridy = 2; - panel_1.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); + leftPanel.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); JLabel lblArchivesCopied = new JLabel("Archives Copied:"); GridBagConstraints gbc_lblArchivesCopied = new GridBagConstraints(); @@ -306,7 +357,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesCopied.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesCopied.gridx = 2; gbc_lblArchivesCopied.gridy = 4; - panel_1.add(lblArchivesCopied, gbc_lblArchivesCopied); + leftPanel.add(lblArchivesCopied, gbc_lblArchivesCopied); JLabel lblArchivesCopiedCount = new JLabel("0"); GridBagConstraints gbc_lblArchivesCopiedCount = new GridBagConstraints(); @@ -314,14 +365,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesCopiedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesCopiedCount.gridx = 3; gbc_lblArchivesCopiedCount.gridy = 4; - panel_1.add(lblArchivesCopiedCount, gbc_lblArchivesCopiedCount); + leftPanel.add(lblArchivesCopiedCount, gbc_lblArchivesCopiedCount); JLabel lblArchivesCopiedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesCopiedPercent = new GridBagConstraints(); gbc_lblArchivesCopiedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesCopiedPercent.gridx = 4; gbc_lblArchivesCopiedPercent.gridy = 4; - panel_1.add(lblArchivesCopiedPercent, gbc_lblArchivesCopiedPercent); + leftPanel.add(lblArchivesCopiedPercent, gbc_lblArchivesCopiedPercent); JLabel lblResourcesCopied = new JLabel("Resources Copied:"); GridBagConstraints gbc_lblResourcesCopied = new GridBagConstraints(); @@ -329,7 +380,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesCopied.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesCopied.gridx = 2; gbc_lblResourcesCopied.gridy = 5; - panel_1.add(lblResourcesCopied, gbc_lblResourcesCopied); + leftPanel.add(lblResourcesCopied, gbc_lblResourcesCopied); JLabel lblResourcesCopiedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesCopiedCount = new GridBagConstraints(); @@ -337,14 +388,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesCopiedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesCopiedCount.gridx = 3; gbc_lblResourcesCopiedCount.gridy = 5; - panel_1.add(lblResourcesCopiedCount, gbc_lblResourcesCopiedCount); + leftPanel.add(lblResourcesCopiedCount, gbc_lblResourcesCopiedCount); JLabel lblResourcesCopiedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesCopiedPercent = new GridBagConstraints(); gbc_lblResourcesCopiedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesCopiedPercent.gridx = 4; gbc_lblResourcesCopiedPercent.gridy = 5; - panel_1.add(lblResourcesCopiedPercent, gbc_lblResourcesCopiedPercent); + leftPanel.add(lblResourcesCopiedPercent, gbc_lblResourcesCopiedPercent); JLabel lblArchivesSkipped = new JLabel("Archives Skipped:"); lblArchivesSkipped.setHorizontalAlignment(SwingConstants.RIGHT); @@ -353,7 +404,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkipped.gridx = 2; gbc_lblArchivesSkipped.gridy = 7; - panel_1.add(lblArchivesSkipped, gbc_lblArchivesSkipped); + leftPanel.add(lblArchivesSkipped, gbc_lblArchivesSkipped); JLabel lblArchivesSkippedCount = new JLabel("0"); GridBagConstraints gbc_lblArchivesSkippedCount = new GridBagConstraints(); @@ -361,14 +412,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesSkippedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkippedCount.gridx = 3; gbc_lblArchivesSkippedCount.gridy = 7; - panel_1.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); + leftPanel.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); JLabel lblArchivesSkippedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesSkippedPercent = new GridBagConstraints(); gbc_lblArchivesSkippedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkippedPercent.gridx = 4; gbc_lblArchivesSkippedPercent.gridy = 7; - panel_1.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); + leftPanel.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); JLabel lblResourcesSkipped = new JLabel("Resources Skipped:"); GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); @@ -376,7 +427,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkipped.gridx = 2; gbc_lblResourcesSkipped.gridy = 8; - panel_1.add(lblResourcesSkipped, gbc_lblResourcesSkipped); + leftPanel.add(lblResourcesSkipped, gbc_lblResourcesSkipped); JLabel lblResourcesSkippedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesSkippedCount = new GridBagConstraints(); @@ -384,14 +435,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesSkippedCount.anchor = GridBagConstraints.WEST; gbc_lblResourcesSkippedCount.gridx = 3; gbc_lblResourcesSkippedCount.gridy = 8; - panel_1.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); + leftPanel.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); JLabel labelResourcesSkippedPercent = new JLabel("0%"); GridBagConstraints gbc_labelResourcesSkippedPercent = new GridBagConstraints(); gbc_labelResourcesSkippedPercent.insets = new Insets(0, 0, 5, 5); gbc_labelResourcesSkippedPercent.gridx = 4; gbc_labelResourcesSkippedPercent.gridy = 8; - panel_1.add(labelResourcesSkippedPercent, gbc_labelResourcesSkippedPercent); + leftPanel.add(labelResourcesSkippedPercent, gbc_labelResourcesSkippedPercent); JLabel lblArchivesFailed = new JLabel("Archives Failed:"); lblArchivesFailed.setHorizontalAlignment(SwingConstants.RIGHT); @@ -400,7 +451,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesFailed.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailed.gridx = 2; gbc_lblArchivesFailed.gridy = 10; - panel_1.add(lblArchivesFailed, gbc_lblArchivesFailed); + leftPanel.add(lblArchivesFailed, gbc_lblArchivesFailed); JLabel lblArchivesFailedCount = new JLabel("0"); GridBagConstraints gbc_lblArchivesFailedCount = new GridBagConstraints(); @@ -408,14 +459,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesFailedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailedCount.gridx = 3; gbc_lblArchivesFailedCount.gridy = 10; - panel_1.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); + leftPanel.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); JLabel lblArchivesFailedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesFailedPercent = new GridBagConstraints(); gbc_lblArchivesFailedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailedPercent.gridx = 4; gbc_lblArchivesFailedPercent.gridy = 10; - panel_1.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); + leftPanel.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); JLabel lblResourcesFailed = new JLabel("Resources Failed:"); GridBagConstraints gbc_lblResourcesFailed = new GridBagConstraints(); @@ -423,7 +474,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesFailed.insets = new Insets(0, 0, 0, 5); gbc_lblResourcesFailed.gridx = 2; gbc_lblResourcesFailed.gridy = 11; - panel_1.add(lblResourcesFailed, gbc_lblResourcesFailed); + leftPanel.add(lblResourcesFailed, gbc_lblResourcesFailed); JLabel lblResourcesFailedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesFailedCount = new GridBagConstraints(); @@ -431,25 +482,33 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 0, 5); gbc_lblResourcesFailedCount.gridx = 3; gbc_lblResourcesFailedCount.gridy = 11; - panel_1.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); + leftPanel.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); JLabel lblResourcesFailedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesFailedPercent = new GridBagConstraints(); gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 0, 5); gbc_lblResourcesFailedPercent.gridx = 4; gbc_lblResourcesFailedPercent.gridy = 11; - panel_1.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); + leftPanel.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); + + copyOperations.addBatchListener(() -> { + SwingUtilities.invokeLater(() -> { + + //Set all of the label text + + }); + }); } private void setupRightProgressPane(JPanel progressPanel) { - JPanel panel_2 = new JPanel(); - progressPanel.add(panel_2); - GridBagLayout gbl_panel_2 = new GridBagLayout(); - gbl_panel_2.columnWidths = new int[] {30, 0, 90, 0, 30, 30, 0, 0}; - gbl_panel_2.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - gbl_panel_2.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - gbl_panel_2.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - panel_2.setLayout(gbl_panel_2); + JPanel rightPanel = new JPanel(); + progressPanel.add(rightPanel); + GridBagLayout gbl_rightPanel = new GridBagLayout(); + gbl_rightPanel.columnWidths = new int[] {30, 0, 90, 0, 30, 30, 0, 0}; + gbl_rightPanel.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + gbl_rightPanel.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_rightPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + rightPanel.setLayout(gbl_rightPanel); JLabel lblNewLabel = new JLabel("Process Operation"); GridBagConstraints gbc_lblNewLabel = new GridBagConstraints(); @@ -457,7 +516,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblNewLabel.insets = new Insets(0, 0, 5, 5); gbc_lblNewLabel.gridx = 0; gbc_lblNewLabel.gridy = 0; - panel_2.add(lblNewLabel, gbc_lblNewLabel); + rightPanel.add(lblNewLabel, gbc_lblNewLabel); JLabel lblTotalArchives = new JLabel("Total Archives:"); GridBagConstraints gbc_lblTotalArchives = new GridBagConstraints(); @@ -465,7 +524,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblTotalArchives.insets = new Insets(0, 0, 5, 5); gbc_lblTotalArchives.gridx = 1; gbc_lblTotalArchives.gridy = 1; - panel_2.add(lblTotalArchives, gbc_lblTotalArchives); + rightPanel.add(lblTotalArchives, gbc_lblTotalArchives); JLabel lblTotalArchvesCount = new JLabel("0"); GridBagConstraints gbc_lblTotalArchvesCount = new GridBagConstraints(); @@ -473,7 +532,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblTotalArchvesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalArchvesCount.gridx = 2; gbc_lblTotalArchvesCount.gridy = 1; - panel_2.add(lblTotalArchvesCount, gbc_lblTotalArchvesCount); + rightPanel.add(lblTotalArchvesCount, gbc_lblTotalArchvesCount); JLabel lblTotalResources = new JLabel("Total Resources:"); GridBagConstraints gbc_lblTotalResources = new GridBagConstraints(); @@ -481,7 +540,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblTotalResources.insets = new Insets(0, 0, 5, 5); gbc_lblTotalResources.gridx = 1; gbc_lblTotalResources.gridy = 2; - panel_2.add(lblTotalResources, gbc_lblTotalResources); + rightPanel.add(lblTotalResources, gbc_lblTotalResources); JLabel lblTotalResourcesCount = new JLabel("0"); GridBagConstraints gbc_lblTotalResourcesCount = new GridBagConstraints(); @@ -489,7 +548,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblTotalResourcesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalResourcesCount.gridx = 2; gbc_lblTotalResourcesCount.gridy = 2; - panel_2.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); + rightPanel.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); JLabel lblArchivesProcessed = new JLabel("Archives Processed:"); GridBagConstraints gbc_lblArchivesProcessed = new GridBagConstraints(); @@ -497,7 +556,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblArchivesProcessed.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesProcessed.gridx = 1; gbc_lblArchivesProcessed.gridy = 4; - panel_2.add(lblArchivesProcessed, gbc_lblArchivesProcessed); + rightPanel.add(lblArchivesProcessed, gbc_lblArchivesProcessed); lblArchivesProcessed.setHorizontalAlignment(SwingConstants.RIGHT); JLabel lblArchivesProcessedCount = new JLabel("0"); @@ -506,14 +565,14 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblArchivesProcessedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesProcessedCount.gridx = 2; gbc_lblArchivesProcessedCount.gridy = 4; - panel_2.add(lblArchivesProcessedCount, gbc_lblArchivesProcessedCount); + rightPanel.add(lblArchivesProcessedCount, gbc_lblArchivesProcessedCount); JLabel lblArchivesProcessedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesProcessedPercent = new GridBagConstraints(); gbc_lblArchivesProcessedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesProcessedPercent.gridx = 3; gbc_lblArchivesProcessedPercent.gridy = 4; - panel_2.add(lblArchivesProcessedPercent, gbc_lblArchivesProcessedPercent); + rightPanel.add(lblArchivesProcessedPercent, gbc_lblArchivesProcessedPercent); JLabel lblResourcesProcessed = new JLabel("Resources Processed:"); GridBagConstraints gbc_lblResourcesProcessed = new GridBagConstraints(); @@ -521,7 +580,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesProcessed.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessed.gridx = 1; gbc_lblResourcesProcessed.gridy = 5; - panel_2.add(lblResourcesProcessed, gbc_lblResourcesProcessed); + rightPanel.add(lblResourcesProcessed, gbc_lblResourcesProcessed); JLabel lblResourcesProcessedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesProcessedCount = new GridBagConstraints(); @@ -529,14 +588,14 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesProcessedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessedCount.gridx = 2; gbc_lblResourcesProcessedCount.gridy = 5; - panel_2.add(lblResourcesProcessedCount, gbc_lblResourcesProcessedCount); + rightPanel.add(lblResourcesProcessedCount, gbc_lblResourcesProcessedCount); JLabel lblResourcesProcessedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesProcessedPercent = new GridBagConstraints(); gbc_lblResourcesProcessedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessedPercent.gridx = 3; gbc_lblResourcesProcessedPercent.gridy = 5; - panel_2.add(lblResourcesProcessedPercent, gbc_lblResourcesProcessedPercent); + rightPanel.add(lblResourcesProcessedPercent, gbc_lblResourcesProcessedPercent); JLabel lblArchivesSkipped = new JLabel("Archives Skipped:"); GridBagConstraints gbc_lblArchivesSkipped = new GridBagConstraints(); @@ -544,7 +603,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblArchivesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkipped.gridx = 1; gbc_lblArchivesSkipped.gridy = 7; - panel_2.add(lblArchivesSkipped, gbc_lblArchivesSkipped); + rightPanel.add(lblArchivesSkipped, gbc_lblArchivesSkipped); JLabel lblArchivesSkippedCount = new JLabel("0"); GridBagConstraints gbc_lblArchivesSkippedCount = new GridBagConstraints(); @@ -552,14 +611,14 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblArchivesSkippedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkippedCount.gridx = 2; gbc_lblArchivesSkippedCount.gridy = 7; - panel_2.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); + rightPanel.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); JLabel lblArchivesSkippedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesSkippedPercent = new GridBagConstraints(); gbc_lblArchivesSkippedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkippedPercent.gridx = 3; gbc_lblArchivesSkippedPercent.gridy = 7; - panel_2.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); + rightPanel.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); JLabel lblResourcesSkipped = new JLabel("Resources Skipped:"); GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); @@ -567,7 +626,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkipped.gridx = 1; gbc_lblResourcesSkipped.gridy = 8; - panel_2.add(lblResourcesSkipped, gbc_lblResourcesSkipped); + rightPanel.add(lblResourcesSkipped, gbc_lblResourcesSkipped); JLabel lblResourcesSkippedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesSkippedCount = new GridBagConstraints(); @@ -575,14 +634,14 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesSkippedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkippedCount.gridx = 2; gbc_lblResourcesSkippedCount.gridy = 8; - panel_2.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); + rightPanel.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); JLabel lblResourcesSkippedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesSkippedPercent = new GridBagConstraints(); gbc_lblResourcesSkippedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkippedPercent.gridx = 3; gbc_lblResourcesSkippedPercent.gridy = 8; - panel_2.add(lblResourcesSkippedPercent, gbc_lblResourcesSkippedPercent); + rightPanel.add(lblResourcesSkippedPercent, gbc_lblResourcesSkippedPercent); JLabel lblArchivesFailed = new JLabel("Archives Failed:"); GridBagConstraints gbc_lblArchivesFailed = new GridBagConstraints(); @@ -590,7 +649,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblArchivesFailed.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailed.gridx = 1; gbc_lblArchivesFailed.gridy = 10; - panel_2.add(lblArchivesFailed, gbc_lblArchivesFailed); + rightPanel.add(lblArchivesFailed, gbc_lblArchivesFailed); JLabel lblArchivesFailedCount = new JLabel("0"); GridBagConstraints gbc_lblArchivesFailedCount = new GridBagConstraints(); @@ -598,14 +657,14 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblArchivesFailedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailedCount.gridx = 2; gbc_lblArchivesFailedCount.gridy = 10; - panel_2.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); + rightPanel.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); JLabel lblArchivesFailedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesFailedPercent = new GridBagConstraints(); gbc_lblArchivesFailedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailedPercent.gridx = 3; gbc_lblArchivesFailedPercent.gridy = 10; - panel_2.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); + rightPanel.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); JLabel lblResourcesFailed = new JLabel("Resources Failed:"); GridBagConstraints gbc_lblResourcesFailed = new GridBagConstraints(); @@ -613,7 +672,7 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesFailed.insets = new Insets(0, 0, 0, 5); gbc_lblResourcesFailed.gridx = 1; gbc_lblResourcesFailed.gridy = 11; - panel_2.add(lblResourcesFailed, gbc_lblResourcesFailed); + rightPanel.add(lblResourcesFailed, gbc_lblResourcesFailed); JLabel lblResourcesFailedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesFailedCount = new GridBagConstraints(); @@ -621,21 +680,23 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 0, 5); gbc_lblResourcesFailedCount.gridx = 2; gbc_lblResourcesFailedCount.gridy = 11; - panel_2.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); + rightPanel.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); JLabel lblResourcesFailedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesFailedPercent = new GridBagConstraints(); gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 0, 5); gbc_lblResourcesFailedPercent.gridx = 3; gbc_lblResourcesFailedPercent.gridy = 11; - panel_2.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); + rightPanel.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); } - private BatchRunner genCopyBatches() { + private BatchRunner genCopyBatches(File dir) { BatchRunner batchRunner = new BatchRunner("Copy Operations"); - for(int i = 0; i < 10; i++) { - Batch b = new Batch("File " + i); - batchRunner.addBatch(b); + if(dir != null) { + for(File f : dir.listFiles()) { + Batch b = new Batch(f.getName()); + batchRunner.addBatch(b); + } } return batchRunner; } @@ -658,6 +719,8 @@ public void update() { @Override public void mouseWheelMoved(MouseWheelEvent e) { + System.out.println("Scrolled: " + e.getComponent()); + System.out.println("Child: " + e.getComponent().getComponentAt(e.getPoint())); if(e.getSource() instanceof JTabbedPane) { JTabbedPane pane = (JTabbedPane) e.getSource(); Component scrolledComponent = pane.getComponentAt(e.getPoint()); From a3e96a4aa09e594c89afda37600158fa1590fc0e Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sun, 5 May 2024 21:22:40 -0400 Subject: [PATCH 07/22] More progress --- .gitignore | 2 +- .../excite/modding/concurrent/Batch.java | 113 ++- .../modding/concurrent/BatchContainer.java | 4 +- .../modding/concurrent/BatchRunner.java | 14 +- .../modding/concurrent/BatchWorker.java | 4 +- .../excite/modding/concurrent/Batcher.java | 29 +- .../excite/modding/ui/CustomProgressBar.java | 18 + .../excite/modding/ui/EJTabbedPane.java | 167 ++++ .../excite/modding/ui/FixedCellGrid.java | 148 ---- .../excite/modding/ui/FixedCellSizeGrid.java | 101 --- .../excite/modding/ui/Window.java | 741 +++++++++--------- .../excite/modding/ui/WrapLayout.java | 192 +++++ .../unarchiver/QuickAccessArchive.java | 37 + .../excite/modding/unarchiver/Unarchiver.java | 110 ++- .../excite/modding/util/FileUtils.java | 43 + 15 files changed, 1043 insertions(+), 680 deletions(-) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/CustomProgressBar.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java delete mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java delete mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/WrapLayout.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java diff --git a/.gitignore b/.gitignore index f7fcb5a..af9628a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ build/tmp hs_err_pid*.log #Run -/run +/run* /backups # output diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java index 75d71b0..29f8eb8 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -11,7 +11,7 @@ public class Batch implements Batcher { private final String name; - private final Set runnables = new HashSet<>(); + private final Set> runnables = new HashSet<>(); private final LinkedHashSet listeners = new LinkedHashSet<>(); private volatile boolean accepting = true; @@ -27,8 +27,8 @@ public String getName() { @Override public void addRunnable(Callable runnable) { if(accepting) { - BatchedCallable b = new BatchedCallable(runnable); - runnables.add(new BatchedCallable(runnable)); + BatchedCallable b = new BatchedCallable<>(this, runnable); + runnables.add(new BatchedCallable<>(this, runnable)); updateListeners(); } else { @@ -39,8 +39,8 @@ public void addRunnable(Callable runnable) { @Override public void addRunnable(Runnable runnable) { if(accepting) { - BatchedCallable b = new BatchedCallable(runnable); - runnables.add(new BatchedCallable(runnable)); + BatchedCallable b = new BatchedCallable<>(this, runnable); + runnables.add(new BatchedCallable<>(this, runnable)); updateListeners(); } else { @@ -61,11 +61,12 @@ public void addListener(BatchListener listener) { } @Override - public Collection getRunnables() { + public Collection> getRunnables() { return Set.copyOf(runnables); } - + @Override + @SuppressWarnings("unchecked") public Collection getListeners() { return (Collection) listeners.clone(); } @@ -73,7 +74,7 @@ public Collection getListeners() { public void shutdownNow() throws InterruptedException { accepting = false; Shutdown shutdown = new Shutdown(); - for(BatchedCallable r : runnables) { + for(BatchedCallable r : runnables) { if(r.getState() == State.NEW) { r.shutdown(shutdown); } @@ -91,44 +92,98 @@ private void notAccepting() { throw new IllegalStateException("Batch is not accepting new tasks or listeners."); } - public class BatchedCallable implements Callable { + /** + * A wrapper class for a {@link Callable} object that participates in a batch execution managed by a {@link Batcher}. + * + * This class allows for the creation of callables that can be tracked and managed by a batching system. It provides methods + * to get the execution state, retrieve the result after completion, and handle exceptions. + * + * @param the type of the result produced by the wrapped {@link Callable} + */ + public static class BatchedCallable implements Callable { + private final Batcher batch; private final Callable child; private volatile SoftReference threadRef; protected volatile Throwable thrown; protected volatile boolean finished = false; + protected volatile T result; + - public BatchedCallable(Runnable r) { - this(() -> { + /** + * Creates a new BatchedCallable instance for a provided {@link Runnable} object. + *

+ * This convenience constructor takes a Runnable and converts it to a Callable that simply calls the {@link Runnable#run} method + * and returns null. It then delegates to the main constructor with the converted callable. + * + * @param batch the {@link Batcher} instance managing this callable + * @param r the {@link Runnable} object to be wrapped + */ + public BatchedCallable(Batcher batch, Runnable r) { + this(batch, () -> { r.run(); return null; }); } - public BatchedCallable(Callable c) { + /** + * Creates a new BatchedCallable instance for the provided {@link Callable} object. + *

+ * This constructor wraps a given Callable and associates it with the specified Batcher. The Batcher is + * notified of updates to the state of this callable. + * + * @param batch the {@link Batcher} instance managing this callable + * @param c the {@link Callable} object to be wrapped + */ + public BatchedCallable(Batcher batch, Callable c) { + this.batch = batch; this.child = c; - updateListeners(); + batch.updateListeners(); } + /** + * Implements the `call` method of the {@link Callable} interface. + *

+ * This method executes the wrapped `Callable` object and stores the result. It also updates the state of this object + * and notifies the associated Batcher before and after execution. If any exceptions occur during execution, they are + * stored but not re-thrown by this method. The caller of this method is responsible for handling any exceptions. + * + * @return the result of the wrapped callable's execution (which may be null), or null if an exception occurred + */ @Override public T call() { Thread thread; try { thread = Thread.currentThread(); threadRef = new SoftReference<>(thread); - updateListeners(); - child.call(); + batch.updateListeners(); + result = child.call(); + return result; } catch(Throwable t) { this.thrown = t; } finally { finished = true; - updateListeners(); + batch.updateListeners(); } return null; } + /** + * Gets the current execution state of the wrapped callable. + * + * This method examines the internal state and thread reference to determine the current execution state. It can return one of the following states: + * + *

    + *
  • NEW: The callable has not yet been submitted for execution. + *
  • TERMINATED: The callable has finished execution, either successfully or with an exception. + *
  • The actual state of the thread running the callable (e.g., `RUNNING`, `WAITING`): + *

    If a thread is currently executing the callable, this state reflects the thread's lifecycle. + *

+ * + * @return the current state of the callable execution, as described above + */ public State getState() { if(finished) { //the thread is no longer working on this runnable return State.TERMINATED; @@ -143,10 +198,36 @@ public State getState() { return thread.getState(); } + /** + * Retrieves the result of the computation after the {@link #call} method has finished executing. + * + * @throws IllegalStateException if the {@link #call} method has not yet finished executing. + * @return the result of the computation (which may be null), or {@code null} if the computation threw an exception. + */ + public T getResult() { + if(!finished) { + throw new IllegalStateException("Cannot obtain the result before it is calculated!"); + } + return result; + } + + /** + * Gets the exception thrown by the wrapped callable, if any. + *

+ * This method returns the exception that was thrown during the execution of the wrapped callable, or null if no exception + * occurred. + * + * @return the exception thrown by the wrapped callable, or null if no exception occurred + */ public Throwable getThrown() { return thrown; } + /** + * Sets the state of this callable to a proper shutdown state + * + * @param shutdown a dummy exception representing that this thread has failed to complete due to being shutdown. + */ protected void shutdown(Shutdown shutdown) { finished = true; thrown = shutdown; diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java index 51a6a10..59e2785 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java @@ -2,11 +2,13 @@ import java.util.Collection; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + public interface BatchContainer { public abstract String getName(); - public Collection.BatchedCallable> getRunnables(); + public Collection> getRunnables(); public Collection getListeners(); diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index 811a05c..ed0e104 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -6,6 +6,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + public class BatchRunner implements BatchWorker { private final String name; @@ -55,7 +57,9 @@ public void startBatch() throws InterruptedException { } synchronized(batches) { started = true; - executor.invokeAll(getRunnables()); + for(Batcher batch : batches) { + executor.invokeAll(batch.getRunnables()); + } } } } @@ -79,9 +83,9 @@ public void shutdownNow() throws InterruptedException { } @Override - public Collection.BatchedCallable> getRunnables() { + public Collection> getRunnables() { synchronized(batches) { - LinkedHashSet.BatchedCallable> ret = new LinkedHashSet<>(); + LinkedHashSet> ret = new LinkedHashSet<>(); for(Batcher batch : batches) { ret.addAll(batch.getRunnables()); } @@ -116,8 +120,8 @@ public Collection> getBatches() { public int getCompleted() { int ret = 0; - Collection.BatchedCallable> callables = getRunnables(); - for(Batch.BatchedCallable callable : callables) {ret++;} + Collection> callables = getRunnables(); + for(BatchedCallable callable : callables) {ret++;} return ret; } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java index aace9af..713b370 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java @@ -3,6 +3,8 @@ import java.util.Collection; import java.util.concurrent.TimeUnit; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + public interface BatchWorker extends BatchContainer { public abstract void addBatch(Batcher batcher); @@ -13,7 +15,7 @@ public interface BatchWorker extends BatchContainer { public void shutdownNow() throws InterruptedException; - public Collection.BatchedCallable> getRunnables(); + public Collection> getRunnables(); public Collection getListeners(); diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java index 0a3e795..bd2cb19 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java @@ -3,18 +3,45 @@ import java.util.Collection; import java.util.concurrent.Callable; +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; + public interface Batcher extends BatchContainer { public abstract void addRunnable(Callable runnable); public abstract void addRunnable(Runnable runnable); + @SuppressWarnings({ "rawtypes", "unchecked" }) + public default void addRunnables(Collection runnables) { + if(runnables.size() > 0) { + Object o = runnables.iterator().next(); + if(o instanceof Callable) { + runnables.forEach((r) -> { + addRunnable((Callable)r); + }); + } + else if (o instanceof Runnable) { + runnables.forEach((r) -> { + addRunnable((Runnable)r); + }); + } + else { + throw new IllegalArgumentException(o.getClass().toString()); + } + } + else { + return; + } + } + public abstract void addListener(BatchListener listener); public void shutdownNow() throws InterruptedException; - public Collection.BatchedCallable> getRunnables(); + public Collection> getRunnables(); public Collection getListeners(); + public void updateListeners(); + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/CustomProgressBar.java b/src/main/java/com/gamebuster19901/excite/modding/ui/CustomProgressBar.java new file mode 100644 index 0000000..8f77905 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/CustomProgressBar.java @@ -0,0 +1,18 @@ +package com.gamebuster19901.excite.modding.ui; + +import javax.swing.JProgressBar; + +public class CustomProgressBar extends JProgressBar { + + @Override + public String getString() { + if(this.getPercentComplete() <= 0d) { + return "Not Started"; + } + if(this.getPercentComplete() < 1d) { + return "Progress: " + ((int)(this.getPercentComplete() * 100)) + "%"; + } + return "Complete"; + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java b/src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java new file mode 100644 index 0000000..b3a76e0 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java @@ -0,0 +1,167 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Component; + +import javax.swing.Icon; +import javax.swing.JTabbedPane; + +/** + * A custom tabbed pane component that extends {@link javax.swing.JTabbedPane} and provides additional functionalities for managing tabs. + * + * This class offers a convenience layer on top of the standard `JTabbedPane` by: + * * Encapsulating tab information within a {@link Tab} record. + * * Providing methods for adding and retrieving tabs using `Tab` objects. + * * Propagating `IndexOutOfBoundsException` for methods that interact with tabs to ensure consistent error handling. + * + * @see javax.swing.JTabbedPane + * @see Tab + */ +public class EJTabbedPane extends JTabbedPane { + + /** + * Constructs a new EJTabbedPane with the default tab placement (TOP). + */ + public EJTabbedPane() { + super(); + } + + /** + * Constructs a new EJTabbedPane with the specified tab placement. + * + * @param tabPlacement The placement of the tabs. This can be one of the following values from {@link javax.swing.JTabbedPane}: + * * {@link JTabbedPane#TOP} + * * {@link JTabbedPane#BOTTOM} + * * {@link JTabbedPane#LEFT} + * * {@link JTabbedPane#RIGHT} + * @throws IllegalArgumentException if the tabPlacement is not a valid value. + */ + public EJTabbedPane(int tabPlacement) throws IllegalArgumentException { + super(tabPlacement); + } + + /** + * Constructs a new EJTabbedPane with the specified tab placement and layout policy. + * + * @param tabPlacement The placement of the tabs. Refer to {@link #EJTabbedPane(int)} for valid values. + * @param tabLayoutPolicy The layout policy for the tabs. This can be one of the following values from {@link javax.swing.JTabbedPane}: + * * {@link JTabbedPane#WRAP_TAB_LAYOUT} + * * {@link JTabbedPane#SCROLL_TAB_LAYOUT} + * @throws IllegalArgumentException if the tabPlacement or tabLayoutPolicy is not a valid value. + */ + public EJTabbedPane(int tabPlacement, int tabLayoutPolicy) throws IllegalArgumentException { + super(tabPlacement, tabLayoutPolicy); + } + + /** + * Adds a new tab to the EJTabbedPane. + * + * This method extracts the title and component from the provided {@link Tab} object and calls the underlying `JTabbedPane` method to add the tab. + * + * @param tab The tab object containing the information for the new tab. + * @return The same `tab` object that was provided as input. + */ + public Tab addTab(Tab tab) { + this.addTab(tab.title, tab.icon, tab.component, tab.tip); + return tab; + } + + /** + * Inserts a new tab at the specified index within the EJTabbedPane. + * + * This method uses the information from the provided {@link Tab} object (title, icon, component, tooltip) to call the underlying `JTabbedPane` method for insertion. + * + * @param tab The tab object containing the information for the new tab. + * @param index The index at which to insert the tab. + * @return The same `tab` object that was provided as input. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + public Tab putTab(Tab tab, int index) throws IndexOutOfBoundsException { + this.insertTab(tab.title, tab.icon, tab.component, tab.tip, index); + return tab; + } + + /** + * Retrieves a {@link Tab} object from the EJTabbedPane by its title. + * + * This method first finds the index of the tab with the matching title and then calls the `getTab(int index)` method. + * + * @param title The title of the tab to retrieve. + * @return A `Tab` object representing the tab with the specified title, or null if the tab is not found. + */ + public Tab getTab(String title) throws IndexOutOfBoundsException { + int index = this.indexOfTab(title); + return getTab(index); + } + + /** + * Retrieves a {@link Tab} object from the EJTabbedPane by its index. + * + * This method extracts the title, icon, component, and tooltip from the underlying `JTabbedPane` at the specified index and creates a new `Tab` object with this information. + * + * @param index The index of the tab to retrieve. + * @return A `Tab` object representing the tab at the specified index, or null if the index is invalid. + * @throws IndexOutOfBoundsException if the index is invalid. + */ + public Tab getTab(int index) throws IndexOutOfBoundsException { + if(index < 0 || index > this.getComponentCount()) { + return null; + } + String title = this.getTitleAt(index); + Icon icon = this.getIconAt(index); + Component component = this.getComponentAt(index); + String tip = this.getToolTipTextAt(index); + return new Tab(title, icon, component, tip); + } + + public Tab getSelectedTab() { + return getTab(getSelectedIndex()); + } + + public int getIndex(Tab tab) { + if(tab.title != null) { + return this.indexOfTab(tab.title); + } + if(tab.icon != null) { + return this.indexOfTab(tab.icon); + } + throw new IllegalArgumentException("Tab has null title and null icon, cannot obtain index"); + } + + public void setSelectedTab(Tab tab) { + this.setSelectedIndex(getIndex(tab)); + this.invalidate(); + } + + @Override + protected void fireStateChanged() { + super.fireStateChanged(); + } + + /** + * A record that encapsulates information about a single tab. + */ + public static final record Tab(String title, Icon icon, Component component, String tip) { + + /** + * Constructs a new Tab object with the specified title and component. + * + * @param title The title of the tab. + * @param component The component to be displayed in the tab. + */ + public Tab(String title, Component component) { + this(title, null, component, null); + } + + /** + * Constructs a new Tab object with the specified title, icon, and component. + * + * @param title The title of the tab. + * @param icon The icon to be displayed on the tab. + * @param component The component to be displayed in the tab. + */ + public Tab(String title, Icon icon, Component component) { + this(title, icon, component, null); + } + } +} + diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java deleted file mode 100644 index 27c6fc7..0000000 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellGrid.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.gamebuster19901.excite.modding.ui; - -import java.awt.Component; -import java.util.Iterator; -import java.util.Map.Entry; -import java.util.NavigableSet; -import java.util.TreeMap; - -import javax.swing.JComponent; -import java.awt.*; - -public abstract class FixedCellGrid extends JComponent { - - protected final int padding; - protected final TreeMap components = new TreeMap<>(); - protected final NavigableSet navigator = components.navigableKeySet(); - - public FixedCellGrid(int padding) { - this.padding = padding; - } - - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - - components.forEach((index, component) -> { - component.setBounds(calculateCellX(index), calculateCellY(index), getCellWidth(), getCellHeight()); - }); - - } - - @Override - public void setPreferredSize(Dimension dimension) { - super.setPreferredSize(dimension); - } - - @Override - public void setBounds(int x, int y, int width, int height) { - super.setBounds(x, y, width, height); - this.setPreferredSize(new Dimension(width - x, height - y)); - } - - protected abstract void onGridUpdate(); - - protected abstract int calculateCellX(int index); - - protected abstract int getCellWidth(); - - protected abstract int calculateCellY(int index); - - protected abstract int getCellHeight(); - - public final Component getComponent(Integer index) { - return components.get(index); - } - - public final Component putComponent(Integer index, Component component) { - super.add(component); - System.out.println("Adding component " + component + " at index " + index); - return components.put(index, component); - } - - public final void removeComponent(Integer index) { - Component toRemove = getComponent(index); - if(toRemove != null) { - components.remove(index); //we want to remove from the grid directly since it will be much faster than the overridden FixedCellGrid.remove() - super.remove(toRemove); //Then use super to remove the component reference from swing - } - } - - @Override - public void removeAll() { - components.clear(); - super.removeAll(); - } - - public final int getLowestFreeCell() { - Integer prevIndex = 0; - Integer index = -1; - while(index != null) { - index = navigator.higher(index); - if(index == null) { - return components.size(); - } - if(index - prevIndex > 1) { //we've found a gap in the defined cells - return prevIndex + 1; - } - prevIndex = index; - } - - return components.size(); - } - - public final Component getFirstComponent() { - return components.firstEntry().getValue(); - } - - public final Component getLastComponent() { - return components.lastEntry().getValue(); - } - - public final TreeMap getComponentMap() { - return components; - } - - @Override - @Deprecated - public Component add(Component component) { - //super.add(component); - System.out.println("Adding component at index " + getLowestFreeCell()); - putComponent(getLowestFreeCell(), component); - return component; - } - - @Override - @Deprecated - public Component add(Component component, int index) { - putComponent(index, component); - return component; - } - - @Override - @Deprecated - public void remove(Component component) { - Iterator> set = components.entrySet().iterator(); - while(set.hasNext()) { - Entry e = set.next(); - if(e.getValue() == component) { - set.remove(); - break; - } - } - super.remove(component); - } - - @Override - @Deprecated - public void remove(int index) { - removeComponent(index); - } - - @Override - @Deprecated - public void add(Component component, Object constraints) { - add(component); // Ignore constraints, components are positioned based on the cell location and size - } - -} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java b/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java deleted file mode 100644 index 1c35c5a..0000000 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/FixedCellSizeGrid.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.gamebuster19901.excite.modding.ui; -import java.awt.*; - -public class FixedCellSizeGrid extends FixedCellGrid { - - private int cellWidth; - private int cellHeight; - private int scroll; - - public FixedCellSizeGrid(Dimension gridDimension, Dimension cellDimension) { - this(gridDimension, cellDimension, 0); - } - - public FixedCellSizeGrid(Dimension gridDimension, Dimension cellDimension, int padding) { - super(padding); - this.cellWidth = cellDimension.width; - this.cellHeight = cellDimension.height; - this.setPreferredSize(gridDimension); - try { - calculateCellX(1); - } - catch(ArithmeticException e) { - throw new IllegalArgumentException("Grid size is too small, it cannot hold any cells!", e); - } - } - - @Override - public void setPreferredSize(Dimension dimension) { - super.setPreferredSize(dimension); - onGridUpdate(); - } - - @Override - protected void onGridUpdate() { - components.forEach((index, component) -> { - component.setBounds(calculateCellX(index), calculateCellY(index), getCellWidth(), getCellHeight()); - }); - System.out.println(this.getPreferredSize()); - System.out.println(cellWidth); - invalidate(); - } - - protected void setCellSize(Dimension dimension) { - this.cellWidth = dimension.width; - this.cellHeight = dimension.height; - onGridUpdate(); - } - - @Override - protected int getCellWidth() { - return cellWidth; - } - - @Override - protected int getCellHeight() { - return cellHeight; - } - - @Override - protected int calculateCellX(int index) { - return ((((index % (this.getPreferredSize().width / (cellWidth + padding))) * (cellWidth + padding))) + (padding / 2)); - } - - private int calculateCellYRaw(int index) { - int rowIndex = index / (this.getPreferredSize().width / (cellWidth + padding)); - return (rowIndex * (cellHeight + padding)) / (cellHeight + padding); - } - - private int getVisibleRows() { - return this.getPreferredSize().height / (cellHeight + padding); - } - - private int getMaxScroll() { - return Math.max(0, calculateCellYRaw(components.size()) - getVisibleRows()); - } - - protected int calculateCellY(int index) { - int rowIndex = index / (this.getPreferredSize().width / (cellWidth + padding)); - return (rowIndex * (cellHeight + padding)) - (scroll * (cellHeight + padding)); - } - - public int getScroll() { - return scroll; - } - - public void scroll(int offset) { - int newScroll = scroll + offset; - System.out.println("Max scroll: " + getMaxScroll()); - if(newScroll < 0) { - scroll = 0; - } - else if(newScroll > getMaxScroll()) { - scroll = getMaxScroll(); - } - else { - scroll = newScroll; - } - onGridUpdate(); - } - -} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 4deaa9c..17d6b78 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -1,82 +1,78 @@ package com.gamebuster19901.excite.modding.ui; -import java.awt.Component; import java.awt.Dimension; import java.awt.EventQueue; + +import javax.swing.JFrame; + +import java.awt.GridBagLayout; +import java.awt.GridLayout; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; + +import java.awt.GridBagConstraints; import java.awt.Insets; -import java.awt.event.MouseWheelEvent; -import java.awt.event.MouseWheelListener; import java.io.File; +import java.io.FileNotFoundException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; -import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; +import java.nio.file.NotDirectoryException; import java.nio.file.Path; -import java.util.Collection; import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.concurrent.Callable; -import javax.swing.JFrame; +import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; + +import com.gamebuster19901.excite.modding.concurrent.Batch; import com.gamebuster19901.excite.modding.concurrent.BatchListener; import com.gamebuster19901.excite.modding.concurrent.BatchRunner; import com.gamebuster19901.excite.modding.concurrent.Batcher; +import com.gamebuster19901.excite.modding.ui.EJTabbedPane.Tab; +import com.gamebuster19901.excite.modding.unarchiver.Unarchiver; +import com.gamebuster19901.excite.modding.util.FileUtils; import com.gamebuster19901.excite.modding.util.SplitOutputStream; -import com.gamebuster19901.excite.modding.concurrent.Batch; -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; -import com.gamebuster19901.excite.modding.concurrent.BatchContainer; -import javax.swing.JTextField; -import javax.swing.ScrollPaneConstants; -import javax.swing.UIManager; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFileChooser; -import javax.swing.JLabel; import javax.swing.JSeparator; -import javax.swing.JTabbedPane; import javax.swing.JSlider; import javax.swing.JProgressBar; import javax.swing.JScrollPane; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; import javax.swing.JPanel; -import java.awt.GridLayout; -import java.awt.GridBagLayout; -import java.awt.GridBagConstraints; -import javax.swing.JTable; +import javax.swing.JTabbedPane; import javax.swing.JTextArea; -public class Window implements BatchListener, MouseWheelListener { - - static { - UIManager.getDefaults().put("TabbedPane.contentBorderInsets", new Insets(0,0,0,0)); - } +public class Window implements BatchListener { + private static final String CONSOLE = "Console Output"; + private static final String STATUS = "Status"; + private static final String PROGRESS = "Progress"; + private JFrame frame; - private JTextField textFieldDestDir; - private JTextField textFieldSourceDir; - private JSlider threadSlider; - private JLabel lblThreads; - private static Window window; - private final JProgressBar progressBar = new JProgressBar(); - private final JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP); - private final FixedCellGrid gridPanel = genGridPanel(); - private JTable table; + private JTextField textFieldSource; + private JTextField textFieldDest; - private BatchRunner copyOperations; - private BatchRunner processOperations; - + private BatchRunner> copyOperations; + private BatchRunner processOperations; /** - * @wbp.parser.entryPoint + * Launch the application. */ - public static void main(String[] args) throws InterruptedException { - + public static void main(String[] args) { + Window window; EventQueue.invokeLater(new Runnable() { public void run() { try { Window window = new Window(); - Window.window = window; + window.frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } @@ -86,57 +82,142 @@ public void run() { /** * Create the application. - * @throws InterruptedException */ - public Window() throws InterruptedException { + public Window() { initialize(); - update(); } /** - * @wbp.parser.entryPoint + * Initialize the contents of the frame. */ - private void initialize() throws InterruptedException { + private void initialize() { + copyOperations = genCopyBatches(null, null); + processOperations = genProcessBatches(null); - copyOperations = genCopyBatches(null); - setupFrame(); - } - - private void setupFrame() { - frame = new JFrame(); // @wbp.parser.preferredRoot - frame.setTitle("ExciteModder"); + frame = new JFrame(); frame.setBounds(100, 100, 1000, 680); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.getContentPane().setLayout(null); - frame.setResizable(false); - frame.setVisible(true); - - JLabel lblUnmoddedDirectory = new JLabel("Source Directory:"); - lblUnmoddedDirectory.setToolTipText("The directory of the unmodified, original ripped files."); - lblUnmoddedDirectory.setBounds(12, 12, 165, 15); - frame.getContentPane().add(lblUnmoddedDirectory); - - JLabel lblModdedDirectory = new JLabel("Destination Directory:"); - lblModdedDirectory.setToolTipText("Where ExciteModder will copy modified game files to."); - lblModdedDirectory.setBounds(12, 39, 165, 15); - frame.getContentPane().add(lblModdedDirectory); - - textFieldSourceDir = new JTextField(); - textFieldSourceDir.setColumns(10); - textFieldSourceDir.setBounds(171, 10, 578, 19); - frame.getContentPane().add(textFieldSourceDir); - - textFieldDestDir = new JTextField(); - textFieldDestDir.setBounds(171, 37, 578, 19); - frame.getContentPane().add(textFieldDestDir); - textFieldDestDir.setColumns(10); + GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWidths = new int[]{5, 0, 0, 0, 0, 0, 5, 0}; + gridBagLayout.rowHeights = new int[]{5, 0, 0, 5, 0, 0, 16, 0, 0}; + gridBagLayout.columnWeights = new double[]{0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, Double.MIN_VALUE}; + frame.getContentPane().setLayout(gridBagLayout); + + JLabel lblSourceDirectory = new JLabel("Source Directory"); + GridBagConstraints gbc_lblSourceDirectory = new GridBagConstraints(); + gbc_lblSourceDirectory.gridwidth = 2; + gbc_lblSourceDirectory.fill = GridBagConstraints.VERTICAL; + gbc_lblSourceDirectory.anchor = GridBagConstraints.EAST; + gbc_lblSourceDirectory.insets = new Insets(0, 0, 5, 5); + gbc_lblSourceDirectory.gridx = 1; + gbc_lblSourceDirectory.gridy = 1; + frame.getContentPane().add(lblSourceDirectory, gbc_lblSourceDirectory); + + textFieldSource = new JTextField(); + GridBagConstraints gbc_textFieldSource = new GridBagConstraints(); + gbc_textFieldSource.insets = new Insets(0, 0, 5, 5); + gbc_textFieldSource.fill = GridBagConstraints.BOTH; + gbc_textFieldSource.gridx = 3; + gbc_textFieldSource.gridy = 1; + frame.getContentPane().add(textFieldSource, gbc_textFieldSource); + textFieldSource.setColumns(10); JButton btnChangeSource = new JButton("Change"); - btnChangeSource.setToolTipText("Change where ExciteModder will copy the game files from"); - btnChangeSource.setBounds(761, 9, 117, 19); + GridBagConstraints gbc_btnChangeSource = new GridBagConstraints(); + gbc_btnChangeSource.fill = GridBagConstraints.VERTICAL; + gbc_btnChangeSource.insets = new Insets(0, 0, 5, 5); + gbc_btnChangeSource.gridx = 4; + gbc_btnChangeSource.gridy = 1; + frame.getContentPane().add(btnChangeSource, gbc_btnChangeSource); + + JButton btnExtract = new JButton("Extract!"); + GridBagConstraints gbc_btnExtract = new GridBagConstraints(); + gbc_btnExtract.insets = new Insets(0, 0, 5, 5); + gbc_btnExtract.fill = GridBagConstraints.VERTICAL; + gbc_btnExtract.gridheight = 2; + gbc_btnExtract.gridx = 5; + gbc_btnExtract.gridy = 1; + frame.getContentPane().add(btnExtract, gbc_btnExtract); + + JLabel lblDestinationDirectory = new JLabel("Destination Directory"); + GridBagConstraints gbc_lblDestinationDirectory = new GridBagConstraints(); + gbc_lblDestinationDirectory.gridwidth = 2; + gbc_lblDestinationDirectory.insets = new Insets(0, 0, 5, 5); + gbc_lblDestinationDirectory.anchor = GridBagConstraints.EAST; + gbc_lblDestinationDirectory.gridx = 1; + gbc_lblDestinationDirectory.gridy = 2; + frame.getContentPane().add(lblDestinationDirectory, gbc_lblDestinationDirectory); + + textFieldDest = new JTextField(); + GridBagConstraints gbc_textFieldDest = new GridBagConstraints(); + gbc_textFieldDest.insets = new Insets(0, 0, 5, 5); + gbc_textFieldDest.fill = GridBagConstraints.BOTH; + gbc_textFieldDest.gridx = 3; + gbc_textFieldDest.gridy = 2; + frame.getContentPane().add(textFieldDest, gbc_textFieldDest); + textFieldDest.setColumns(10); + + JButton btnChangeDest = new JButton("Change"); + GridBagConstraints gbc_btnChangeDest = new GridBagConstraints(); + gbc_btnChangeDest.insets = new Insets(0, 0, 5, 5); + gbc_btnChangeDest.gridx = 4; + gbc_btnChangeDest.gridy = 2; + frame.getContentPane().add(btnChangeDest, gbc_btnChangeDest); + + JSeparator separator = new JSeparator(); + separator.setPreferredSize(new Dimension(1,1)); + GridBagConstraints gbc_separator = new GridBagConstraints(); + gbc_separator.anchor = GridBagConstraints.SOUTH; + gbc_separator.fill = GridBagConstraints.HORIZONTAL; + gbc_separator.gridwidth = 5; + gbc_separator.insets = new Insets(0, 0, 5, 5); + gbc_separator.gridx = 1; + gbc_separator.gridy = 3; + frame.getContentPane().add(separator, gbc_separator); + + JLabel lblThreads = new JLabel("Threads: "); + GridBagConstraints gbc_lblThreads = new GridBagConstraints(); + gbc_lblThreads.anchor = GridBagConstraints.WEST; + gbc_lblThreads.insets = new Insets(0, 0, 5, 5); + gbc_lblThreads.gridx = 1; + gbc_lblThreads.gridy = 4; + frame.getContentPane().add(lblThreads, gbc_lblThreads); + + JSlider slider = new JSlider(); + slider.setSnapToTicks(true); + slider.setPreferredSize(new Dimension(77, 16)); + slider.setBorder(null); + GridBagConstraints gbc_slider = new GridBagConstraints(); + gbc_slider.fill = GridBagConstraints.HORIZONTAL; + gbc_slider.insets = new Insets(0, 0, 5, 5); + gbc_slider.gridx = 1; + gbc_slider.gridy = 5; + frame.getContentPane().add(slider, gbc_slider); + + JProgressBar progressBar = new CustomProgressBar(); + progressBar.setStringPainted(true); + GridBagConstraints gbc_progressBar = new GridBagConstraints(); + gbc_progressBar.fill = GridBagConstraints.HORIZONTAL; + gbc_progressBar.gridwidth = 5; + gbc_progressBar.insets = new Insets(0, 0, 5, 5); + gbc_progressBar.gridx = 1; + gbc_progressBar.gridy = 6; + frame.getContentPane().add(progressBar, gbc_progressBar); + + EJTabbedPane tabbedPane = new EJTabbedPane(JTabbedPane.TOP); + setupTabbedPane(tabbedPane); + GridBagConstraints gbc_tabbedPane = new GridBagConstraints(); + gbc_tabbedPane.gridwidth = 5; + gbc_tabbedPane.insets = new Insets(0, 0, 0, 5); + gbc_tabbedPane.fill = GridBagConstraints.BOTH; + gbc_tabbedPane.gridx = 1; + gbc_tabbedPane.gridy = 7; + frame.getContentPane().add(tabbedPane, gbc_tabbedPane); + btnChangeSource.addActionListener((e) -> { File f; - Path path = Path.of(textFieldSourceDir.getText().trim()).toAbsolutePath(); + Path path = Path.of(textFieldSource.getText().trim()).toAbsolutePath(); if(Files.exists(path)) { f = path.toAbsolutePath().toFile(); } @@ -149,95 +230,107 @@ private void setupFrame() { int status = chooser.showOpenDialog(frame); if(status == JFileChooser.APPROVE_OPTION) { try { - selectSourceDirectory(chooser.getSelectedFile()); - } catch (InvocationTargetException | InterruptedException e1) { + selectDirectories(tabbedPane, chooser.getSelectedFile(), new File(textFieldDest.getText())); + } catch (InterruptedException e1) { e1.printStackTrace(); } } }); - frame.getContentPane().add(btnChangeSource); - JButton btnChangeDest = new JButton("Change"); - btnChangeDest.setToolTipText("Change where ExciteModder will copy the game files to"); - btnChangeDest.setBounds(761, 36, 117, 19); - frame.getContentPane().add(btnChangeDest); - - threadSlider = new JSlider(); - threadSlider.getSnapToTicks(); - threadSlider.setMinimum(1); - threadSlider.setMaximum(Runtime.getRuntime().availableProcessors()); - threadSlider.setValue(Math.max(1, Runtime.getRuntime().availableProcessors() / 2)); - threadSlider.setBounds(5, 90, 92, 16); - threadSlider.addChangeListener((s) -> { - if(lblThreads != null) { - lblThreads.setText("Threads: " + threadSlider.getValue()); + btnChangeDest.addActionListener((e) -> { + File f; + Path path = Path.of(textFieldDest.getText().trim()).toAbsolutePath(); + if(Files.exists(path)) { + f = path.toAbsolutePath().toFile(); + } + else { + f = null; + } + + JFileChooser chooser = new JFileChooser(f); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + int status = chooser.showOpenDialog(frame); + if(status == JFileChooser.APPROVE_OPTION) { + try { + selectDirectories(tabbedPane, new File(textFieldSource.getText()), chooser.getSelectedFile()); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } } }); - frame.getContentPane().add(threadSlider); - - lblThreads = new JLabel("Threads: " + threadSlider.getValue()); - lblThreads.setBounds(12, 74, 132, 15); - frame.getContentPane().add(lblThreads); - - JSeparator separator = new JSeparator(); - separator.setBounds(12, 64, 971, 2); - frame.getContentPane().add(separator); - - progressBar.setVisible(false); - JLabel lblStatus = new JLabel("Progress: 0%"); - lblStatus.setHorizontalAlignment(SwingConstants.CENTER); - lblStatus.setBounds(440, 110, 120, 15); - lblStatus.setVisible(false); - - frame.getContentPane().add(lblStatus); - progressBar.setBounds(12, 110, 971, 15); - frame.getContentPane().add(progressBar); - - JButton btnExtract = new JButton("Extract!"); - btnExtract.setBounds(890, 9, 93, 45); - btnExtract.setEnabled(false); - - frame.getContentPane().add(btnExtract); - frame.validate(); - frame.repaint(); - //tabbedPane = setupTabbedPane(true); - //frame.add(setupTabbedPane(true)); //this line is necessary here so that the wbp parser can see that the tabbed pane is a subcomponent of the frame. It doesn't seem to recognize it as being so if it isn't manually added here, even though it's added in setupTabbedPane + btnExtract.addActionListener((e) -> { + try { + File folder = new File(textFieldDest.getText()); + if(folder.exists()) { + LinkedHashSet paths = FileUtils.getFilesRecursively(folder.toPath()); + if(!folder.isDirectory()) { + throw new NotDirectoryException(folder.getAbsolutePath().toString()); + } + if(paths.size() != 0) { + int result = JOptionPane.showOptionDialog(frame, "Are you sure? You will overwrite " + paths.size() + " files.", "Overwrite?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null); + if(result != 0) { + return; + } + } + } + + + + } + catch(Throwable t) { + throw new RuntimeException(t); + } + }); } - private void selectSourceDirectory(File selectedDir) throws InvocationTargetException, InterruptedException { - if(selectedDir != null) { - textFieldSourceDir.setText(selectedDir.getAbsolutePath()); + private void selectDirectories(EJTabbedPane pane, File sourceDir, File destDir) throws InterruptedException { + Tab tab = pane.getSelectedTab(); + if(sourceDir != null && !sourceDir.getPath().isBlank()) { + textFieldSource.setText(sourceDir.getAbsolutePath()); + } + else { + textFieldSource.setText(""); + } + if(destDir != null && !destDir.getPath().isBlank()) { + textFieldDest.setText(destDir.getAbsolutePath()); } else { - textFieldSourceDir.setText(""); + textFieldDest.setText(""); } copyOperations.shutdownNow(); - copyOperations = genCopyBatches(selectedDir); - setupTabbedPane(false); + copyOperations = genCopyBatches(sourceDir, destDir); + setupTabbedPane(pane); + pane.setSelectedTab(tab); + System.out.println("Set tab!"); update(); } - - private void setupTabbedPane(boolean initialSetup) { - tabbedPane.removeAll(); - if(initialSetup) { - tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - tabbedPane.setBounds(0, 129, 1000, 511); - frame.getContentPane().add(tabbedPane); - } + + public EJTabbedPane setupTabbedPane(EJTabbedPane tabbedPane) { + Tab consoleTab = setupConsoleOutputTab(tabbedPane); + Tab statusTab = setupStatusTab(tabbedPane); + Tab progressTab = setupProgressTab(tabbedPane); - setupConsoleOutputTab(); - setupStatusTab(); - setupProgressTab(); + tabbedPane.removeAll(); + tabbedPane.setTabLayoutPolicy(EJTabbedPane.SCROLL_TAB_LAYOUT); + tabbedPane.addTab(consoleTab); + tabbedPane.addTab(statusTab); + tabbedPane.addTab(progressTab); - for(Batcher b : copyOperations.getBatches()) { - tabbedPane.addTab(b.getName(), null); + for(Batcher> b : copyOperations.getBatches()) { + tabbedPane.addTab(b.getName(), new JPanel()); } - return; + + return tabbedPane; } - private void setupConsoleOutputTab() { + private Tab setupConsoleOutputTab(EJTabbedPane tabbedPane) { + Tab consoleTab = tabbedPane.getTab(CONSOLE); + if(consoleTab != null) { + return consoleTab; + } + JPanel consolePanel = new JPanel(); consolePanel.setLayout(new GridLayout(0, 2, 0, 0)); JTextArea textArea = new JTextArea(); @@ -252,61 +345,93 @@ private void setupConsoleOutputTab() { new Throwable().printStackTrace(pw); textArea.setText(sw.toString()); - JScrollPane scrollPane = new JScrollPane(textArea); - scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); - tabbedPane.addTab("Console OutputT", null, scrollPane, null); - - - + JScrollPane scrollPaneConsole = new JScrollPane(textArea); + scrollPaneConsole.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + Tab tab = new Tab(CONSOLE, null, scrollPaneConsole, null); + tabbedPane.addTab(tab.title(), tab.icon(), tab.component(), tab.tip()); //need to add this separately so the window builder can see it + return tab; } - public void setupStatusTab() { - tabbedPane.addTab("Status", null, gridPanel, null); - Iterator> batches = copyOperations.getBatches().iterator(); + private Tab setupStatusTab(EJTabbedPane tabbedPane) { + Tab statusTab = tabbedPane.getTab(STATUS); + + if(statusTab != null) { + //remove all + } + + JPanel statusGrid = new JPanel(new WrapLayout()); + JScrollPane statusGridScroller = new JScrollPane(statusGrid); + + Iterator>> batches = copyOperations.getBatches().iterator(); int i = 0; while(batches.hasNext()) { - gridPanel.putComponent(i, new BatchOperationComponent(batches.next())); + BatchOperationComponent b = new BatchOperationComponent(batches.next()); + b.setPreferredSize(new Dimension(150, 175)); + statusGrid.add(b); i++; } + + Tab tab = new Tab(STATUS, null, statusGridScroller, null); + tabbedPane.addTab(tab.title(), tab.icon(), tab.component(), tab.tip()); //need to add this separately so the window builder can see it + + + return tab; } - public void setupProgressTab() { + public Tab setupProgressTab(EJTabbedPane tabbedPane) { + Tab progressTab = tabbedPane.getTab(PROGRESS); + if(progressTab != null) { + return progressTab; + } + JPanel progressPanel = new JPanel(); - tabbedPane.addTab("Progress", null, progressPanel, null); + Tab tab = new Tab(PROGRESS, null, progressPanel, null); + tabbedPane.addTab(tab.title(), tab.icon(), tab.component(), tab.tip()); //need to add this separately so the window builder can see it progressPanel.setLayout(new GridLayout(0, 2, 0, 0)); setupLeftProgressPane(progressPanel); setupRightProgressPane(progressPanel); - + return tab; } private void setupLeftProgressPane(JPanel progressPanel) { JPanel leftPanel = new JPanel(); progressPanel.add(leftPanel); GridBagLayout gbl_leftPanel = new GridBagLayout(); - gbl_leftPanel.columnWidths = new int[] {100, 0, 70, 90, 0, 0, 0}; - gbl_leftPanel.rowHeights = new int[] {0, 15, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0}; + gbl_leftPanel.columnWidths = new int[] {100, 0, 70, 90, 0, 0, 11}; + gbl_leftPanel.rowHeights = new int[] {0, 15, 0, 0, 30, 0, 0, 30, 0, 0, 30, 0, 0, 29, 0}; gbl_leftPanel.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - gbl_leftPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_leftPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, Double.MIN_VALUE}; leftPanel.setLayout(gbl_leftPanel); - JLabel lblCopyOperation = new JLabel("Copy Operation"); + JLabel lblCopyOperation = new JLabel("Unarchive Operation"); GridBagConstraints gbc_lblCopyOperation = new GridBagConstraints(); gbc_lblCopyOperation.gridwidth = 6; gbc_lblCopyOperation.insets = new Insets(0, 0, 5, 5); gbc_lblCopyOperation.gridx = 0; gbc_lblCopyOperation.gridy = 0; leftPanel.add(lblCopyOperation, gbc_lblCopyOperation); - BatchOperationComponent allBatchesCopy = new BatchOperationComponent(copyOperations); - allBatchesCopy.setToolTipText("All Batches"); - GridBagConstraints gbc_allBatches = new GridBagConstraints(); - gbc_allBatches.fill = GridBagConstraints.BOTH; - gbc_allBatches.gridheight = 11; - gbc_allBatches.insets = new Insets(0, 0, 0, 0); - gbc_allBatches.gridx = 0; - gbc_allBatches.gridy = 1; - leftPanel.add(allBatchesCopy, gbc_allBatches); + + JSeparator separator = new JSeparator(); + separator.setOrientation(SwingConstants.VERTICAL); + separator.setPreferredSize(new Dimension(1, 1)); + GridBagConstraints gbc_separator = new GridBagConstraints(); + gbc_separator.gridheight = 14; + gbc_separator.fill = GridBagConstraints.VERTICAL; + gbc_separator.gridx = 6; + gbc_separator.gridy = 0; + leftPanel.add(separator, gbc_separator); + BatchOperationComponent copyBatchComponent = new BatchOperationComponent(copyOperations); + copyBatchComponent.setToolTipText("All Batches"); + + GridBagConstraints gbc_copyBatchComponent = new GridBagConstraints(); + gbc_copyBatchComponent.fill = GridBagConstraints.BOTH; + gbc_copyBatchComponent.gridheight = 13; + gbc_copyBatchComponent.insets = new Insets(0, 0, 0, 5); + gbc_copyBatchComponent.gridx = 0; + gbc_copyBatchComponent.gridy = 1; + leftPanel.add(copyBatchComponent, gbc_copyBatchComponent); JSeparator separator_1 = new JSeparator(); GridBagConstraints gbc_separator_1 = new GridBagConstraints(); @@ -323,7 +448,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalArchives.insets = new Insets(0, 0, 5, 5); gbc_lblTotalArchives.anchor = GridBagConstraints.NORTHEAST; gbc_lblTotalArchives.gridx = 2; - gbc_lblTotalArchives.gridy = 1; + gbc_lblTotalArchives.gridy = 2; leftPanel.add(lblTotalArchives, gbc_lblTotalArchives); JLabel lblTotalArchivesCount = new JLabel("0"); @@ -331,7 +456,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalArchivesCount.anchor = GridBagConstraints.WEST; gbc_lblTotalArchivesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalArchivesCount.gridx = 3; - gbc_lblTotalArchivesCount.gridy = 1; + gbc_lblTotalArchivesCount.gridy = 2; leftPanel.add(lblTotalArchivesCount, gbc_lblTotalArchivesCount); JLabel lblFoundResources = new JLabel("Total Resources:"); @@ -340,7 +465,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblFoundResources.anchor = GridBagConstraints.EAST; gbc_lblFoundResources.insets = new Insets(0, 0, 5, 5); gbc_lblFoundResources.gridx = 2; - gbc_lblFoundResources.gridy = 2; + gbc_lblFoundResources.gridy = 3; leftPanel.add(lblFoundResources, gbc_lblFoundResources); JLabel lblTotalResourcesCount = new JLabel("0"); @@ -348,7 +473,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblTotalResourcesCount.anchor = GridBagConstraints.WEST; gbc_lblTotalResourcesCount.insets = new Insets(0, 0, 5, 5); gbc_lblTotalResourcesCount.gridx = 3; - gbc_lblTotalResourcesCount.gridy = 2; + gbc_lblTotalResourcesCount.gridy = 3; leftPanel.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); JLabel lblArchivesCopied = new JLabel("Archives Copied:"); @@ -356,7 +481,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesCopied.anchor = GridBagConstraints.EAST; gbc_lblArchivesCopied.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesCopied.gridx = 2; - gbc_lblArchivesCopied.gridy = 4; + gbc_lblArchivesCopied.gridy = 5; leftPanel.add(lblArchivesCopied, gbc_lblArchivesCopied); JLabel lblArchivesCopiedCount = new JLabel("0"); @@ -364,22 +489,15 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesCopiedCount.anchor = GridBagConstraints.WEST; gbc_lblArchivesCopiedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesCopiedCount.gridx = 3; - gbc_lblArchivesCopiedCount.gridy = 4; + gbc_lblArchivesCopiedCount.gridy = 5; leftPanel.add(lblArchivesCopiedCount, gbc_lblArchivesCopiedCount); - JLabel lblArchivesCopiedPercent = new JLabel("0%"); - GridBagConstraints gbc_lblArchivesCopiedPercent = new GridBagConstraints(); - gbc_lblArchivesCopiedPercent.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesCopiedPercent.gridx = 4; - gbc_lblArchivesCopiedPercent.gridy = 4; - leftPanel.add(lblArchivesCopiedPercent, gbc_lblArchivesCopiedPercent); - JLabel lblResourcesCopied = new JLabel("Resources Copied:"); GridBagConstraints gbc_lblResourcesCopied = new GridBagConstraints(); gbc_lblResourcesCopied.anchor = GridBagConstraints.EAST; gbc_lblResourcesCopied.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesCopied.gridx = 2; - gbc_lblResourcesCopied.gridy = 5; + gbc_lblResourcesCopied.gridy = 6; leftPanel.add(lblResourcesCopied, gbc_lblResourcesCopied); JLabel lblResourcesCopiedCount = new JLabel("0"); @@ -387,14 +505,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesCopiedCount.anchor = GridBagConstraints.WEST; gbc_lblResourcesCopiedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesCopiedCount.gridx = 3; - gbc_lblResourcesCopiedCount.gridy = 5; + gbc_lblResourcesCopiedCount.gridy = 6; leftPanel.add(lblResourcesCopiedCount, gbc_lblResourcesCopiedCount); JLabel lblResourcesCopiedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesCopiedPercent = new GridBagConstraints(); gbc_lblResourcesCopiedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesCopiedPercent.gridx = 4; - gbc_lblResourcesCopiedPercent.gridy = 5; + gbc_lblResourcesCopiedPercent.gridy = 6; leftPanel.add(lblResourcesCopiedPercent, gbc_lblResourcesCopiedPercent); JLabel lblArchivesSkipped = new JLabel("Archives Skipped:"); @@ -403,7 +521,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesSkipped.anchor = GridBagConstraints.EAST; gbc_lblArchivesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkipped.gridx = 2; - gbc_lblArchivesSkipped.gridy = 7; + gbc_lblArchivesSkipped.gridy = 8; leftPanel.add(lblArchivesSkipped, gbc_lblArchivesSkipped); JLabel lblArchivesSkippedCount = new JLabel("0"); @@ -411,14 +529,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesSkippedCount.anchor = GridBagConstraints.WEST; gbc_lblArchivesSkippedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkippedCount.gridx = 3; - gbc_lblArchivesSkippedCount.gridy = 7; + gbc_lblArchivesSkippedCount.gridy = 8; leftPanel.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); JLabel lblArchivesSkippedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesSkippedPercent = new GridBagConstraints(); gbc_lblArchivesSkippedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesSkippedPercent.gridx = 4; - gbc_lblArchivesSkippedPercent.gridy = 7; + gbc_lblArchivesSkippedPercent.gridy = 8; leftPanel.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); JLabel lblResourcesSkipped = new JLabel("Resources Skipped:"); @@ -426,7 +544,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesSkipped.anchor = GridBagConstraints.EAST; gbc_lblResourcesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkipped.gridx = 2; - gbc_lblResourcesSkipped.gridy = 8; + gbc_lblResourcesSkipped.gridy = 9; leftPanel.add(lblResourcesSkipped, gbc_lblResourcesSkipped); JLabel lblResourcesSkippedCount = new JLabel("0"); @@ -434,14 +552,14 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblResourcesSkippedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkippedCount.anchor = GridBagConstraints.WEST; gbc_lblResourcesSkippedCount.gridx = 3; - gbc_lblResourcesSkippedCount.gridy = 8; + gbc_lblResourcesSkippedCount.gridy = 9; leftPanel.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); JLabel labelResourcesSkippedPercent = new JLabel("0%"); GridBagConstraints gbc_labelResourcesSkippedPercent = new GridBagConstraints(); gbc_labelResourcesSkippedPercent.insets = new Insets(0, 0, 5, 5); gbc_labelResourcesSkippedPercent.gridx = 4; - gbc_labelResourcesSkippedPercent.gridy = 8; + gbc_labelResourcesSkippedPercent.gridy = 9; leftPanel.add(labelResourcesSkippedPercent, gbc_labelResourcesSkippedPercent); JLabel lblArchivesFailed = new JLabel("Archives Failed:"); @@ -450,7 +568,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesFailed.anchor = GridBagConstraints.EAST; gbc_lblArchivesFailed.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailed.gridx = 2; - gbc_lblArchivesFailed.gridy = 10; + gbc_lblArchivesFailed.gridy = 11; leftPanel.add(lblArchivesFailed, gbc_lblArchivesFailed); JLabel lblArchivesFailedCount = new JLabel("0"); @@ -458,44 +576,42 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_lblArchivesFailedCount.anchor = GridBagConstraints.WEST; gbc_lblArchivesFailedCount.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailedCount.gridx = 3; - gbc_lblArchivesFailedCount.gridy = 10; + gbc_lblArchivesFailedCount.gridy = 11; leftPanel.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); JLabel lblArchivesFailedPercent = new JLabel("0%"); GridBagConstraints gbc_lblArchivesFailedPercent = new GridBagConstraints(); gbc_lblArchivesFailedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblArchivesFailedPercent.gridx = 4; - gbc_lblArchivesFailedPercent.gridy = 10; + gbc_lblArchivesFailedPercent.gridy = 11; leftPanel.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); JLabel lblResourcesFailed = new JLabel("Resources Failed:"); GridBagConstraints gbc_lblResourcesFailed = new GridBagConstraints(); gbc_lblResourcesFailed.anchor = GridBagConstraints.EAST; - gbc_lblResourcesFailed.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailed.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesFailed.gridx = 2; - gbc_lblResourcesFailed.gridy = 11; + gbc_lblResourcesFailed.gridy = 12; leftPanel.add(lblResourcesFailed, gbc_lblResourcesFailed); JLabel lblResourcesFailedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesFailedCount = new GridBagConstraints(); gbc_lblResourcesFailedCount.anchor = GridBagConstraints.WEST; - gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesFailedCount.gridx = 3; - gbc_lblResourcesFailedCount.gridy = 11; + gbc_lblResourcesFailedCount.gridy = 12; leftPanel.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); JLabel lblResourcesFailedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesFailedPercent = new GridBagConstraints(); - gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesFailedPercent.gridx = 4; - gbc_lblResourcesFailedPercent.gridy = 11; + gbc_lblResourcesFailedPercent.gridy = 12; leftPanel.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); copyOperations.addBatchListener(() -> { SwingUtilities.invokeLater(() -> { - - //Set all of the label text - + update(); }); }); } @@ -504,12 +620,23 @@ private void setupRightProgressPane(JPanel progressPanel) { JPanel rightPanel = new JPanel(); progressPanel.add(rightPanel); GridBagLayout gbl_rightPanel = new GridBagLayout(); - gbl_rightPanel.columnWidths = new int[] {30, 0, 90, 0, 30, 30, 0, 0}; - gbl_rightPanel.rowHeights = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + gbl_rightPanel.columnWidths = new int[] {0, 0, 90, 0, 30, 30, 0, 0}; + gbl_rightPanel.rowHeights = new int[]{0, 30, 0, 30, 0, 0, 0, 30, 30, 30, 30, 0, 0, 0, 0}; gbl_rightPanel.columnWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; - gbl_rightPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.MIN_VALUE}; + gbl_rightPanel.rowWeights = new double[]{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, Double.MIN_VALUE}; rightPanel.setLayout(gbl_rightPanel); + BatchOperationComponent copyBatchComponent = new BatchOperationComponent(processOperations); + copyBatchComponent.setToolTipText("All Batches"); + + GridBagConstraints gbc_copyBatchComponent = new GridBagConstraints(); + gbc_copyBatchComponent.fill = GridBagConstraints.BOTH; + gbc_copyBatchComponent.gridheight = 13; + gbc_copyBatchComponent.insets = new Insets(0, 0, 5, 5); + gbc_copyBatchComponent.gridx = 0; + gbc_copyBatchComponent.gridy = 1; + rightPanel.add(copyBatchComponent, gbc_copyBatchComponent); + JLabel lblNewLabel = new JLabel("Process Operation"); GridBagConstraints gbc_lblNewLabel = new GridBagConstraints(); gbc_lblNewLabel.gridwidth = 7; @@ -518,22 +645,6 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblNewLabel.gridy = 0; rightPanel.add(lblNewLabel, gbc_lblNewLabel); - JLabel lblTotalArchives = new JLabel("Total Archives:"); - GridBagConstraints gbc_lblTotalArchives = new GridBagConstraints(); - gbc_lblTotalArchives.anchor = GridBagConstraints.EAST; - gbc_lblTotalArchives.insets = new Insets(0, 0, 5, 5); - gbc_lblTotalArchives.gridx = 1; - gbc_lblTotalArchives.gridy = 1; - rightPanel.add(lblTotalArchives, gbc_lblTotalArchives); - - JLabel lblTotalArchvesCount = new JLabel("0"); - GridBagConstraints gbc_lblTotalArchvesCount = new GridBagConstraints(); - gbc_lblTotalArchvesCount.anchor = GridBagConstraints.WEST; - gbc_lblTotalArchvesCount.insets = new Insets(0, 0, 5, 5); - gbc_lblTotalArchvesCount.gridx = 2; - gbc_lblTotalArchvesCount.gridy = 1; - rightPanel.add(lblTotalArchvesCount, gbc_lblTotalArchvesCount); - JLabel lblTotalResources = new JLabel("Total Resources:"); GridBagConstraints gbc_lblTotalResources = new GridBagConstraints(); gbc_lblTotalResources.anchor = GridBagConstraints.EAST; @@ -550,36 +661,12 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblTotalResourcesCount.gridy = 2; rightPanel.add(lblTotalResourcesCount, gbc_lblTotalResourcesCount); - JLabel lblArchivesProcessed = new JLabel("Archives Processed:"); - GridBagConstraints gbc_lblArchivesProcessed = new GridBagConstraints(); - gbc_lblArchivesProcessed.anchor = GridBagConstraints.EAST; - gbc_lblArchivesProcessed.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesProcessed.gridx = 1; - gbc_lblArchivesProcessed.gridy = 4; - rightPanel.add(lblArchivesProcessed, gbc_lblArchivesProcessed); - lblArchivesProcessed.setHorizontalAlignment(SwingConstants.RIGHT); - - JLabel lblArchivesProcessedCount = new JLabel("0"); - GridBagConstraints gbc_lblArchivesProcessedCount = new GridBagConstraints(); - gbc_lblArchivesProcessedCount.anchor = GridBagConstraints.WEST; - gbc_lblArchivesProcessedCount.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesProcessedCount.gridx = 2; - gbc_lblArchivesProcessedCount.gridy = 4; - rightPanel.add(lblArchivesProcessedCount, gbc_lblArchivesProcessedCount); - - JLabel lblArchivesProcessedPercent = new JLabel("0%"); - GridBagConstraints gbc_lblArchivesProcessedPercent = new GridBagConstraints(); - gbc_lblArchivesProcessedPercent.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesProcessedPercent.gridx = 3; - gbc_lblArchivesProcessedPercent.gridy = 4; - rightPanel.add(lblArchivesProcessedPercent, gbc_lblArchivesProcessedPercent); - JLabel lblResourcesProcessed = new JLabel("Resources Processed:"); GridBagConstraints gbc_lblResourcesProcessed = new GridBagConstraints(); gbc_lblResourcesProcessed.anchor = GridBagConstraints.EAST; gbc_lblResourcesProcessed.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessed.gridx = 1; - gbc_lblResourcesProcessed.gridy = 5; + gbc_lblResourcesProcessed.gridy = 4; rightPanel.add(lblResourcesProcessed, gbc_lblResourcesProcessed); JLabel lblResourcesProcessedCount = new JLabel("0"); @@ -587,45 +674,22 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesProcessedCount.anchor = GridBagConstraints.WEST; gbc_lblResourcesProcessedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessedCount.gridx = 2; - gbc_lblResourcesProcessedCount.gridy = 5; + gbc_lblResourcesProcessedCount.gridy = 4; rightPanel.add(lblResourcesProcessedCount, gbc_lblResourcesProcessedCount); JLabel lblResourcesProcessedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesProcessedPercent = new GridBagConstraints(); gbc_lblResourcesProcessedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesProcessedPercent.gridx = 3; - gbc_lblResourcesProcessedPercent.gridy = 5; + gbc_lblResourcesProcessedPercent.gridy = 4; rightPanel.add(lblResourcesProcessedPercent, gbc_lblResourcesProcessedPercent); - JLabel lblArchivesSkipped = new JLabel("Archives Skipped:"); - GridBagConstraints gbc_lblArchivesSkipped = new GridBagConstraints(); - gbc_lblArchivesSkipped.anchor = GridBagConstraints.EAST; - gbc_lblArchivesSkipped.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesSkipped.gridx = 1; - gbc_lblArchivesSkipped.gridy = 7; - rightPanel.add(lblArchivesSkipped, gbc_lblArchivesSkipped); - - JLabel lblArchivesSkippedCount = new JLabel("0"); - GridBagConstraints gbc_lblArchivesSkippedCount = new GridBagConstraints(); - gbc_lblArchivesSkippedCount.anchor = GridBagConstraints.WEST; - gbc_lblArchivesSkippedCount.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesSkippedCount.gridx = 2; - gbc_lblArchivesSkippedCount.gridy = 7; - rightPanel.add(lblArchivesSkippedCount, gbc_lblArchivesSkippedCount); - - JLabel lblArchivesSkippedPercent = new JLabel("0%"); - GridBagConstraints gbc_lblArchivesSkippedPercent = new GridBagConstraints(); - gbc_lblArchivesSkippedPercent.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesSkippedPercent.gridx = 3; - gbc_lblArchivesSkippedPercent.gridy = 7; - rightPanel.add(lblArchivesSkippedPercent, gbc_lblArchivesSkippedPercent); - JLabel lblResourcesSkipped = new JLabel("Resources Skipped:"); GridBagConstraints gbc_lblResourcesSkipped = new GridBagConstraints(); gbc_lblResourcesSkipped.anchor = GridBagConstraints.EAST; gbc_lblResourcesSkipped.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkipped.gridx = 1; - gbc_lblResourcesSkipped.gridy = 8; + gbc_lblResourcesSkipped.gridy = 5; rightPanel.add(lblResourcesSkipped, gbc_lblResourcesSkipped); JLabel lblResourcesSkippedCount = new JLabel("0"); @@ -633,134 +697,79 @@ private void setupRightProgressPane(JPanel progressPanel) { gbc_lblResourcesSkippedCount.anchor = GridBagConstraints.WEST; gbc_lblResourcesSkippedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkippedCount.gridx = 2; - gbc_lblResourcesSkippedCount.gridy = 8; + gbc_lblResourcesSkippedCount.gridy = 5; rightPanel.add(lblResourcesSkippedCount, gbc_lblResourcesSkippedCount); JLabel lblResourcesSkippedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesSkippedPercent = new GridBagConstraints(); gbc_lblResourcesSkippedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesSkippedPercent.gridx = 3; - gbc_lblResourcesSkippedPercent.gridy = 8; + gbc_lblResourcesSkippedPercent.gridy = 5; rightPanel.add(lblResourcesSkippedPercent, gbc_lblResourcesSkippedPercent); - JLabel lblArchivesFailed = new JLabel("Archives Failed:"); - GridBagConstraints gbc_lblArchivesFailed = new GridBagConstraints(); - gbc_lblArchivesFailed.anchor = GridBagConstraints.EAST; - gbc_lblArchivesFailed.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesFailed.gridx = 1; - gbc_lblArchivesFailed.gridy = 10; - rightPanel.add(lblArchivesFailed, gbc_lblArchivesFailed); - - JLabel lblArchivesFailedCount = new JLabel("0"); - GridBagConstraints gbc_lblArchivesFailedCount = new GridBagConstraints(); - gbc_lblArchivesFailedCount.anchor = GridBagConstraints.WEST; - gbc_lblArchivesFailedCount.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesFailedCount.gridx = 2; - gbc_lblArchivesFailedCount.gridy = 10; - rightPanel.add(lblArchivesFailedCount, gbc_lblArchivesFailedCount); - - JLabel lblArchivesFailedPercent = new JLabel("0%"); - GridBagConstraints gbc_lblArchivesFailedPercent = new GridBagConstraints(); - gbc_lblArchivesFailedPercent.insets = new Insets(0, 0, 5, 5); - gbc_lblArchivesFailedPercent.gridx = 3; - gbc_lblArchivesFailedPercent.gridy = 10; - rightPanel.add(lblArchivesFailedPercent, gbc_lblArchivesFailedPercent); - JLabel lblResourcesFailed = new JLabel("Resources Failed:"); GridBagConstraints gbc_lblResourcesFailed = new GridBagConstraints(); gbc_lblResourcesFailed.anchor = GridBagConstraints.EAST; - gbc_lblResourcesFailed.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailed.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesFailed.gridx = 1; - gbc_lblResourcesFailed.gridy = 11; + gbc_lblResourcesFailed.gridy = 6; rightPanel.add(lblResourcesFailed, gbc_lblResourcesFailed); JLabel lblResourcesFailedCount = new JLabel("0"); GridBagConstraints gbc_lblResourcesFailedCount = new GridBagConstraints(); gbc_lblResourcesFailedCount.anchor = GridBagConstraints.WEST; - gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedCount.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesFailedCount.gridx = 2; - gbc_lblResourcesFailedCount.gridy = 11; + gbc_lblResourcesFailedCount.gridy = 6; rightPanel.add(lblResourcesFailedCount, gbc_lblResourcesFailedCount); JLabel lblResourcesFailedPercent = new JLabel("0%"); GridBagConstraints gbc_lblResourcesFailedPercent = new GridBagConstraints(); - gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 0, 5); + gbc_lblResourcesFailedPercent.insets = new Insets(0, 0, 5, 5); gbc_lblResourcesFailedPercent.gridx = 3; - gbc_lblResourcesFailedPercent.gridy = 11; + gbc_lblResourcesFailedPercent.gridy = 6; rightPanel.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); } - private BatchRunner genCopyBatches(File dir) { - BatchRunner batchRunner = new BatchRunner("Copy Operations"); - if(dir != null) { - for(File f : dir.listFiles()) { - Batch b = new Batch(f.getName()); - batchRunner.addBatch(b); + @SuppressWarnings({ "rawtypes", "unchecked" }) + private BatchRunner> genCopyBatches(File source, File dest) { + BatchRunner> batchRunner = new BatchRunner("Unarchive"); + try { + Unarchiver unarchiver = new Unarchiver(source.toPath(), dest.toPath()); + if(FileUtils.isDirectory(source) && FileUtils.isDirectory(dest)) { + for(Batch> batch : unarchiver.getCopyBatches()) { + batchRunner.addBatch(batch); + } } } + catch(Throwable t) { + Batch> errBatch = new Batch<>("Error"); + errBatch.addRunnable(() -> { + throw t; + }); + batchRunner.addBatch(errBatch); + } return batchRunner; } - private void setGridBatches(BatchContainer batch, FixedCellGrid grid) { - Collection batches = batch.getRunnables(); - for(int i = 0; i < batches.size(); i++) { - tabbedPane.addTab("File " + i, new BatchOperationComponent(batch)); + private BatchRunner genProcessBatches(File dir) { + BatchRunner batchRunner = new BatchRunner("Process"); + if(dir != null) { + //stuff } + return batchRunner; } - private BatchRunner getProcessBatches() { - throw new UnsupportedOperationException("Not yet implemented"); + public static enum BatchResult { + SUCCESS, + FAILURE, + SKIP } - + @Override public void update() { + frame.invalidate(); frame.repaint(); } - @Override - public void mouseWheelMoved(MouseWheelEvent e) { - System.out.println("Scrolled: " + e.getComponent()); - System.out.println("Child: " + e.getComponent().getComponentAt(e.getPoint())); - if(e.getSource() instanceof JTabbedPane) { - JTabbedPane pane = (JTabbedPane) e.getSource(); - Component scrolledComponent = pane.getComponentAt(e.getPoint()); - if(!(scrolledComponent instanceof FixedCellSizeGrid) && scrolledComponent.getParent() instanceof JTabbedPane) { - int units = e.getWheelRotation(); - System.out.println(units); - int oldIndex = pane.getSelectedIndex(); - int newIndex = oldIndex + units; - if(newIndex < 0) { - pane.setSelectedIndex(0); - } - else if (newIndex >= pane.getTabCount()) { - pane.setSelectedIndex(pane.getTabCount() - 1); - } - else { - pane.setSelectedIndex(newIndex); - } - } - else if(scrolledComponent instanceof FixedCellSizeGrid) { - FixedCellSizeGrid grid = (FixedCellSizeGrid) scrolledComponent; - grid.scroll(e.getWheelRotation()); - } - } - else { - //System.out.println(e.getSource()); - } - } - - private FixedCellGrid genGridPanel() { - FixedCellGrid gridPanel = new FixedCellSizeGrid(new Dimension(385, 385), new Dimension(100, 100), 0); - gridPanel.setVisible(true); - - return gridPanel; - } - - private int getWantedThreads() { - int ret = threadSlider.getValue(); - if(ret < 1) { - return 1; - } - return ret; - } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/WrapLayout.java b/src/main/java/com/gamebuster19901/excite/modding/ui/WrapLayout.java new file mode 100644 index 0000000..2260ef3 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/WrapLayout.java @@ -0,0 +1,192 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.*; +import javax.swing.JScrollPane; +import javax.swing.SwingUtilities; + +/** + * FlowLayout subclass that fully supports wrapping of components. + */ +public class WrapLayout extends FlowLayout +{ + private Dimension preferredLayoutSize; + + /** + * Constructs a new WrapLayout with a left + * alignment and a default 5-unit horizontal and vertical gap. + */ + public WrapLayout() + { + super(); + } + + /** + * Constructs a new FlowLayout with the specified + * alignment and a default 5-unit horizontal and vertical gap. + * The value of the alignment argument must be one of + * WrapLayout, WrapLayout, + * or WrapLayout. + * @param align the alignment value + */ + public WrapLayout(int align) + { + super(align); + } + + /** + * Creates a new flow layout manager with the indicated alignment + * and the indicated horizontal and vertical gaps. + *

+ * The value of the alignment argument must be one of + * WrapLayout, WrapLayout, + * or WrapLayout. + * @param align the alignment value + * @param hgap the horizontal gap between components + * @param vgap the vertical gap between components + */ + public WrapLayout(int align, int hgap, int vgap) + { + super(align, hgap, vgap); + } + + /** + * Returns the preferred dimensions for this layout given the + * visible components in the specified target container. + * @param target the component which needs to be laid out + * @return the preferred dimensions to lay out the + * subcomponents of the specified container + */ + @Override + public Dimension preferredLayoutSize(Container target) + { + return layoutSize(target, true); + } + + /** + * Returns the minimum dimensions needed to layout the visible + * components contained in the specified target container. + * @param target the component which needs to be laid out + * @return the minimum dimensions to lay out the + * subcomponents of the specified container + */ + @Override + public Dimension minimumLayoutSize(Container target) + { + Dimension minimum = layoutSize(target, false); + minimum.width -= (getHgap() + 1); + return minimum; + } + + /** + * Returns the minimum or preferred dimension needed to layout the target + * container. + * + * @param target target to get layout size for + * @param preferred should preferred size be calculated + * @return the dimension to layout the target container + */ + private Dimension layoutSize(Container target, boolean preferred) + { + synchronized (target.getTreeLock()) + { + // Each row must fit with the width allocated to the containter. + // When the container width = 0, the preferred width of the container + // has not yet been calculated so lets ask for the maximum. + + int targetWidth = target.getSize().width; + Container container = target; + + while (container.getSize().width == 0 && container.getParent() != null) + { + container = container.getParent(); + } + + targetWidth = container.getSize().width; + + if (targetWidth == 0) + targetWidth = Integer.MAX_VALUE; + + int hgap = getHgap(); + int vgap = getVgap(); + Insets insets = target.getInsets(); + int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); + int maxWidth = targetWidth - horizontalInsetsAndGap; + + // Fit components into the allowed width + + Dimension dim = new Dimension(0, 0); + int rowWidth = 0; + int rowHeight = 0; + + int nmembers = target.getComponentCount(); + + for (int i = 0; i < nmembers; i++) + { + Component m = target.getComponent(i); + + if (m.isVisible()) + { + Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); + + // Can't add the component to current row. Start a new row. + + if (rowWidth + d.width > maxWidth) + { + addRow(dim, rowWidth, rowHeight); + rowWidth = 0; + rowHeight = 0; + } + + // Add a horizontal gap for all components after the first + + if (rowWidth != 0) + { + rowWidth += hgap; + } + + rowWidth += d.width; + rowHeight = Math.max(rowHeight, d.height); + } + } + + addRow(dim, rowWidth, rowHeight); + + dim.width += horizontalInsetsAndGap; + dim.height += insets.top + insets.bottom + vgap * 2; + + // When using a scroll pane or the DecoratedLookAndFeel we need to + // make sure the preferred size is less than the size of the + // target containter so shrinking the container size works + // correctly. Removing the horizontal gap is an easy way to do this. + + Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); + + if (scrollPane != null && target.isValid()) + { + dim.width -= (hgap + 1); + } + + return dim; + } + } + + /* + * A new row has been completed. Use the dimensions of this row + * to update the preferred size for the container. + * + * @param dim update the width and height when appropriate + * @param rowWidth the width of the row to add + * @param rowHeight the height of the row to add + */ + private void addRow(Dimension dim, int rowWidth, int rowHeight) + { + dim.width = Math.max(dim.width, rowWidth); + + if (dim.height > 0) + { + dim.height += getVgap(); + } + + dim.height += rowHeight; + } +} \ No newline at end of file diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java new file mode 100644 index 0000000..f2a87ec --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java @@ -0,0 +1,37 @@ +package com.gamebuster19901.excite.modding.unarchiver; + +import java.io.IOException; +import java.nio.file.Path; + +public class QuickAccessArchive { + + private final Toc toc; + private final Path archivePath; + private volatile Archive archive; + private volatile boolean set; + + public QuickAccessArchive(Toc toc, Path archivePath) { + this.toc = toc; + this.archivePath = archivePath; + } + + public synchronized void setArchive() throws IOException { + if(set == false) { + try { + this.archive = new Archive(archivePath, toc); + } + catch(Throwable t) { + throw t; + } + finally { + set = true; //so we don't attempt to read the file multiple times if it fails + } + } + } + + public Archive getArchive() throws IOException { + setArchive(); + return archive; + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java index c4db3ac..7b7f96f 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java @@ -4,65 +4,80 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashSet; -import java.util.Scanner; +import java.util.List; +import java.util.concurrent.Callable; import java.util.stream.Stream; -import com.gamebuster19901.excite.modding.game.file.kaitai.TocMonster; +import com.gamebuster19901.excite.modding.concurrent.Batch; import com.gamebuster19901.excite.modding.game.file.kaitai.TocMonster.Details; public class Unarchiver { - private static final Path runDir = Path.of(".").resolve("run"); - private static final Path ripDir = runDir.resolve("rip"); + private final Path sourceDir; + private final Path destDir; public LinkedHashSet tocs = new LinkedHashSet<>(); public LinkedHashSet archives = new LinkedHashSet<>(); - - public void unarchiveDir(Path dir) throws IOException { - try(Stream fileStream = Files.walk(dir)) { - fileStream.filter(Files::isRegularFile).forEach((f) -> { - if(f.getFileName().toString().endsWith(".toc")) { - tocs.add(f); - } - else { - archives.add(f); - } - }); - } + + public Unarchiver(Path sourceDir, Path destDir) throws IOException { + this.sourceDir = sourceDir; + this.destDir = destDir; + refresh(); } - public void unarchive(Path tocFile) throws IOException { - TocMonster toc = TocMonster.fromFile(tocFile.toAbsolutePath().toString()); - ArrayList

details = toc.details(); - for(Details fileDetails : details) { - System.out.println(tocFile.getFileName() + "/" + fileDetails.name()); + public Collection>> getCopyBatches() { + LinkedHashSet>> batches = new LinkedHashSet<>(); + for(Path toc : tocs) { + batches.add(getCopyBatch(toc)); } - Archive archive = null; - for(Path archivePath : archives) { - if(getFileName(tocFile).equals(getFileName(archivePath))) { - archive = new Archive(archivePath, tocFile); - break; + return batches; + } + + public Batch> getCopyBatch(Path tocFile) { + Batch> batch = new Batch<>(tocFile.getFileName().toString()); + try { + Toc toc = new Toc(tocFile.toAbsolutePath()); + List
details = toc.getFiles(); + + final QuickAccessArchive archive = getArchive(toc); + for(Details resource : details) { + batch.addRunnable(() -> { + try { + String resourceName = resource.name(); + System.out.println(tocFile.getFileName() + "/" + resourceName); + archive.getArchive().getFile(resourceName).writeTo(destDir.resolve(resourceName)); + if(resource.name().endsWith("tex")) { + return () -> { + System.out.println("This is an example of processing a texture!"); + return null; + }; + } + } + catch(Throwable t) { + throw t; + } + return null; + }); } + } - if(archive == null) { - throw new FileNotFoundException("Resource file for toc " + tocFile); + catch(Throwable t) { + batch.addRunnable(() -> { + throw t; //let the batchrunner know that an error occurred + }); } - - archive.writeTo(ripDir); + return batch; } - @SuppressWarnings("resource") - public static void main(String[] args) throws IOException { - Unarchiver unarchiver = new Unarchiver(); - unarchiver.unarchiveDir(Path.of("./gameData")); - - for(Path toc : unarchiver.tocs) { - System.out.println("Unarchiving " + toc); - unarchiver.unarchive(toc); + private QuickAccessArchive getArchive(Toc toc) throws IOException { + for(Path archivePath : archives) { + if(getFileName(toc.getFile()).equals(getFileName(archivePath))) { + return new QuickAccessArchive(toc, archivePath); + } } - new Scanner(System.in).nextLine(); //wait to exit + throw new FileNotFoundException("Resource file for toc " + toc.getFile().getFileName()); } private static String getFileName(Path f) { @@ -71,4 +86,19 @@ private static String getFileName(Path f) { return (i == -1) ? fileName : fileName.substring(0, i); } + private void refresh() throws IOException { + tocs.clear(); + archives.clear(); + try(Stream fileStream = Files.walk(sourceDir)) { + fileStream.filter(Files::isRegularFile).forEach((f) -> { + if(f.getFileName().toString().endsWith(".toc")) { + tocs.add(f); + } + else { + archives.add(f); + } + }); + } + } + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java index 3923d4f..9216ad5 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java +++ b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java @@ -8,11 +8,15 @@ import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; public class FileUtils { @@ -120,4 +124,43 @@ public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOEx } } + public static LinkedHashSet getFilesRecursively(Path path) throws IOException { + LinkedHashSet paths = new LinkedHashSet<>(); + if (Files.exists(path)) { + Files.walkFileTree(path, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + paths.add(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (Files.isSymbolicLink(dir)) { + //skip symbolic links + return FileVisitResult.SKIP_SUBTREE; + } + return FileVisitResult.CONTINUE; + } + }); + } else { + System.out.println("The specified path does not exist: " + path); + } + return paths; + } + + public static String getFileName(Path f) { + String fileName = f.getFileName().toString(); + int i = fileName.lastIndexOf('.'); + return (i == -1) ? fileName : fileName.substring(0, i); + } + + public static boolean isDirectory(Path dir) { + return Files.isDirectory(dir) && !Files.isSymbolicLink(dir); + } + + public static boolean isDirectory(File dir) { + return isDirectory(dir.getAbsoluteFile().toPath()); + } + } From 89b0d6169850e2ff9191357e3b0cd5273b711bef Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Tue, 7 May 2024 16:32:11 -0400 Subject: [PATCH 08/22] improve concurrency --- .../excite/modding/concurrent/Batch.java | 5 + .../modding/concurrent/BatchContainer.java | 2 +- .../modding/concurrent/BatchRunner.java | 25 ++--- .../modding/ui/BatchOperationComponent.java | 11 +++ .../modding/ui/BatchedImageComponent.java | 7 ++ .../excite/modding/ui/TestWindow.java | 98 +++++++++++++++++++ .../excite/modding/ui/Window.java | 47 ++++++--- .../modding/unarchiver/ArchivedFile.java | 13 ++- .../unarchiver/QuickAccessArchive.java | 23 ++--- .../excite/modding/unarchiver/Unarchiver.java | 5 + 10 files changed, 191 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java index 29f8eb8..e995735 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -233,4 +233,9 @@ protected void shutdown(Shutdown shutdown) { thrown = shutdown; } } + + @Override + public void update() { + //NO-OP + } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java index 59e2785..4f507ae 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java @@ -4,7 +4,7 @@ import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; -public interface BatchContainer { +public interface BatchContainer extends BatchListener { public abstract String getName(); diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index ed0e104..4b7a264 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -84,24 +84,20 @@ public void shutdownNow() throws InterruptedException { @Override public Collection> getRunnables() { - synchronized(batches) { - LinkedHashSet> ret = new LinkedHashSet<>(); - for(Batcher batch : batches) { - ret.addAll(batch.getRunnables()); - } - return ret; + LinkedHashSet> ret = new LinkedHashSet<>(); + for(Batcher batch : batches) { + ret.addAll(batch.getRunnables()); } + return ret; } @Override public Collection getListeners() { - synchronized(batches) { - LinkedHashSet ret = new LinkedHashSet<>(); - for(Batcher batch : batches) { - ret.addAll(batch.getListeners()); - } - return ret; + LinkedHashSet ret = new LinkedHashSet<>(); + for(Batcher batch : batches) { + ret.addAll(batch.getListeners()); } + return ret; } @Override @@ -124,5 +120,10 @@ public int getCompleted() { for(BatchedCallable callable : callables) {ret++;} return ret; } + + @Override + public void update() { + //NO-OP + } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java index 94c7be9..3c34370 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java @@ -10,6 +10,7 @@ import javax.swing.JLabel; import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; public class BatchOperationComponent extends JPanel implements BatchContainer { @@ -54,5 +55,15 @@ public Collection getRunnables() { public Collection getListeners() { return batch.getListeners(); } + + @Override + public void update() { + SwingUtilities.invokeLater(() -> { + batch.update(); + revalidate(); + repaint(); + }); + + } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java index 6f834e6..31a3656 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java @@ -19,6 +19,7 @@ public BatchedImageComponent(BatchContainer batch) { @Override public Image getImage() { + System.out.println("Image"); int _new = 0; int working = 0; int success = 0; @@ -65,5 +66,11 @@ public Collection getRunnables() { public Collection getListeners() { return batch.getListeners(); } + + @Override + public void update() { + revalidate(); + repaint(); + } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java b/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java new file mode 100644 index 0000000..7e5d43f --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java @@ -0,0 +1,98 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Dimension; +import java.awt.EventQueue; + +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +import com.gamebuster19901.excite.modding.concurrent.Batch; +import com.gamebuster19901.excite.modding.concurrent.BatchRunner; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; + +public class TestWindow { + + private JFrame frame; + + /** + * Launch the application. + */ + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + TestWindow window = new TestWindow(); + window.frame.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + /** + * Create the application. + */ + public TestWindow() { + initialize(); + } + + /** + * Initialize the contents of the frame. + */ + private void initialize() { + System.out.println(Thread.currentThread()); + frame = new JFrame(); + frame.setBounds(100, 100, 450, 300); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + Batch batch = new Batch("Test"); + BatchRunner r = new BatchRunner("TestRunner"); + r.addBatch(batch); + BatchOperationComponent c = new BatchOperationComponent(r); + c.setPreferredSize(new Dimension(150, 175)); + for(int i = 0; i < 1000; i++) { + final int j = i; + batch.addRunnable(() -> { + try { + System.out.println(Thread.currentThread() + ": Ran " + j); + SwingUtilities.invokeLater(() -> { + c.update(); + System.out.println("Updated!"); + }); + + } catch (Throwable e) { + throw new RuntimeException(e); + } + }); + } + GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWidths = new int[]{450, 0}; + gridBagLayout.rowHeights = new int[]{263, 0}; + gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE}; + gridBagLayout.rowWeights = new double[]{1.0, Double.MIN_VALUE}; + frame.getContentPane().setLayout(gridBagLayout); + JPanel panel = new JPanel(); + + + panel.add(c); + panel.setLayout(new WrapLayout()); + GridBagConstraints gbc_panel = new GridBagConstraints(); + gbc_panel.fill = GridBagConstraints.BOTH; + gbc_panel.gridx = 0; + gbc_panel.gridy = 0; + frame.getContentPane().add(panel, gbc_panel); + frame.setVisible(true); + new Thread(() -> { + try { + Thread.sleep(500); + r.startBatch(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }).start(); + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 17d6b78..601723e 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -14,7 +14,7 @@ import java.awt.GridBagConstraints; import java.awt.Insets; import java.io.File; -import java.io.FileNotFoundException; +import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringWriter; @@ -60,8 +60,9 @@ public class Window implements BatchListener { private JTextField textFieldSource; private JTextField textFieldDest; - private BatchRunner> copyOperations; - private BatchRunner processOperations; + private volatile Unarchiver unarchiver; + private volatile BatchRunner> copyOperations; + private volatile BatchRunner processOperations; /** * Launch the application. @@ -89,9 +90,11 @@ public Window() { /** * Initialize the contents of the frame. + * @throws IOException */ private void initialize() { - copyOperations = genCopyBatches(null, null); + unarchiver = genUnarchiver(null, null); + copyOperations = genCopyBatches(); processOperations = genProcessBatches(null); frame = new JFrame(); @@ -268,15 +271,23 @@ private void initialize() { throw new NotDirectoryException(folder.getAbsolutePath().toString()); } if(paths.size() != 0) { - int result = JOptionPane.showOptionDialog(frame, "Are you sure? You will overwrite " + paths.size() + " files.", "Overwrite?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null); + int result = JOptionPane.showOptionDialog(frame, "Are you sure? This directory has " + paths.size() + " pre-existing files.\n\nDuplicates will be overridden", "Overwrite?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, null, null); if(result != 0) { return; } + + new Thread(() -> { + try { + copyOperations.startBatch(); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); + } + }).start(); + } } - } catch(Throwable t) { throw new RuntimeException(t); @@ -300,7 +311,8 @@ private void selectDirectories(EJTabbedPane pane, File sourceDir, File destDir) } copyOperations.shutdownNow(); - copyOperations = genCopyBatches(sourceDir, destDir); + unarchiver = genUnarchiver(sourceDir, destDir); + copyOperations = genCopyBatches(); setupTabbedPane(pane); pane.setSelectedTab(tab); System.out.println("Set tab!"); @@ -731,14 +743,25 @@ private void setupRightProgressPane(JPanel progressPanel) { rightPanel.add(lblResourcesFailedPercent, gbc_lblResourcesFailedPercent); } + private Unarchiver genUnarchiver(File source, File dest) { + try { + Unarchiver unarchiver = new Unarchiver(source.toPath(), dest.toPath()); + return unarchiver; + } + catch(Throwable t) { + return null; + } + } + @SuppressWarnings({ "rawtypes", "unchecked" }) - private BatchRunner> genCopyBatches(File source, File dest) { + private BatchRunner> genCopyBatches() { BatchRunner> batchRunner = new BatchRunner("Unarchive"); try { - Unarchiver unarchiver = new Unarchiver(source.toPath(), dest.toPath()); - if(FileUtils.isDirectory(source) && FileUtils.isDirectory(dest)) { - for(Batch> batch : unarchiver.getCopyBatches()) { - batchRunner.addBatch(batch); + if(unarchiver != null) { + if(unarchiver.isValid()) { + for(Batch> batch : unarchiver.getCopyBatches()) { + batchRunner.addBatch(batch); + } } } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java index aaa5f5a..f45e7d8 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/ArchivedFile.java @@ -12,7 +12,6 @@ public class ArchivedFile { private final TocMonster.Details fileDetails; private final Archive archive; - private final byte[] bytes; public ArchivedFile(TocMonster.Details fileDetails, Archive archive) { try { @@ -24,10 +23,9 @@ public ArchivedFile(TocMonster.Details fileDetails, Archive archive) { System.out.println("File end: " + ((int)fileDetails.fileOffset() + (int)fileDetails.fileSize())); byte[] bytes = archive.getBytes(); System.out.println("Array size: " + bytes.length); - this.bytes = Arrays.copyOfRange(archive.getBytes(), (int)fileDetails.fileOffset(), (int)(fileDetails.fileOffset() + (int)fileDetails.fileSize())); } catch(Throwable t) { - System.err.println("Could not extract resource " + getName() + " from " + archive.getArchiveFile().getFileName()); + System.err.println("Bad resource reference: " + getName() + " from " + archive.getArchiveFile().getFileName()); t.printStackTrace(); throw t; } @@ -37,8 +35,13 @@ public String getName() { return fileDetails.name(); } - public byte[] getBytes() { - return bytes; + public byte[] getBytes() throws IOException { + try { + return Arrays.copyOfRange(archive.getBytes(), (int)fileDetails.fileOffset(), (int)(fileDetails.fileOffset() + (int)fileDetails.fileSize())); + } + catch(Throwable t) { + throw new IOException(t); + } } public void writeTo(Path directory) throws IOException { diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java index f2a87ec..9faed06 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java @@ -3,34 +3,27 @@ import java.io.IOException; import java.nio.file.Path; +import org.apache.commons.lang3.concurrent.ConcurrentException; + public class QuickAccessArchive { private final Toc toc; private final Path archivePath; private volatile Archive archive; - private volatile boolean set; public QuickAccessArchive(Toc toc, Path archivePath) { this.toc = toc; this.archivePath = archivePath; } - public synchronized void setArchive() throws IOException { - if(set == false) { - try { - this.archive = new Archive(archivePath, toc); - } - catch(Throwable t) { - throw t; - } - finally { - set = true; //so we don't attempt to read the file multiple times if it fails + public Archive getArchive() throws IOException, ConcurrentException { + if(archive == null) { + synchronized(this) { + if(archive == null) { + archive = new Archive(archivePath, toc); + } } } - } - - public Archive getArchive() throws IOException { - setArchive(); return archive; } diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java index 7b7f96f..8a8b7fb 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java @@ -12,6 +12,7 @@ import com.gamebuster19901.excite.modding.concurrent.Batch; import com.gamebuster19901.excite.modding.game.file.kaitai.TocMonster.Details; +import com.gamebuster19901.excite.modding.util.FileUtils; public class Unarchiver { @@ -71,6 +72,10 @@ public Batch> getCopyBatch(Path tocFile) { return batch; } + public boolean isValid() { + return FileUtils.isDirectory(sourceDir) && FileUtils.isDirectory(destDir); + } + private QuickAccessArchive getArchive(Toc toc) throws IOException { for(Path archivePath : archives) { if(getFileName(toc.getFile()).equals(getFileName(archivePath))) { From 1f19e658fe0ad9b2db11c428ccdd6bf5e050efe6 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Wed, 15 May 2024 03:50:17 -0400 Subject: [PATCH 09/22] More progress --- .../excite/modding/concurrent/Batch.java | 7 +-- .../modding/concurrent/BatchContainer.java | 12 +++- .../modding/concurrent/BatchRunner.java | 10 +--- .../modding/concurrent/BatchWorker.java | 11 ---- .../excite/modding/concurrent/Batcher.java | 14 +---- .../modding/concurrent/BatcherContainer.java | 11 ++++ .../modding/ui/BatchOperationComponent.java | 60 +++++++++++-------- .../modding/ui/BatchedImageComponent.java | 23 +++++-- .../excite/modding/ui/EJTabbedPane.java | 3 +- .../excite/modding/ui/TestWindow.java | 49 ++++++++++----- .../unarchiver/QuickAccessArchive.java | 2 +- 11 files changed, 115 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/concurrent/BatcherContainer.java diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java index e995735..4a314e1 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -6,6 +6,7 @@ import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.Callable; + import static java.lang.Thread.State; public class Batch implements Batcher { @@ -49,7 +50,7 @@ public void addRunnable(Runnable runnable) { } @Override - public void addListener(BatchListener listener) { + public void addBatchListener(BatchListener listener) { if(accepting) { if(!listeners.add(listener)) { System.out.println("Warning: duplicate batch listener ignored."); @@ -234,8 +235,4 @@ protected void shutdown(Shutdown shutdown) { } } - @Override - public void update() { - //NO-OP - } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java index 4f507ae..01cc3a8 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java @@ -4,7 +4,7 @@ import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; -public interface BatchContainer extends BatchListener { +public interface BatchContainer { public abstract String getName(); @@ -12,4 +12,14 @@ public interface BatchContainer extends BatchListener { public Collection getListeners(); + public void addBatchListener(BatchListener listener); + + public default void updateListeners() { + for(BatchListener l : getListeners()) { + l.update(); + } + } + + public void shutdownNow() throws InterruptedException; + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index 4b7a264..40d3274 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -8,7 +8,7 @@ import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; -public class BatchRunner implements BatchWorker { +public class BatchRunner implements BatchWorker, BatcherContainer { private final String name; private final ExecutorService executor; @@ -106,10 +106,11 @@ public void addBatchListener(BatchListener listener) { listenerAdded = true; } for(Batcher batch : batches) { - batch.addListener(listener); + batch.addBatchListener(listener); } } + @Override public Collection> getBatches() { return (Collection>) batches.clone(); } @@ -120,10 +121,5 @@ public int getCompleted() { for(BatchedCallable callable : callables) {ret++;} return ret; } - - @Override - public void update() { - //NO-OP - } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java index 713b370..86def76 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchWorker.java @@ -1,13 +1,8 @@ package com.gamebuster19901.excite.modding.concurrent; -import java.util.Collection; import java.util.concurrent.TimeUnit; -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; - public interface BatchWorker extends BatchContainer { - - public abstract void addBatch(Batcher batcher); public abstract void startBatch() throws InterruptedException; @@ -15,10 +10,4 @@ public interface BatchWorker extends BatchContainer { public void shutdownNow() throws InterruptedException; - public Collection> getRunnables(); - - public Collection getListeners(); - - public void addBatchListener(BatchListener listener); - } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java index bd2cb19..60debc1 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batcher.java @@ -3,14 +3,12 @@ import java.util.Collection; import java.util.concurrent.Callable; -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; - public interface Batcher extends BatchContainer { public abstract void addRunnable(Callable runnable); public abstract void addRunnable(Runnable runnable); - + @SuppressWarnings({ "rawtypes", "unchecked" }) public default void addRunnables(Collection runnables) { if(runnables.size() > 0) { @@ -34,14 +32,4 @@ else if (o instanceof Runnable) { } } - public abstract void addListener(BatchListener listener); - - public void shutdownNow() throws InterruptedException; - - public Collection> getRunnables(); - - public Collection getListeners(); - - public void updateListeners(); - } diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatcherContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatcherContainer.java new file mode 100644 index 0000000..c744692 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatcherContainer.java @@ -0,0 +1,11 @@ +package com.gamebuster19901.excite.modding.concurrent; + +import java.util.Collection; + +public interface BatcherContainer extends BatchContainer{ + + public abstract Collection> getBatches(); + + public abstract void addBatch(Batcher batcher); + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java index 3c34370..bf20cca 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchOperationComponent.java @@ -1,23 +1,22 @@ package com.gamebuster19901.excite.modding.ui; +import java.awt.BorderLayout; +import java.util.Collection; + +import javax.swing.JLabel; import javax.swing.JPanel; +import javax.swing.SwingConstants; import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; import com.gamebuster19901.excite.modding.concurrent.BatchContainer; import com.gamebuster19901.excite.modding.concurrent.BatchListener; -import java.awt.BorderLayout; -import java.util.Collection; -import javax.swing.JLabel; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; +public class BatchOperationComponent extends JPanel implements BatchContainer, BatchListener { -public class BatchOperationComponent extends JPanel implements BatchContainer { - - private BatchedImageComponent batch; - private JLabel fileName; + private BatchedImageComponent image; + private JLabel nameLabel; - public BatchOperationComponent(BatchContainer batch) { + public BatchOperationComponent(BatchContainer batch) { this(new BatchedImageComponent(batch)); setName(batch.getName()); } @@ -25,17 +24,17 @@ public BatchOperationComponent(BatchContainer batch) { /** @wbp.parser.constructor **/ - public BatchOperationComponent(BatchedImageComponent batch) { - this.batch = batch; + public BatchOperationComponent(BatchedImageComponent batch) { + this.image = batch; setLayout(new BorderLayout(0, 0)); add(batch, BorderLayout.CENTER); String name = batch.getName(); - fileName = new JLabel(name); - fileName.setHorizontalAlignment(SwingConstants.CENTER); - add(fileName, BorderLayout.SOUTH); + nameLabel = new JLabel(name); + nameLabel.setHorizontalAlignment(SwingConstants.CENTER); + add(nameLabel, BorderLayout.SOUTH); this.setName(name); } @@ -43,27 +42,38 @@ public BatchOperationComponent(BatchedImageComponent batch) { public void setName(String name) { super.setName(name); this.setToolTipText(name); - this.fileName.setText(name); + this.nameLabel.setText(name); } @Override - public Collection getRunnables() { - return batch.getRunnables(); + public void addBatchListener(BatchListener listener) { + image.addBatchListener(listener); + } + + @Override + public void shutdownNow() throws InterruptedException { + image.shutdownNow(); + } + + @Override + public Collection> getRunnables() { + return image.getRunnables(); } @Override public Collection getListeners() { - return batch.getListeners(); + return image.getListeners(); } @Override - public void update() { - SwingUtilities.invokeLater(() -> { - batch.update(); - revalidate(); - repaint(); - }); + public void updateListeners() { + image.updateListeners(); + } + @Override + public void update() { + image.update(); + repaint(); } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java index 31a3656..affc6a6 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java @@ -4,17 +4,19 @@ import java.awt.Image; import java.util.Collection; import java.util.LinkedHashMap; + import com.gamebuster19901.excite.modding.concurrent.BatchListener; import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; import com.gamebuster19901.excite.modding.concurrent.BatchContainer; -public class BatchedImageComponent extends ImageComponent implements BatchContainer { +public class BatchedImageComponent extends ImageComponent implements BatchContainer, BatchListener { - private final BatchContainer batch; + private final BatchContainer batch; - public BatchedImageComponent(BatchContainer batch) { + public BatchedImageComponent(BatchContainer batch) { super(batch.getName()); this.batch = batch; + addBatchListener(this); } @Override @@ -56,9 +58,8 @@ public Image getImage() { return StripedImageGenerator.generateImage(getWidth(), getHeight(), (LinkedHashMap) colors); } - @Override - public Collection getRunnables() { + public Collection> getRunnables() { return batch.getRunnables(); } @@ -67,10 +68,20 @@ public Collection getListeners() { return batch.getListeners(); } + @Override + public void addBatchListener(BatchListener listener) { + batch.addBatchListener(listener); + } + @Override public void update() { - revalidate(); repaint(); + System.out.println("Updated"); + } + + @Override + public void shutdownNow() throws InterruptedException { + batch.shutdownNow(); } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java b/src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java index b3a76e0..b8317ab 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/EJTabbedPane.java @@ -100,9 +100,8 @@ public Tab getTab(String title) throws IndexOutOfBoundsException { * * @param index The index of the tab to retrieve. * @return A `Tab` object representing the tab at the specified index, or null if the index is invalid. - * @throws IndexOutOfBoundsException if the index is invalid. */ - public Tab getTab(int index) throws IndexOutOfBoundsException { + public Tab getTab(int index) { if(index < 0 || index > this.getComponentCount()) { return null; } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java b/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java index 7e5d43f..ecb6801 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java @@ -5,12 +5,14 @@ import javax.swing.JFrame; import javax.swing.JPanel; -import javax.swing.SwingUtilities; import com.gamebuster19901.excite.modding.concurrent.Batch; import com.gamebuster19901.excite.modding.concurrent.BatchRunner; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; +import javax.swing.JTabbedPane; +import java.awt.Insets; +import java.util.Random; public class TestWindow { @@ -51,34 +53,49 @@ private void initialize() { Batch batch = new Batch("Test"); BatchRunner r = new BatchRunner("TestRunner"); r.addBatch(batch); + + GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWidths = new int[]{450, 0}; + gridBagLayout.rowHeights = new int[]{263, 0}; + gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE}; + gridBagLayout.rowWeights = new double[]{1.0, Double.MIN_VALUE}; + frame.getContentPane().setLayout(gridBagLayout); + JPanel panel = new JPanel(); + GridBagLayout gbl_panel = new GridBagLayout(); + gbl_panel.columnWidths = new int[]{150, 39, 0}; + gbl_panel.rowHeights = new int[]{175, 0}; + gbl_panel.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE}; + gbl_panel.rowWeights = new double[]{1.0, Double.MIN_VALUE}; + panel.setLayout(gbl_panel); BatchOperationComponent c = new BatchOperationComponent(r); c.setPreferredSize(new Dimension(150, 175)); - for(int i = 0; i < 1000; i++) { + for(int i = 0; i < 20; i++) { final int j = i; batch.addRunnable(() -> { try { + Thread.sleep(new Random().nextInt(0, 1000)); System.out.println(Thread.currentThread() + ": Ran " + j); - SwingUtilities.invokeLater(() -> { - c.update(); - System.out.println("Updated!"); - }); - } catch (Throwable e) { throw new RuntimeException(e); } }); } - GridBagLayout gridBagLayout = new GridBagLayout(); - gridBagLayout.columnWidths = new int[]{450, 0}; - gridBagLayout.rowHeights = new int[]{263, 0}; - gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE}; - gridBagLayout.rowWeights = new double[]{1.0, Double.MIN_VALUE}; - frame.getContentPane().setLayout(gridBagLayout); - JPanel panel = new JPanel(); - panel.add(c); - panel.setLayout(new WrapLayout()); + + + JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP); + GridBagConstraints gbc_c = new GridBagConstraints(); + gbc_c.anchor = GridBagConstraints.NORTHWEST; + gbc_c.insets = new Insets(0, 0, 0, 5); + gbc_c.gridx = 0; + gbc_c.gridy = 0; + tabbedPane.add(c, gbc_c); + GridBagConstraints gbc_tabbedPane = new GridBagConstraints(); + gbc_tabbedPane.anchor = GridBagConstraints.WEST; + gbc_tabbedPane.gridx = 1; + gbc_tabbedPane.gridy = 0; + panel.add(tabbedPane, gbc_tabbedPane); GridBagConstraints gbc_panel = new GridBagConstraints(); gbc_panel.fill = GridBagConstraints.BOTH; gbc_panel.gridx = 0; diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java index 9faed06..e845a0d 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/QuickAccessArchive.java @@ -9,7 +9,7 @@ public class QuickAccessArchive { private final Toc toc; private final Path archivePath; - private volatile Archive archive; + private volatile Archive archive; //This MUST be volatile or double checked locking will not work! public QuickAccessArchive(Toc toc, Path archivePath) { this.toc = toc; From f6dc8c733d91b32c5f172a869fbad48e6ae4c57c Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Wed, 15 May 2024 14:22:22 -0400 Subject: [PATCH 10/22] Resources to skip are blue --- .../excite/modding/concurrent/Batch.java | 20 ++++++++-- .../modding/concurrent/BatchContainer.java | 12 ++++++ .../modding/ui/BatchedImageComponent.java | 30 +++++++++++++- .../excite/modding/ui/Window.java | 17 ++++---- .../excite/modding/unarchiver/Unarchiver.java | 22 +++++------ .../concurrent/DecidingBatchedCallable.java | 39 +++++++++++++++++++ .../unarchiver/concurrent/DecisionType.java | 9 +++++ .../unarchiver/concurrent/Skippable.java | 7 ++++ .../unarchiver/concurrent/package-info.java | 1 + 9 files changed, 134 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecisionType.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/Skippable.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/package-info.java diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java index 4a314e1..d456312 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -1,6 +1,6 @@ package com.gamebuster19901.excite.modding.concurrent; -import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; @@ -25,6 +25,16 @@ public String getName() { return name; } + public void addRunnable(BatchedCallable batchedCallable) { + if(accepting) { + runnables.add(batchedCallable); + updateListeners(); + } + else { + notAccepting(); + } + } + @Override public void addRunnable(Callable runnable) { if(accepting) { @@ -105,8 +115,10 @@ public static class BatchedCallable implements Callable { private final Batcher batch; private final Callable child; - private volatile SoftReference threadRef; + private volatile WeakReference threadRef; protected volatile Throwable thrown; + private final Object startLock = new Object(); + private volatile Boolean started = false; protected volatile boolean finished = false; protected volatile T result; @@ -148,6 +160,8 @@ public BatchedCallable(Batcher batch, Callable c) { * This method executes the wrapped `Callable` object and stores the result. It also updates the state of this object * and notifies the associated Batcher before and after execution. If any exceptions occur during execution, they are * stored but not re-thrown by this method. The caller of this method is responsible for handling any exceptions. + * + * The behavior of this callable is undefined if call() is executed more than once. * * @return the result of the wrapped callable's execution (which may be null), or null if an exception occurred */ @@ -156,7 +170,7 @@ public T call() { Thread thread; try { thread = Thread.currentThread(); - threadRef = new SoftReference<>(thread); + threadRef = new WeakReference<>(thread); batch.updateListeners(); result = child.call(); return result; diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java index 01cc3a8..6938af8 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchContainer.java @@ -1,6 +1,7 @@ package com.gamebuster19901.excite.modding.concurrent; import java.util.Collection; +import java.util.LinkedHashSet; import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; @@ -10,6 +11,17 @@ public interface BatchContainer { public Collection> getRunnables(); + public default Collection getResults() throws IllegalStateException { + LinkedHashSet results = new LinkedHashSet<>(); + for(BatchedCallable callable : getRunnables()) { + T result = callable.getResult(); + if(result != null || (result == null && callable.getThrown() == null)) { + results.add(result); + } + } + return results; + } + public Collection getListeners(); public void addBatchListener(BatchListener listener); diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java index affc6a6..aa786e4 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java @@ -5,7 +5,10 @@ import java.util.Collection; import java.util.LinkedHashMap; +import org.apache.commons.lang3.tuple.Pair; + import com.gamebuster19901.excite.modding.concurrent.BatchListener; +import com.gamebuster19901.excite.modding.unarchiver.concurrent.DecisionType; import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; import com.gamebuster19901.excite.modding.concurrent.BatchContainer; @@ -23,6 +26,7 @@ public BatchedImageComponent(BatchContainer batch) { public Image getImage() { System.out.println("Image"); int _new = 0; + int skipped = 0; int working = 0; int success = 0; int failure = 0; @@ -37,10 +41,31 @@ public Image getImage() { working++; continue; case TERMINATED: - if(runnable.getThrown() == null) { - success++; + if(runnable.getThrown() != null) { + failure++; continue; } + Object r = runnable.getResult(); + if (r instanceof Pair) { + Object key = ((Pair) r).getKey(); + if(key instanceof DecisionType) { + DecisionType decision = (DecisionType) key; + switch(decision) { + case IGNORE: + failure++; + continue; + case PROCEED: + success++; + continue; + case SKIP: + skipped++; + continue; + default: + other++; + continue; + } + } + } failure++; continue; default: @@ -50,6 +75,7 @@ public Image getImage() { LinkedHashMap colors = new LinkedHashMap<>(); colors.put(Color.GREEN, success); + colors.put(Color.BLUE, skipped); colors.put(Color.RED, failure); colors.put(Color.WHITE, working); colors.put(Color.ORANGE, other); diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 601723e..3adc4b2 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -30,12 +30,15 @@ import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import org.apache.commons.lang3.tuple.Pair; + import com.gamebuster19901.excite.modding.concurrent.Batch; import com.gamebuster19901.excite.modding.concurrent.BatchListener; import com.gamebuster19901.excite.modding.concurrent.BatchRunner; import com.gamebuster19901.excite.modding.concurrent.Batcher; import com.gamebuster19901.excite.modding.ui.EJTabbedPane.Tab; import com.gamebuster19901.excite.modding.unarchiver.Unarchiver; +import com.gamebuster19901.excite.modding.unarchiver.concurrent.DecisionType; import com.gamebuster19901.excite.modding.util.FileUtils; import com.gamebuster19901.excite.modding.util.SplitOutputStream; @@ -61,7 +64,7 @@ public class Window implements BatchListener { private JTextField textFieldDest; private volatile Unarchiver unarchiver; - private volatile BatchRunner> copyOperations; + private volatile BatchRunner>> copyOperations; private volatile BatchRunner processOperations; /** @@ -330,7 +333,7 @@ public EJTabbedPane setupTabbedPane(EJTabbedPane tabbedPane) { tabbedPane.addTab(statusTab); tabbedPane.addTab(progressTab); - for(Batcher> b : copyOperations.getBatches()) { + for(Batcher>> b : copyOperations.getBatches()) { tabbedPane.addTab(b.getName(), new JPanel()); } @@ -374,7 +377,7 @@ private Tab setupStatusTab(EJTabbedPane tabbedPane) { JPanel statusGrid = new JPanel(new WrapLayout()); JScrollPane statusGridScroller = new JScrollPane(statusGrid); - Iterator>> batches = copyOperations.getBatches().iterator(); + Iterator>>> batches = copyOperations.getBatches().iterator(); int i = 0; while(batches.hasNext()) { BatchOperationComponent b = new BatchOperationComponent(batches.next()); @@ -754,19 +757,19 @@ private Unarchiver genUnarchiver(File source, File dest) { } @SuppressWarnings({ "rawtypes", "unchecked" }) - private BatchRunner> genCopyBatches() { - BatchRunner> batchRunner = new BatchRunner("Unarchive"); + private BatchRunner>> genCopyBatches() { + BatchRunner>> batchRunner = new BatchRunner("Unarchive"); try { if(unarchiver != null) { if(unarchiver.isValid()) { - for(Batch> batch : unarchiver.getCopyBatches()) { + for(Batch>> batch : unarchiver.getCopyBatches()) { batchRunner.addBatch(batch); } } } } catch(Throwable t) { - Batch> errBatch = new Batch<>("Error"); + Batch>> errBatch = new Batch<>("Error"); errBatch.addRunnable(() -> { throw t; }); diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java index 8a8b7fb..d00df4c 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java @@ -10,8 +10,11 @@ import java.util.concurrent.Callable; import java.util.stream.Stream; +import org.apache.commons.lang3.tuple.Pair; + import com.gamebuster19901.excite.modding.concurrent.Batch; import com.gamebuster19901.excite.modding.game.file.kaitai.TocMonster.Details; +import com.gamebuster19901.excite.modding.unarchiver.concurrent.DecisionType; import com.gamebuster19901.excite.modding.util.FileUtils; public class Unarchiver { @@ -28,16 +31,16 @@ public Unarchiver(Path sourceDir, Path destDir) throws IOException { refresh(); } - public Collection>> getCopyBatches() { - LinkedHashSet>> batches = new LinkedHashSet<>(); + public Collection>>> getCopyBatches() { + LinkedHashSet>>> batches = new LinkedHashSet<>(); for(Path toc : tocs) { batches.add(getCopyBatch(toc)); } return batches; } - public Batch> getCopyBatch(Path tocFile) { - Batch> batch = new Batch<>(tocFile.getFileName().toString()); + public Batch>> getCopyBatch(Path tocFile) { + Batch>> batch = new Batch<>(tocFile.getFileName().toString()); try { Toc toc = new Toc(tocFile.toAbsolutePath()); List
details = toc.getFiles(); @@ -50,23 +53,20 @@ public Batch> getCopyBatch(Path tocFile) { System.out.println(tocFile.getFileName() + "/" + resourceName); archive.getArchive().getFile(resourceName).writeTo(destDir.resolve(resourceName)); if(resource.name().endsWith("tex")) { - return () -> { - System.out.println("This is an example of processing a texture!"); - return null; - }; + return Pair.of(DecisionType.SKIP, () -> {return null;}); } } catch(Throwable t) { - throw t; + return Pair.of(DecisionType.IGNORE, () -> {throw t;}); } - return null; + return Pair.of(DecisionType.PROCEED, () -> {return null;}); }); } } catch(Throwable t) { batch.addRunnable(() -> { - throw t; //let the batchrunner know that an error occurred + return Pair.of(DecisionType.IGNORE, () -> {throw t;}); //let the batchrunner know that an error occurred }); } return batch; diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java new file mode 100644 index 0000000..f0f4b67 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java @@ -0,0 +1,39 @@ +package com.gamebuster19901.excite.modding.unarchiver.concurrent; + +import java.util.concurrent.Callable; + +import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; +import com.gamebuster19901.excite.modding.concurrent.Batcher; + +public class DecidingBatchedCallable extends BatchedCallable { + + private volatile DecisionType decision; + + public DecidingBatchedCallable(Batcher batch, Callable c) { + super(batch, c); + } + + @Override + public T call() { + T ret = super.call(); + processDecision(); + return ret; + } + + private void processDecision() { + if(this.getThrown() != null) { + decision = DecisionType.IGNORE; + } + else if(this.result.shouldSkip()) { + decision = DecisionType.SKIP; + } + else { + decision = DecisionType.PROCEED; + } + } + + public DecisionType getDecisionType() { + return decision; + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecisionType.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecisionType.java new file mode 100644 index 0000000..85fef16 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecisionType.java @@ -0,0 +1,9 @@ +package com.gamebuster19901.excite.modding.unarchiver.concurrent; + +public enum DecisionType { + + PROCEED, //Proceed with the process + SKIP, //Don't proceed with the process, we know we don't know how to handle it + IGNORE; //Don't proceed with the process, an unexpected exception occurred. + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/Skippable.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/Skippable.java new file mode 100644 index 0000000..b707c4f --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/Skippable.java @@ -0,0 +1,7 @@ +package com.gamebuster19901.excite.modding.unarchiver.concurrent; + +public interface Skippable { + + public boolean shouldSkip(); + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/package-info.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/package-info.java new file mode 100644 index 0000000..1c2836b --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/package-info.java @@ -0,0 +1 @@ +package com.gamebuster19901.excite.modding.unarchiver.concurrent; \ No newline at end of file From 958ffd6356b4d79e5a5a31fda953ed5a70d6dcab Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Wed, 15 May 2024 14:22:48 -0400 Subject: [PATCH 11/22] Add key to status tab --- .../modding/ui/BatchedImageComponent.java | 2 +- .../excite/modding/ui/ColorKeyComponent.java | 47 +++++++++++++++ .../excite/modding/ui/TestWindow.java | 57 ++++++++++++++++--- .../excite/modding/ui/Window.java | 54 +++++++++++++++++- 4 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/ColorKeyComponent.java diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java index aa786e4..78e9ad3 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageComponent.java @@ -75,7 +75,7 @@ public Image getImage() { LinkedHashMap colors = new LinkedHashMap<>(); colors.put(Color.GREEN, success); - colors.put(Color.BLUE, skipped); + colors.put(Color.CYAN.darker(), skipped); colors.put(Color.RED, failure); colors.put(Color.WHITE, working); colors.put(Color.ORANGE, other); diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/ColorKeyComponent.java b/src/main/java/com/gamebuster19901/excite/modding/ui/ColorKeyComponent.java new file mode 100644 index 0000000..6b8c326 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/ColorKeyComponent.java @@ -0,0 +1,47 @@ +package com.gamebuster19901.excite.modding.ui; + +import java.awt.Color; + +import javax.swing.JPanel; +import javax.swing.BorderFactory; +import javax.swing.JLabel; +import java.awt.FlowLayout; +import java.awt.GridBagLayout; +import java.awt.GridBagConstraints; +import java.awt.Insets; +import javax.swing.SwingConstants; + +public class ColorKeyComponent extends JPanel { + + private static final long serialVersionUID = 1L; + + public ColorKeyComponent(Color color, String name) { + this.setBorder(BorderFactory.createEmptyBorder()); + GridBagLayout gridBagLayout = new GridBagLayout(); + gridBagLayout.columnWidths = new int[]{0, 0}; + gridBagLayout.rowHeights = new int[]{25}; + gridBagLayout.columnWeights = new double[]{0.0, 0.0}; + gridBagLayout.rowWeights = new double[]{0.0}; + setLayout(gridBagLayout); + JLabel coloring = new JLabel("■"); + coloring.setBorder(BorderFactory.createEmptyBorder()); + coloring.setHorizontalAlignment(SwingConstants.TRAILING); + FlowLayout flowLayout = new FlowLayout(); + flowLayout.setAlignment(FlowLayout.LEFT); + coloring.setForeground(color); + GridBagConstraints gbc_coloring = new GridBagConstraints(); + gbc_coloring.anchor = GridBagConstraints.EAST; + gbc_coloring.insets = new Insets(0, 0, 2, 0); + gbc_coloring.gridx = 0; + gbc_coloring.gridy = 0; + add(coloring, gbc_coloring); + JLabel lblName = new JLabel(":" + name); + GridBagConstraints gbc_lblName = new GridBagConstraints(); + gbc_lblName.anchor = GridBagConstraints.WEST; + gbc_lblName.gridx = 1; + gbc_lblName.gridy = 0; + add(lblName, gbc_lblName); + + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java b/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java index ecb6801..fc49b8a 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/TestWindow.java @@ -1,5 +1,6 @@ package com.gamebuster19901.excite.modding.ui; +import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; @@ -13,6 +14,7 @@ import javax.swing.JTabbedPane; import java.awt.Insets; import java.util.Random; +import javax.swing.JScrollPane; public class TestWindow { @@ -67,8 +69,6 @@ private void initialize() { gbl_panel.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE}; gbl_panel.rowWeights = new double[]{1.0, Double.MIN_VALUE}; panel.setLayout(gbl_panel); - BatchOperationComponent c = new BatchOperationComponent(r); - c.setPreferredSize(new Dimension(150, 175)); for(int i = 0; i < 20; i++) { final int j = i; batch.addRunnable(() -> { @@ -85,17 +85,58 @@ private void initialize() { JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.TOP); - GridBagConstraints gbc_c = new GridBagConstraints(); - gbc_c.anchor = GridBagConstraints.NORTHWEST; - gbc_c.insets = new Insets(0, 0, 0, 5); - gbc_c.gridx = 0; - gbc_c.gridy = 0; - tabbedPane.add(c, gbc_c); GridBagConstraints gbc_tabbedPane = new GridBagConstraints(); gbc_tabbedPane.anchor = GridBagConstraints.WEST; gbc_tabbedPane.gridx = 1; gbc_tabbedPane.gridy = 0; panel.add(tabbedPane, gbc_tabbedPane); + + JPanel panel_1 = new JPanel(); + tabbedPane.addTab("New tab", null, panel_1, null); + GridBagLayout gbl_panel_1 = new GridBagLayout(); + gbl_panel_1.columnWidths = new int[]{0, 0, 0}; + gbl_panel_1.rowHeights = new int[]{0, 178, 0}; + gbl_panel_1.columnWeights = new double[]{0.0, 1.0, Double.MIN_VALUE}; + gbl_panel_1.rowWeights = new double[]{1.0, 0.0, Double.MIN_VALUE}; + panel_1.setLayout(gbl_panel_1); + + ColorKeyComponent lblNewLabel = new ColorKeyComponent(Color.RED, "Errorifying, really weird"); + GridBagConstraints gbc_lblNewLabel = new GridBagConstraints(); + gbc_lblNewLabel.insets = new Insets(0, 0, 5, 5); + gbc_lblNewLabel.gridx = 0; + gbc_lblNewLabel.gridy = 0; + panel_1.add(lblNewLabel, gbc_lblNewLabel); + + ColorKeyComponent lblNewLabel_1 = new ColorKeyComponent(Color.RED, "Error"); + GridBagLayout gridBagLayout_1 = (GridBagLayout) lblNewLabel_1.getLayout(); + gridBagLayout_1.rowWeights = new double[]{0.0}; + gridBagLayout_1.rowHeights = new int[]{25}; + gridBagLayout_1.columnWeights = new double[]{0.0, 1.0}; + gridBagLayout_1.columnWidths = new int[]{25, 78}; + GridBagConstraints gbc_lblNewLabel_1 = new GridBagConstraints(); + gbc_lblNewLabel_1.insets = new Insets(0, 0, 5, 0); + gbc_lblNewLabel_1.fill = GridBagConstraints.BOTH; + gbc_lblNewLabel_1.gridx = 1; + gbc_lblNewLabel_1.gridy = 0; + panel_1.add(lblNewLabel_1, gbc_lblNewLabel_1); + + + + BatchOperationComponent c = new BatchOperationComponent(r); + + JScrollPane scrollPane = new JScrollPane(c); + GridBagConstraints gbc_scrollPane = new GridBagConstraints(); + gbc_scrollPane.gridwidth = 2; + gbc_scrollPane.anchor = GridBagConstraints.NORTHWEST; + gbc_scrollPane.gridx = 0; + gbc_scrollPane.gridy = 1; + panel_1.add(scrollPane, gbc_scrollPane); + c.setPreferredSize(new Dimension(150, 175)); + GridBagConstraints gbc_c = new GridBagConstraints(); + gbc_c.anchor = GridBagConstraints.NORTHWEST; + gbc_c.insets = new Insets(0, 0, 0, 5); + gbc_c.gridx = 0; + gbc_c.gridy = 0; GridBagConstraints gbc_panel = new GridBagConstraints(); gbc_panel.fill = GridBagConstraints.BOTH; gbc_panel.gridx = 0; diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 3adc4b2..a45b293 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -52,6 +52,9 @@ import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.JTextArea; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.FlowLayout; public class Window implements BatchListener { @@ -374,6 +377,11 @@ private Tab setupStatusTab(EJTabbedPane tabbedPane) { //remove all } + JPanel contents = setupStatusNavigationPanel(tabbedPane); + + + + Tab tab = new Tab(STATUS, null, contents, null); JPanel statusGrid = new JPanel(new WrapLayout()); JScrollPane statusGridScroller = new JScrollPane(statusGrid); @@ -386,13 +394,57 @@ private Tab setupStatusTab(EJTabbedPane tabbedPane) { i++; } - Tab tab = new Tab(STATUS, null, statusGridScroller, null); + GridBagConstraints gbc_statusGridScroller = new GridBagConstraints(); + gbc_statusGridScroller.fill = GridBagConstraints.BOTH; + gbc_statusGridScroller.gridx = 0; + gbc_statusGridScroller.gridy = 1; + contents.add(statusGridScroller, gbc_statusGridScroller); + + statusGridScroller.getVerticalScrollBar().setUnitIncrement(175 / 2); tabbedPane.addTab(tab.title(), tab.icon(), tab.component(), tab.tip()); //need to add this separately so the window builder can see it return tab; } + private JPanel setupStatusNavigationPanel(EJTabbedPane tabbedPane) { + JPanel contents = new JPanel(); + GridBagLayout gbl_contents = new GridBagLayout(); + gbl_contents.columnWidths = new int[]{22, 0}; + gbl_contents.rowHeights = new int[]{0, 13, 0}; + gbl_contents.columnWeights = new double[]{1.0, Double.MIN_VALUE}; + gbl_contents.rowWeights = new double[]{0.0, 1.0, Double.MIN_VALUE}; + contents.setLayout(gbl_contents); + + JPanel NavigationPanel = new JPanel(); + GridBagConstraints gbc_NavigationPanel = new GridBagConstraints(); + gbc_NavigationPanel.insets = new Insets(0, 0, 5, 0); + gbc_NavigationPanel.fill = GridBagConstraints.BOTH; + gbc_NavigationPanel.gridx = 0; + gbc_NavigationPanel.gridy = 0; + contents.add(NavigationPanel, gbc_NavigationPanel); + NavigationPanel.setLayout(new BorderLayout(0, 0)); + + JLabel lblKey = new JLabel("Key"); + lblKey.setHorizontalAlignment(SwingConstants.CENTER); + NavigationPanel.add(lblKey); + + JPanel keysPanel = new JPanel(); + FlowLayout flowLayout = (FlowLayout) keysPanel.getLayout(); + flowLayout.setHgap(20); + flowLayout.setVgap(0); + NavigationPanel.add(keysPanel, BorderLayout.SOUTH); + + keysPanel.add(new ColorKeyComponent(Color.GRAY, "Not Started")); + keysPanel.add(new ColorKeyComponent(Color.ORANGE, "Other")); + keysPanel.add(new ColorKeyComponent(Color.WHITE, "Working")); + keysPanel.add(new ColorKeyComponent(Color.RED, "Failure")); + keysPanel.add(new ColorKeyComponent(Color.CYAN.darker(), "Skipped")); + keysPanel.add(new ColorKeyComponent(Color.GREEN, "Success")); + + return contents; + } + public Tab setupProgressTab(EJTabbedPane tabbedPane) { Tab progressTab = tabbedPane.getTab(PROGRESS); if(progressTab != null) { From 56628d29501fea8dcd3abaeb882fa86d2a2b97d6 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Wed, 15 May 2024 16:40:55 -0400 Subject: [PATCH 12/22] MOAR performance, Batchrunner go vroom --- .../modding/concurrent/BatchRunner.java | 15 +++---- .../modding/ui/BatchedImageListener.java | 7 ---- .../excite/modding/ui/Window.java | 31 ++++++++++----- .../concurrent/DecidingBatchedCallable.java | 39 ------------------- .../excite/modding/util/FileUtils.java | 3 -- 5 files changed, 27 insertions(+), 68 deletions(-) delete mode 100644 src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java delete mode 100644 src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index 40d3274..5c26b10 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -1,6 +1,8 @@ package com.gamebuster19901.excite.modding.concurrent; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -57,9 +59,9 @@ public void startBatch() throws InterruptedException { } synchronized(batches) { started = true; - for(Batcher batch : batches) { - executor.invokeAll(batch.getRunnables()); - } + ArrayList> batchers = new ArrayList<>(getRunnables()); + Collections.shuffle(batchers); //So multiple threads don't wait for a single archive to lazily load, allows multiple archives to lazily load at a time + executor.invokeAll(batchers); } } } @@ -114,12 +116,5 @@ public void addBatchListener(BatchListener listener) { public Collection> getBatches() { return (Collection>) batches.clone(); } - - public int getCompleted() { - int ret = 0; - Collection> callables = getRunnables(); - for(BatchedCallable callable : callables) {ret++;} - return ret; - } } diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java b/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java deleted file mode 100644 index 53d399d..0000000 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/BatchedImageListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.gamebuster19901.excite.modding.ui; - -public interface BatchedImageListener { - - public void onBatchedImageUpdate(); - -} diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index a45b293..9fc1ba8 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -65,6 +65,7 @@ public class Window implements BatchListener { private JFrame frame; private JTextField textFieldSource; private JTextField textFieldDest; + private JSlider threadSlider; private volatile Unarchiver unarchiver; private volatile BatchRunner>> copyOperations; @@ -100,6 +101,14 @@ public Window() { */ private void initialize() { unarchiver = genUnarchiver(null, null); + + threadSlider = new JSlider(); + threadSlider.setSnapToTicks(true); + threadSlider.setMinimum(1); + threadSlider.setMaximum(Runtime.getRuntime().availableProcessors()); + threadSlider.setPreferredSize(new Dimension(77, 16)); + threadSlider.setBorder(null); + copyOperations = genCopyBatches(); processOperations = genProcessBatches(null); @@ -193,16 +202,13 @@ private void initialize() { gbc_lblThreads.gridy = 4; frame.getContentPane().add(lblThreads, gbc_lblThreads); - JSlider slider = new JSlider(); - slider.setSnapToTicks(true); - slider.setPreferredSize(new Dimension(77, 16)); - slider.setBorder(null); + GridBagConstraints gbc_slider = new GridBagConstraints(); gbc_slider.fill = GridBagConstraints.HORIZONTAL; gbc_slider.insets = new Insets(0, 0, 5, 5); gbc_slider.gridx = 1; gbc_slider.gridy = 5; - frame.getContentPane().add(slider, gbc_slider); + frame.getContentPane().add(threadSlider, gbc_slider); JProgressBar progressBar = new CustomProgressBar(); progressBar.setStringPainted(true); @@ -425,7 +431,7 @@ private JPanel setupStatusNavigationPanel(EJTabbedPane tabbedPane) { contents.add(NavigationPanel, gbc_NavigationPanel); NavigationPanel.setLayout(new BorderLayout(0, 0)); - JLabel lblKey = new JLabel("Key"); + JLabel lblKey = new JLabel("Legend"); lblKey.setHorizontalAlignment(SwingConstants.CENTER); NavigationPanel.add(lblKey); @@ -447,9 +453,9 @@ private JPanel setupStatusNavigationPanel(EJTabbedPane tabbedPane) { public Tab setupProgressTab(EJTabbedPane tabbedPane) { Tab progressTab = tabbedPane.getTab(PROGRESS); - if(progressTab != null) { + /*if(progressTab != null) { return progressTab; - } + }*/ JPanel progressPanel = new JPanel(); Tab tab = new Tab(PROGRESS, null, progressPanel, null); @@ -490,7 +496,9 @@ private void setupLeftProgressPane(JPanel progressPanel) { gbc_separator.gridy = 0; leftPanel.add(separator, gbc_separator); BatchOperationComponent copyBatchComponent = new BatchOperationComponent(copyOperations); + copyOperations.addBatchListener(copyBatchComponent); copyBatchComponent.setToolTipText("All Batches"); + System.out.println(copyOperations.getBatches().size() + " OISHDFPIOHDFIUOSPHUIFSIDOHUOFHUIOSDHIFUHOHIUO"); GridBagConstraints gbc_copyBatchComponent = new GridBagConstraints(); gbc_copyBatchComponent.fill = GridBagConstraints.BOTH; @@ -678,6 +686,7 @@ private void setupLeftProgressPane(JPanel progressPanel) { copyOperations.addBatchListener(() -> { SwingUtilities.invokeLater(() -> { + //lblResourcesCopiedCount.setText(copyBatchComponent.); update(); }); }); @@ -810,7 +819,7 @@ private Unarchiver genUnarchiver(File source, File dest) { @SuppressWarnings({ "rawtypes", "unchecked" }) private BatchRunner>> genCopyBatches() { - BatchRunner>> batchRunner = new BatchRunner("Unarchive"); + BatchRunner>> batchRunner = new BatchRunner("Unarchive", this.getUsableThreads()); try { if(unarchiver != null) { if(unarchiver.isValid()) { @@ -850,4 +859,8 @@ public void update() { frame.repaint(); } + private int getUsableThreads() { + return Math.clamp(threadSlider.getValue(), 1, Runtime.getRuntime().availableProcessors()); + } + } diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java deleted file mode 100644 index f0f4b67..0000000 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/concurrent/DecidingBatchedCallable.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.gamebuster19901.excite.modding.unarchiver.concurrent; - -import java.util.concurrent.Callable; - -import com.gamebuster19901.excite.modding.concurrent.Batch.BatchedCallable; -import com.gamebuster19901.excite.modding.concurrent.Batcher; - -public class DecidingBatchedCallable extends BatchedCallable { - - private volatile DecisionType decision; - - public DecidingBatchedCallable(Batcher batch, Callable c) { - super(batch, c); - } - - @Override - public T call() { - T ret = super.call(); - processDecision(); - return ret; - } - - private void processDecision() { - if(this.getThrown() != null) { - decision = DecisionType.IGNORE; - } - else if(this.result.shouldSkip()) { - decision = DecisionType.SKIP; - } - else { - decision = DecisionType.PROCEED; - } - } - - public DecisionType getDecisionType() { - return decision; - } - -} diff --git a/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java index 9216ad5..43b98e0 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java +++ b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java @@ -8,15 +8,12 @@ import java.nio.ByteOrder; import java.nio.charset.Charset; import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; -import java.util.HashSet; import java.util.LinkedHashSet; -import java.util.List; public class FileUtils { From e06144c02f430fdcb0f8affce6c264396cbb220b Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sat, 18 May 2024 22:09:01 -0400 Subject: [PATCH 13/22] rm unused field --- .../gamebuster19901/excite/modding/concurrent/BatchRunner.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index 5c26b10..ae0b281 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -15,7 +15,6 @@ public class BatchRunner implements BatchWorker, BatcherContainer { private final String name; private final ExecutorService executor; private final LinkedHashSet> batches = new LinkedHashSet>(); - private volatile boolean started = false; private volatile boolean listenerAdded = false; public BatchRunner(String name) { @@ -58,7 +57,6 @@ public void startBatch() throws InterruptedException { throw new IllegalStateException("BatchRunner has already been started!"); } synchronized(batches) { - started = true; ArrayList> batchers = new ArrayList<>(getRunnables()); Collections.shuffle(batchers); //So multiple threads don't wait for a single archive to lazily load, allows multiple archives to lazily load at a time executor.invokeAll(batchers); From 21aadee837069d0d76de984deb336dbf4e8108f6 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sat, 18 May 2024 22:09:26 -0400 Subject: [PATCH 14/22] Fix extraction doing nothing if the destination directory contains no files --- .../gamebuster19901/excite/modding/ui/Window.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java index 9fc1ba8..2fca743 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java +++ b/src/main/java/com/gamebuster19901/excite/modding/ui/Window.java @@ -288,15 +288,14 @@ private void initialize() { return; } - new Thread(() -> { - try { - copyOperations.startBatch(); - } catch (InterruptedException e1) { - throw new RuntimeException(e1); - } - }).start(); - } + new Thread(() -> { + try { + copyOperations.startBatch(); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); + } + }).start(); } From de42a0c50a49b4e6de003524f5969825f4799fae Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sat, 18 May 2024 22:10:15 -0400 Subject: [PATCH 15/22] Extracted resources are now placed into a folder with their respective archives' names. --- .../gamebuster19901/excite/modding/unarchiver/Unarchiver.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java index d00df4c..36c1336 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java @@ -50,8 +50,9 @@ public Batch>> getCopyBatch(Path tocFile) { batch.addRunnable(() -> { try { String resourceName = resource.name(); + Path dest = destDir.resolve(tocFile.getFileName()).resolve(resourceName); System.out.println(tocFile.getFileName() + "/" + resourceName); - archive.getArchive().getFile(resourceName).writeTo(destDir.resolve(resourceName)); + archive.getArchive().getFile(resourceName).writeTo(dest); if(resource.name().endsWith("tex")) { return Pair.of(DecisionType.SKIP, () -> {return null;}); } From 8fcfb4332e4ae94ef402a56f0a2c53ff9936f566 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sat, 18 May 2024 22:46:18 -0400 Subject: [PATCH 16/22] Actually put resources in correct folder --- .../gamebuster19901/excite/modding/unarchiver/Unarchiver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java index 36c1336..3134341 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java @@ -50,7 +50,7 @@ public Batch>> getCopyBatch(Path tocFile) { batch.addRunnable(() -> { try { String resourceName = resource.name(); - Path dest = destDir.resolve(tocFile.getFileName()).resolve(resourceName); + Path dest = destDir.resolve(tocFile.getFileName()); System.out.println(tocFile.getFileName() + "/" + resourceName); archive.getArchive().getFile(resourceName).writeTo(dest); if(resource.name().endsWith("tex")) { From 7b9f7d712ece2c872dcd440adaf26164a24b9503 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Tue, 21 May 2024 10:32:21 -0400 Subject: [PATCH 17/22] Fix deadlock --- .../gamebuster19901/excite/modding/concurrent/BatchRunner.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java index ae0b281..974240a 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/BatchRunner.java @@ -60,6 +60,7 @@ public void startBatch() throws InterruptedException { ArrayList> batchers = new ArrayList<>(getRunnables()); Collections.shuffle(batchers); //So multiple threads don't wait for a single archive to lazily load, allows multiple archives to lazily load at a time executor.invokeAll(batchers); + executor.close(); } } } From ba427d2ed51cbde78c89a6260e866046d8a3ad8c Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Tue, 21 May 2024 10:35:16 -0400 Subject: [PATCH 18/22] Add an AssetAnalyzer --- .../modding/debugging/AssetAnalyzer.java | 96 +++++++++++++++++++ .../modding/debugging/package-info.java | 1 + .../excite/modding/unarchiver/Unarchiver.java | 21 ++-- .../excite/modding/util/FileUtils.java | 5 + 4 files changed, 116 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/gamebuster19901/excite/modding/debugging/AssetAnalyzer.java create mode 100644 src/main/java/com/gamebuster19901/excite/modding/debugging/package-info.java diff --git a/src/main/java/com/gamebuster19901/excite/modding/debugging/AssetAnalyzer.java b/src/main/java/com/gamebuster19901/excite/modding/debugging/AssetAnalyzer.java new file mode 100644 index 0000000..ba4a730 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/debugging/AssetAnalyzer.java @@ -0,0 +1,96 @@ +package com.gamebuster19901.excite.modding.debugging; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; + +import com.gamebuster19901.excite.modding.concurrent.Batch; +import com.gamebuster19901.excite.modding.concurrent.BatchRunner; +import com.gamebuster19901.excite.modding.game.file.kaitai.TocMonster.Details; +import com.gamebuster19901.excite.modding.unarchiver.QuickAccessArchive; +import com.gamebuster19901.excite.modding.unarchiver.Toc; +import com.gamebuster19901.excite.modding.unarchiver.Unarchiver; +import com.thegamecommunity.excite.modding.game.file.AssetType; + +public class AssetAnalyzer { + + private static final Path current = Path.of("").toAbsolutePath(); + private static final Path run = current.resolve("run"); + private static final Path assets = current.resolve("gameData"); + + public static void main(String[] args) throws IOException, InterruptedException { + System.out.println(current); + System.out.println(run); + System.out.println(assets); + + Unarchiver unarchiver = new Unarchiver(assets, run); + Collection tocs = unarchiver.getTocs(); + BatchRunner>> runner = new BatchRunner<>("Analyzer"); + + for(Path tocFile : tocs) { + String tocName = tocFile.getFileName().toString(); + Batch>> batch = new Batch>>(tocName); + Toc toc = new Toc(tocFile); + List
details = toc.getFiles(); + + final QuickAccessArchive QArchive = unarchiver.getArchive(toc); + for(Details resource : details) { + String resourceName = resource.name(); + AssetType type = AssetType.getAssetType(resourceName); + if(type == AssetType.UNRECOGNIZED) { + throw new AssertionError("Unrecognized asset type: " + resourceName + " in " + tocName); + } + batch.addRunnable(() -> { + return Pair.of(type, Pair.of(resource.typeCode(), resource.typeCodeInt())); + }); + } + runner.addBatch(batch); + } + + runner.startBatch(); + Collection>> results = runner.getResults(); + HashMap> stringCodes = new HashMap>(); + HashMap> intCodes = new HashMap>(); + for(AssetType type : AssetType.values()) { + stringCodes.put(type, new HashSet<>()); + intCodes.put(type, new HashSet<>()); + } + for(Pair> pair : results) { + Pair pair2 = pair.getValue(); + stringCodes.get(pair.getKey()).add(pair2.getKey()); + intCodes.get(pair.getKey()).add(pair2.getValue()); + } + + for(AssetType assetType : AssetType.values()) { + HashSet strings = stringCodes.get(assetType); + HashSet ints = intCodes.get(assetType); + System.out.println("============" + assetType.toString().toUpperCase() + "============"); + System.out.println("Analysis of '" + assetType + "' type:"); + System.out.println("Total string typecodes: " + strings.size()); + System.out.println("Total int typecodes: " + ints.size()); + System.out.println("List of string typecodes below: \n"); + stringCodes.get(assetType).forEach((forCode) -> { + if(forCode.indexOf('\0') != -1) { + System.out.print('['); + for(char c : forCode.toCharArray()) { + System.out.print(Integer.toHexString((int)c) + " "); + } + System.out.println(']'); + } + else { + System.out.println(forCode); + } + }); + System.out.println("List of int typecodes below: \n"); + intCodes.get(assetType).forEach((forCode) -> { + System.out.println(forCode); + }); + } + } + +} diff --git a/src/main/java/com/gamebuster19901/excite/modding/debugging/package-info.java b/src/main/java/com/gamebuster19901/excite/modding/debugging/package-info.java new file mode 100644 index 0000000..0a8f913 --- /dev/null +++ b/src/main/java/com/gamebuster19901/excite/modding/debugging/package-info.java @@ -0,0 +1 @@ +package com.gamebuster19901.excite.modding.debugging; \ No newline at end of file diff --git a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java index 3134341..36565ee 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java +++ b/src/main/java/com/gamebuster19901/excite/modding/unarchiver/Unarchiver.java @@ -5,6 +5,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.concurrent.Callable; @@ -31,6 +32,10 @@ public Unarchiver(Path sourceDir, Path destDir) throws IOException { refresh(); } + public Collection getTocs() { + return Collections.unmodifiableCollection(tocs); + } + public Collection>>> getCopyBatches() { LinkedHashSet>>> batches = new LinkedHashSet<>(); for(Path toc : tocs) { @@ -45,29 +50,31 @@ public Batch>> getCopyBatch(Path tocFile) { Toc toc = new Toc(tocFile.toAbsolutePath()); List
details = toc.getFiles(); - final QuickAccessArchive archive = getArchive(toc); + final QuickAccessArchive QArchive = getArchive(toc); for(Details resource : details) { batch.addRunnable(() -> { try { String resourceName = resource.name(); Path dest = destDir.resolve(tocFile.getFileName()); System.out.println(tocFile.getFileName() + "/" + resourceName); - archive.getArchive().getFile(resourceName).writeTo(dest); + Archive archive = QArchive.getArchive(); + ArchivedFile f = archive.getFile(resourceName); + f.writeTo(dest); if(resource.name().endsWith("tex")) { - return Pair.of(DecisionType.SKIP, () -> {return null;}); + return Pair.of(DecisionType.SKIP, () -> {return null;}); //the asset was successfully extracted, but we don't know how to process it } + return Pair.of(DecisionType.PROCEED, () -> {return null;}); //the asset was successfully extracted, and will be submitted to the next batchRunner to convert into a viewable format } catch(Throwable t) { - return Pair.of(DecisionType.IGNORE, () -> {throw t;}); + return Pair.of(DecisionType.IGNORE, () -> {throw t;}); //let the next batchrunner that an error ocurred, and will not be submitted to the next batchrunner. } - return Pair.of(DecisionType.PROCEED, () -> {return null;}); }); } } catch(Throwable t) { batch.addRunnable(() -> { - return Pair.of(DecisionType.IGNORE, () -> {throw t;}); //let the batchrunner know that an error occurred + return Pair.of(DecisionType.IGNORE, () -> {throw t;}); //let the next batchrunner know that an error occurred }); } return batch; @@ -77,7 +84,7 @@ public boolean isValid() { return FileUtils.isDirectory(sourceDir) && FileUtils.isDirectory(destDir); } - private QuickAccessArchive getArchive(Toc toc) throws IOException { + public QuickAccessArchive getArchive(Toc toc) throws IOException { for(Path archivePath : archives) { if(getFileName(toc.getFile()).equals(getFileName(archivePath))) { return new QuickAccessArchive(toc, archivePath); diff --git a/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java index 43b98e0..92154e6 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java +++ b/src/main/java/com/gamebuster19901/excite/modding/util/FileUtils.java @@ -151,6 +151,11 @@ public static String getFileName(Path f) { int i = fileName.lastIndexOf('.'); return (i == -1) ? fileName : fileName.substring(0, i); } + + public static String getExtension(String fileName) { + int i = fileName.lastIndexOf('.'); + return i == -1 ? "" : fileName.substring(i + 1); + } public static boolean isDirectory(Path dir) { return Files.isDirectory(dir) && !Files.isSymbolicLink(dir); From a0079ad9354fb332c9819eaf2225416faa2cb1af Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Tue, 21 May 2024 13:03:10 -0400 Subject: [PATCH 19/22] spaces -> tabs --- .../excite/modding/concurrent/Batch.java | 328 +++++++++--------- 1 file changed, 164 insertions(+), 164 deletions(-) diff --git a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java index d456312..54a6f87 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java +++ b/src/main/java/com/gamebuster19901/excite/modding/concurrent/Batch.java @@ -12,65 +12,65 @@ public class Batch implements Batcher { private final String name; - private final Set> runnables = new HashSet<>(); - private final LinkedHashSet listeners = new LinkedHashSet<>(); - private volatile boolean accepting = true; - - public Batch(String name) { - this.name = name; - } - - @Override - public String getName() { - return name; - } - - public void addRunnable(BatchedCallable batchedCallable) { - if(accepting) { - runnables.add(batchedCallable); - updateListeners(); - } - else { - notAccepting(); - } - } - - @Override - public void addRunnable(Callable runnable) { - if(accepting) { - BatchedCallable b = new BatchedCallable<>(this, runnable); - runnables.add(new BatchedCallable<>(this, runnable)); - updateListeners(); - } - else { - notAccepting(); - } - } + private final Set> runnables = new HashSet<>(); + private final LinkedHashSet listeners = new LinkedHashSet<>(); + private volatile boolean accepting = true; + + public Batch(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + public void addRunnable(BatchedCallable batchedCallable) { + if(accepting) { + runnables.add(batchedCallable); + updateListeners(); + } + else { + notAccepting(); + } + } + + @Override + public void addRunnable(Callable runnable) { + if(accepting) { + BatchedCallable b = new BatchedCallable<>(this, runnable); + runnables.add(new BatchedCallable<>(this, runnable)); + updateListeners(); + } + else { + notAccepting(); + } + } - @Override - public void addRunnable(Runnable runnable) { - if(accepting) { - BatchedCallable b = new BatchedCallable<>(this, runnable); - runnables.add(new BatchedCallable<>(this, runnable)); - updateListeners(); - } - else { - notAccepting(); - } - } - - @Override - public void addBatchListener(BatchListener listener) { - if(accepting) { - if(!listeners.add(listener)) { - System.out.println("Warning: duplicate batch listener ignored."); - } - } - else { - notAccepting(); - } - } - + @Override + public void addRunnable(Runnable runnable) { + if(accepting) { + BatchedCallable b = new BatchedCallable<>(this, runnable); + runnables.add(new BatchedCallable<>(this, runnable)); + updateListeners(); + } + else { + notAccepting(); + } + } + + @Override + public void addBatchListener(BatchListener listener) { + if(accepting) { + if(!listeners.add(listener)) { + System.out.println("Warning: duplicate batch listener ignored."); + } + } + else { + notAccepting(); + } + } + @Override public Collection> getRunnables() { return Set.copyOf(runnables); @@ -82,90 +82,90 @@ public Collection getListeners() { return (Collection) listeners.clone(); } - public void shutdownNow() throws InterruptedException { - accepting = false; - Shutdown shutdown = new Shutdown(); - for(BatchedCallable r : runnables) { - if(r.getState() == State.NEW) { - r.shutdown(shutdown); - } - } - System.err.println(runnables.size()); - } - - public void updateListeners() { - for(BatchListener listener : listeners) { - listener.update(); - } - } - - private void notAccepting() { - throw new IllegalStateException("Batch is not accepting new tasks or listeners."); - } - - /** - * A wrapper class for a {@link Callable} object that participates in a batch execution managed by a {@link Batcher}. - * - * This class allows for the creation of callables that can be tracked and managed by a batching system. It provides methods - * to get the execution state, retrieve the result after completion, and handle exceptions. - * - * @param the type of the result produced by the wrapped {@link Callable} - */ - public static class BatchedCallable implements Callable { - - private final Batcher batch; - private final Callable child; - private volatile WeakReference threadRef; - protected volatile Throwable thrown; - private final Object startLock = new Object(); - private volatile Boolean started = false; - protected volatile boolean finished = false; - protected volatile T result; - - - /** - * Creates a new BatchedCallable instance for a provided {@link Runnable} object. - *

- * This convenience constructor takes a Runnable and converts it to a Callable that simply calls the {@link Runnable#run} method - * and returns null. It then delegates to the main constructor with the converted callable. - * - * @param batch the {@link Batcher} instance managing this callable - * @param r the {@link Runnable} object to be wrapped - */ - public BatchedCallable(Batcher batch, Runnable r) { - this(batch, () -> { - r.run(); - return null; - }); - } - - /** - * Creates a new BatchedCallable instance for the provided {@link Callable} object. - *

- * This constructor wraps a given Callable and associates it with the specified Batcher. The Batcher is - * notified of updates to the state of this callable. - * - * @param batch the {@link Batcher} instance managing this callable - * @param c the {@link Callable} object to be wrapped - */ - public BatchedCallable(Batcher batch, Callable c) { - this.batch = batch; - this.child = c; - batch.updateListeners(); - } - - /** - * Implements the `call` method of the {@link Callable} interface. - *

- * This method executes the wrapped `Callable` object and stores the result. It also updates the state of this object - * and notifies the associated Batcher before and after execution. If any exceptions occur during execution, they are - * stored but not re-thrown by this method. The caller of this method is responsible for handling any exceptions. - * - * The behavior of this callable is undefined if call() is executed more than once. + public void shutdownNow() throws InterruptedException { + accepting = false; + Shutdown shutdown = new Shutdown(); + for(BatchedCallable r : runnables) { + if(r.getState() == State.NEW) { + r.shutdown(shutdown); + } + } + System.err.println(runnables.size()); + } + + public void updateListeners() { + for(BatchListener listener : listeners) { + listener.update(); + } + } + + private void notAccepting() { + throw new IllegalStateException("Batch is not accepting new tasks or listeners."); + } + + /** + * A wrapper class for a {@link Callable} object that participates in a batch execution managed by a {@link Batcher}. + * + * This class allows for the creation of callables that can be tracked and managed by a batching system. It provides methods + * to get the execution state, retrieve the result after completion, and handle exceptions. + * + * @param the type of the result produced by the wrapped {@link Callable} + */ + public static class BatchedCallable implements Callable { + + private final Batcher batch; + private final Callable child; + private volatile WeakReference threadRef; + protected volatile Throwable thrown; + private final Object startLock = new Object(); + private volatile Boolean started = false; + protected volatile boolean finished = false; + protected volatile T result; + + + /** + * Creates a new BatchedCallable instance for a provided {@link Runnable} object. + *

+ * This convenience constructor takes a Runnable and converts it to a Callable that simply calls the {@link Runnable#run} method + * and returns null. It then delegates to the main constructor with the converted callable. * - * @return the result of the wrapped callable's execution (which may be null), or null if an exception occurred - */ - @Override + * @param batch the {@link Batcher} instance managing this callable + * @param r the {@link Runnable} object to be wrapped + */ + public BatchedCallable(Batcher batch, Runnable r) { + this(batch, () -> { + r.run(); + return null; + }); + } + + /** + * Creates a new BatchedCallable instance for the provided {@link Callable} object. + *

+ * This constructor wraps a given Callable and associates it with the specified Batcher. The Batcher is + * notified of updates to the state of this callable. + * + * @param batch the {@link Batcher} instance managing this callable + * @param c the {@link Callable} object to be wrapped + */ + public BatchedCallable(Batcher batch, Callable c) { + this.batch = batch; + this.child = c; + batch.updateListeners(); + } + + /** + * Implements the `call` method of the {@link Callable} interface. + *

+ * This method executes the wrapped `Callable` object and stores the result. It also updates the state of this object + * and notifies the associated Batcher before and after execution. If any exceptions occur during execution, they are + * stored but not re-thrown by this method. The caller of this method is responsible for handling any exceptions. + * + * The behavior of this callable is undefined if call() is executed more than once. + * + * @return the result of the wrapped callable's execution (which may be null), or null if an exception occurred + */ + @Override public T call() { Thread thread; try { @@ -185,20 +185,20 @@ public T call() { return null; } - /** - * Gets the current execution state of the wrapped callable. - * - * This method examines the internal state and thread reference to determine the current execution state. It can return one of the following states: - * - *

    - *
  • NEW: The callable has not yet been submitted for execution. - *
  • TERMINATED: The callable has finished execution, either successfully or with an exception. - *
  • The actual state of the thread running the callable (e.g., `RUNNING`, `WAITING`): - *

    If a thread is currently executing the callable, this state reflects the thread's lifecycle. - *

- * - * @return the current state of the callable execution, as described above - */ + /** + * Gets the current execution state of the wrapped callable. + * + * This method examines the internal state and thread reference to determine the current execution state. It can return one of the following states: + * + *
    + *
  • NEW: The callable has not yet been submitted for execution. + *
  • TERMINATED: The callable has finished execution, either successfully or with an exception. + *
  • The actual state of the thread running the callable (e.g., `RUNNING`, `WAITING`): + *

    If a thread is currently executing the callable, this state reflects the thread's lifecycle. + *

+ * + * @return the current state of the callable execution, as described above + */ public State getState() { if(finished) { //the thread is no longer working on this runnable return State.TERMINATED; @@ -226,14 +226,14 @@ public T getResult() { return result; } - /** - * Gets the exception thrown by the wrapped callable, if any. - *

- * This method returns the exception that was thrown during the execution of the wrapped callable, or null if no exception - * occurred. - * - * @return the exception thrown by the wrapped callable, or null if no exception occurred - */ + /** + * Gets the exception thrown by the wrapped callable, if any. + *

+ * This method returns the exception that was thrown during the execution of the wrapped callable, or null if no exception + * occurred. + * + * @return the exception thrown by the wrapped callable, or null if no exception occurred + */ public Throwable getThrown() { return thrown; } @@ -247,6 +247,6 @@ protected void shutdown(Shutdown shutdown) { finished = true; thrown = shutdown; } - } + } } From dba04ff2d913e3f92fa914c2d1241e324ffb6b5a Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sat, 31 Jan 2026 17:29:46 -0500 Subject: [PATCH 20/22] Update gradle to version 9.3.1 --- build.gradle | 19 +++++++++------- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 46175 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 27 +++++++++++------------ gradlew.bat | 25 +++++++++++---------- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/build.gradle b/build.gradle index de7e3fd..8e9136e 100644 --- a/build.gradle +++ b/build.gradle @@ -6,8 +6,11 @@ plugins { id 'name.valery1707.kaitai' version '0.1.3' } -sourceCompatibility = '21' -targetCompatibility = '21' +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} sourceSets.main.java.srcDirs += 'build/generated/kaitai' @@ -41,17 +44,17 @@ dependencies { testRuntimeOnly group: 'org.junit.jupiter', name :'junit-jupiter-engine', version: '5.10.2' } - -tasks.eclipse { - dependsOn 'init' -} -tasks.init { - finalizedBy 'kaitai' +tasks.compileJava { + dependsOn tasks.kaitai } tasks.kaitai.doFirst { deleteRecursively(output.toPath()); } +tasks.kaitai { + finalizedBy 'eclipse' +} + kaitai { url = new URL('https://github.com/kaitai-io/kaitai_struct_compiler/releases/download/0.10/kaitai-struct-compiler-0.10.zip') packageName = 'com.gamebuster19901.excite.modding.game.file.kaitai' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..61285a659d17295f1de7c53e24fdf13ad755c379 100644 GIT binary patch literal 46175 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

pVX@HRij*jw$g4?}Pn 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 literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ac72c34..37f78a6 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -145,7 +146,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +154,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -170,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -202,16 +202,15 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 15c3111b093dd98c908746676db79df3790ea493 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sat, 31 Jan 2026 17:30:25 -0500 Subject: [PATCH 21/22] Ignore build reports --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index af9628a..8631840 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ build/classes build/libs build/resources/ build/tmp +build/reports #JVM hs_err_pid*.log From 14fda949e7ed53c4094649818c1e4f2d71e12586 Mon Sep 17 00:00:00 2001 From: Gamebuster19901 Date: Sat, 31 Jan 2026 17:30:44 -0500 Subject: [PATCH 22/22] Update Excite to version 0.8.0.0 --- build.gradle | 13 +++---------- .../excite/modding/quicklz/QuickLZ.java | 6 +++++- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/build.gradle b/build.gradle index 8e9136e..713da10 100644 --- a/build.gradle +++ b/build.gradle @@ -27,16 +27,9 @@ repositories { } dependencies { - shadow('com.thegamecommunity:excite') { - version { - branch = 'develop-extract' - } - } - implementation('com.thegamecommunity:excite') { - version { - branch = 'develop-extract' - } - } + shadow group: 'com.thegamecommunity', name:'excite', version: '0.8.0.0' + implementation group: 'com.thegamecommunity', name:'excite', version: '0.8.0.0' + shadow group: 'org.apache.commons', name: 'commons-text', version: '1.11.0' shadow group: 'io.kaitai', name: 'kaitai-struct-runtime', version: '0.10' diff --git a/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java b/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java index eb79f0f..828a23b 100644 --- a/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java +++ b/src/main/java/com/gamebuster19901/excite/modding/quicklz/QuickLZ.java @@ -9,7 +9,11 @@ public class QuickLZ { static { if(!ForeignDependencies.EQUICKLZ.isAvailable()) { - ForeignDependencies.downloadAndCompileAllDeps(); + try { + ForeignDependencies.downloadAndCompileAllDeps(); + } catch (IOException | InterruptedException | LinkageError e) { + throw new RuntimeException(e); + } } }