Skip to content

Commit 893a1a0

Browse files
committed
Fix bugs
1 parent 9bc0f5c commit 893a1a0

4 files changed

Lines changed: 173 additions & 69 deletions

File tree

src/main/java/io/github/pixelclover/uview/core/SettingsManager.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
import java.util.List;
77
import java.util.prefs.Preferences;
88

9+
/**
10+
* Manages persistent application settings using the Java Preferences API. This class handles
11+
* storing and retrieving user preferences like the last used directory and recently opened files.
12+
*/
913
public class SettingsManager {
1014

1115
private static final int MAX_RECENT_FILES = 5;
@@ -14,8 +18,22 @@ public class SettingsManager {
1418

1519
private final Preferences prefs;
1620

21+
/**
22+
* Constructs a settings manager using the default application preferences node. This is intended
23+
* for use by the main application.
24+
*/
1725
public SettingsManager() {
18-
this.prefs = Preferences.userNodeForPackage(App.class);
26+
this(Preferences.userNodeForPackage(App.class));
27+
}
28+
29+
/**
30+
* Constructs a settings manager using a specific preferences node. This is intended for testing
31+
* to allow for dependency injection and isolation.
32+
*
33+
* @param prefs The preferences node to use for storing and retrieving settings.
34+
*/
35+
public SettingsManager(Preferences prefs) {
36+
this.prefs = prefs;
1937
}
2038

2139
public File getLastDirectory() {

src/main/java/io/github/pixelclover/uview/gui/MainWindow.java

Lines changed: 138 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
import com.formdev.flatlaf.extras.FlatSVGIcon;
44
import io.github.pixelclover.uview.App;
5+
import io.github.pixelclover.uview.core.PackageManager;
56
import io.github.pixelclover.uview.core.SettingsManager;
6-
import java.awt.*;
7+
import io.github.pixelclover.uview.model.UnityAsset;
8+
import java.awt.BorderLayout;
9+
import java.awt.Component;
10+
import java.awt.Cursor;
11+
import java.awt.Desktop;
12+
import java.awt.Dimension;
713
import java.awt.datatransfer.DataFlavor;
814
import java.awt.datatransfer.Transferable;
915
import java.awt.event.MouseAdapter;
@@ -14,9 +20,28 @@
1420
import java.io.IOException;
1521
import java.io.InputStream;
1622
import java.nio.file.Path;
23+
import java.util.HashMap;
1724
import java.util.List;
25+
import java.util.Map;
1826
import java.util.Properties;
19-
import javax.swing.*;
27+
import javax.swing.BorderFactory;
28+
import javax.swing.Box;
29+
import javax.swing.BoxLayout;
30+
import javax.swing.Icon;
31+
import javax.swing.JDialog;
32+
import javax.swing.JFileChooser;
33+
import javax.swing.JFrame;
34+
import javax.swing.JLabel;
35+
import javax.swing.JMenu;
36+
import javax.swing.JMenuBar;
37+
import javax.swing.JMenuItem;
38+
import javax.swing.JOptionPane;
39+
import javax.swing.JPanel;
40+
import javax.swing.JSeparator;
41+
import javax.swing.JTabbedPane;
42+
import javax.swing.SwingWorker;
43+
import javax.swing.Timer;
44+
import javax.swing.TransferHandler;
2045
import javax.swing.event.MenuEvent;
2146
import javax.swing.event.MenuListener;
2247
import javax.swing.filechooser.FileNameExtensionFilter;
@@ -26,6 +51,11 @@ public class MainWindow extends JFrame {
2651

2752
private final SettingsManager settingsManager = new SettingsManager();
2853
private final JTabbedPane tabbedPane;
54+
55+
// --- ADDED: Maps to track open editor/viewer frames ---
56+
private final Map<String, JFrame> openAssetViewers = new HashMap<>();
57+
private final Map<String, JDialog> openMetaEditors = new HashMap<>();
58+
2959
private JMenuItem saveMenuItem;
3060
private JMenuItem saveAsMenuItem;
3161
private JMenuItem closeMenuItem;
@@ -63,11 +93,104 @@ public void windowClosing(WindowEvent e) {
6393
updateState();
6494
}
6595

66-
private static String formatSize(long bytes) {
67-
if (bytes < 1024) return bytes + " B";
68-
int exp = (int) (Math.log(bytes) / Math.log(1024));
69-
char pre = "KMGTPE".charAt(exp - 1);
70-
return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
96+
// --- ADDED: Method to show the asset viewer ---
97+
/**
98+
* Shows the asset viewer for a given asset. If a viewer for this asset is already open, it brings
99+
* that window to the front. Otherwise, it creates a new viewer.
100+
*
101+
* @param asset The asset to view.
102+
* @param packageManager The package manager instance.
103+
* @param onSaveCallback A callback to run when the asset is modified.
104+
*/
105+
public void showAssetViewer(
106+
UnityAsset asset, PackageManager packageManager, Runnable onSaveCallback) {
107+
String assetPath = asset.assetPath();
108+
if (openAssetViewers.containsKey(assetPath)) {
109+
JFrame frame = openAssetViewers.get(assetPath);
110+
frame.toFront();
111+
frame.requestFocus();
112+
} else {
113+
AssetViewerFrame viewer = new AssetViewerFrame(this, asset, packageManager, onSaveCallback);
114+
viewer.addWindowListener(
115+
new WindowAdapter() {
116+
@Override
117+
public void windowClosed(WindowEvent e) {
118+
openAssetViewers.remove(assetPath);
119+
}
120+
});
121+
openAssetViewers.put(assetPath, viewer);
122+
viewer.setVisible(true);
123+
}
124+
}
125+
126+
// --- ADDED: Method to show the meta editor ---
127+
/**
128+
* Shows the meta editor for a given asset. If an editor for this asset is already open, it brings
129+
* that window to the front. Otherwise, it creates a new editor.
130+
*
131+
* @param asset The asset whose metadata is to be edited.
132+
* @param packageManager The package manager instance.
133+
* @param onSaveCallback A callback to run when the metadata is modified.
134+
*/
135+
public void showMetaEditor(
136+
UnityAsset asset, PackageManager packageManager, Runnable onSaveCallback) {
137+
String assetPath = asset.assetPath();
138+
if (openMetaEditors.containsKey(assetPath)) {
139+
JDialog dialog = openMetaEditors.get(assetPath);
140+
dialog.toFront();
141+
dialog.requestFocus();
142+
} else {
143+
MetaEditorFrame editor = new MetaEditorFrame(this, asset, packageManager, onSaveCallback);
144+
editor.addWindowListener(
145+
new WindowAdapter() {
146+
@Override
147+
public void windowClosed(WindowEvent e) {
148+
openMetaEditors.remove(assetPath);
149+
}
150+
});
151+
openMetaEditors.put(assetPath, editor);
152+
editor.setVisible(true);
153+
}
154+
}
155+
156+
private class FileDropHandler extends TransferHandler {
157+
@Override
158+
public boolean canImport(TransferSupport support) {
159+
if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
160+
return false;
161+
}
162+
try {
163+
Transferable t = support.getTransferable();
164+
List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
165+
for (File file : files) {
166+
if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) {
167+
return true; // Accept drop if at least one unitypackage is present
168+
}
169+
}
170+
} catch (Exception e) {
171+
return false;
172+
}
173+
return false;
174+
}
175+
176+
@Override
177+
public boolean importData(TransferSupport support) {
178+
if (!canImport(support)) {
179+
return false;
180+
}
181+
try {
182+
Transferable t = support.getTransferable();
183+
List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
184+
for (File file : files) {
185+
if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) {
186+
openPackage(file);
187+
}
188+
}
189+
return true;
190+
} catch (Exception e) {
191+
return false;
192+
}
193+
}
71194
}
72195

73196
private JPanel createStatusBar() {
@@ -100,6 +223,13 @@ private void updateMemoryUsage() {
100223
memoryUsageLabel.setText(String.format("Mem: %s", formatSize(usedMemory)));
101224
}
102225

226+
private static String formatSize(long bytes) {
227+
if (bytes < 1024) return bytes + " B";
228+
int exp = (int) (Math.log(bytes) / Math.log(1024));
229+
char pre = "KMGTPE".charAt(exp - 1);
230+
return String.format("%.1f %sB", bytes / Math.pow(1024, exp), pre);
231+
}
232+
103233
private JMenuBar createMenuBar() {
104234
JMenuBar menuBar = new JMenuBar();
105235
JMenu fileMenu = new JMenu("File");
@@ -198,7 +328,7 @@ public void mouseClicked(MouseEvent e) {
198328
private String getAppVersion() {
199329
try (InputStream is =
200330
App.class.getResourceAsStream(
201-
"/META-INF/maven/io.github.pixelclover.uview/uview/pom.properties")) {
331+
"/META-INF/maven/io.github.pixelclover/uview/pom.properties")) {
202332
if (is == null) return "N/A";
203333
Properties props = new Properties();
204334
props.load(is);
@@ -464,44 +594,4 @@ private void setWorking(boolean working, String status) {
464594
statusLabel.setText(status);
465595
}
466596
}
467-
468-
private class FileDropHandler extends TransferHandler {
469-
@Override
470-
public boolean canImport(TransferSupport support) {
471-
if (!support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
472-
return false;
473-
}
474-
try {
475-
Transferable t = support.getTransferable();
476-
List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
477-
for (File file : files) {
478-
if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) {
479-
return true; // Accept drop if at least one unitypackage is present
480-
}
481-
}
482-
} catch (Exception e) {
483-
return false;
484-
}
485-
return false;
486-
}
487-
488-
@Override
489-
public boolean importData(TransferSupport support) {
490-
if (!canImport(support)) {
491-
return false;
492-
}
493-
try {
494-
Transferable t = support.getTransferable();
495-
List<File> files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
496-
for (File file : files) {
497-
if (file.isFile() && file.getName().toLowerCase().endsWith(".unitypackage")) {
498-
openPackage(file);
499-
}
500-
}
501-
return true;
502-
} catch (Exception e) {
503-
return false;
504-
}
505-
}
506-
}
507597
}

src/main/java/io/github/pixelclover/uview/gui/PackageViewPanel.java

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ private void extractInBackground(TreeEntry entry, Path outputDir) {
295295
new SwingWorker<>() {
296296
@Override
297297
protected Void doInBackground() throws Exception {
298-
Collection<io.github.pixelclover.uview.model.UnityAsset> assetsToExtract;
298+
Collection<UnityAsset> assetsToExtract;
299299
String pathPrefixToStrip;
300300
if (entry instanceof TreeEntry.AssetEntry assetEntry) {
301301
assetsToExtract = List.of(assetEntry.asset());
@@ -340,13 +340,11 @@ private void handleDoubleClick() {
340340
Runnable onSaveCallback =
341341
() -> {
342342
refreshTree();
343-
if (owner instanceof MainWindow) {
344-
((MainWindow) owner).updateState();
345-
}
343+
// The owner is always the MainWindow, so this cast is safe.
344+
((MainWindow) owner).updateState();
346345
};
347-
AssetViewerFrame viewer =
348-
new AssetViewerFrame((JFrame) owner, entry.asset(), packageManager, onSaveCallback);
349-
viewer.setVisible(true);
346+
// --- MODIFIED: Call the manager method on MainWindow ---
347+
((MainWindow) owner).showAssetViewer(entry.asset(), packageManager, onSaveCallback);
350348
}
351349
}
352350

@@ -363,14 +361,11 @@ private void editSelectedMetaFile() {
363361
Runnable onSaveCallback =
364362
() -> {
365363
refreshTree();
366-
if (owner instanceof MainWindow) {
367-
((MainWindow) owner).updateState();
368-
}
364+
((MainWindow) owner).updateState();
369365
};
370366

371-
MetaEditorFrame editor =
372-
new MetaEditorFrame((JFrame) owner, entry.asset(), packageManager, onSaveCallback);
373-
editor.setVisible(true);
367+
// --- MODIFIED: Call the manager method on MainWindow ---
368+
((MainWindow) owner).showMetaEditor(entry.asset(), packageManager, onSaveCallback);
374369
}
375370
}
376371

src/test/java/io/github/pixelclover/uview/core/SettingsManagerTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
package io.github.pixelclover.uview.core;
22

3-
import static org.junit.jupiter.api.Assertions.assertEquals;
4-
import static org.junit.jupiter.api.Assertions.assertTrue;
3+
import static org.junit.jupiter.api.Assertions.*;
54

6-
import io.github.pixelclover.uview.App;
75
import java.io.File;
86
import java.io.IOException;
97
import java.nio.file.Path;
@@ -23,9 +21,12 @@ class SettingsManagerTest {
2321

2422
@BeforeEach
2523
void setUp() throws BackingStoreException {
26-
settingsManager = new SettingsManager();
27-
prefs = Preferences.userNodeForPackage(App.class);
28-
// FIX: Clear preferences BEFORE each test to ensure a clean state.
24+
// Use a unique preference node path just for this test.
25+
// This isolates the test from the actual application settings.
26+
prefs = Preferences.userRoot().node("/io/github/pixelclover/uview/test_prefs");
27+
settingsManager = new SettingsManager(prefs); // Inject the test-specific preferences
28+
29+
// Clear preferences BEFORE each test to ensure a clean state.
2930
prefs.clear();
3031
}
3132

@@ -37,7 +38,7 @@ void tearDown() throws BackingStoreException {
3738

3839
@Test
3940
void getLastDirectory_shouldReturnDefaultWhenNotSet() throws IOException {
40-
// FIX: The fallback is `new File(".")`. We should compare its canonical path.
41+
// The fallback is `new File(".")`. We should compare its canonical path.
4142
File expectedDefault = new File(".");
4243
assertEquals(
4344
expectedDefault.getCanonicalPath(), settingsManager.getLastDirectory().getCanonicalPath());

0 commit comments

Comments
 (0)