From 66b7b44ac8dd510225105e20537234e8096e930b Mon Sep 17 00:00:00 2001 From: Henry Le Grys Date: Mon, 29 Mar 2021 01:20:32 +0100 Subject: [PATCH] Flesh out curse mod manager --- .../com/skcraft/plugin/curse/CurseApi.java | 46 ++- .../com/skcraft/plugin/curse/CurseMod.java | 4 + .../plugin/curse/creator/CurseModsDialog.java | 267 ++++++++++++++++-- .../plugin/curse/creator/PackModScanner.java | 32 +++ .../plugin/curse/model/CurseProject.java | 40 ++- .../curse/model/CurseSearchResults.java | 27 ++ .../skcraft/plugin/curse/model/LoadedMod.java | 19 ++ .../plugin/curse/model/LoadedModList.java | 43 +++ .../plugin/curse/model/ProjectHolder.java | 5 + .../controller/PackManagerController.java | 3 +- .../launcher/creator/plugin/MenuContext.java | 12 + .../launcher/creator/plugin/PluginMenu.java | 8 +- 12 files changed, 482 insertions(+), 24 deletions(-) create mode 100644 builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/PackModScanner.java create mode 100644 builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseSearchResults.java create mode 100644 builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedMod.java create mode 100644 builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedModList.java create mode 100644 builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/ProjectHolder.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/MenuContext.java diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseApi.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseApi.java index 8c45bf2..8b3a365 100644 --- a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseApi.java +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseApi.java @@ -1,5 +1,49 @@ package com.skcraft.plugin.curse; -public class CurseApi { +import com.fasterxml.jackson.core.type.TypeReference; +import com.skcraft.launcher.util.HttpRequest; +import com.skcraft.plugin.curse.model.CurseProject; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import static com.skcraft.launcher.util.HttpRequest.url; + +public class CurseApi { + private static final String CURSE_ADDON_SEARCH = "https://addons-ecs.forgesvc.net/api/v2/addon/search?"; + private static final String MINECRAFT_GAME_ID = "432"; + private static final String CURSE_MOD_SECTION_ID = "6"; + + public static List searchForProjects(String query, String gameVersion) throws IOException, InterruptedException { + HttpRequest.Form form = HttpRequest.Form.form(); + form.add("gameID", MINECRAFT_GAME_ID); + form.add("sectionId", CURSE_MOD_SECTION_ID); // Filter to mods only + form.add("gameVersion", gameVersion); + form.add("searchFilter", query); + + try { + URI uri = new URI(CURSE_ADDON_SEARCH + form.toString()); + + return HttpRequest.get(uri.toURL()) + .execute() + .expectResponseCode(200) + .returnContent() + .asJson(new TypeReference>() {}); + } catch (URISyntaxException | MalformedURLException e) { + // Shhhh. + throw new RuntimeException(e); + } + } + + public static CurseProject getById(String projectId) throws IOException, InterruptedException { + String addonUrl = String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%s", projectId); + return HttpRequest.get(url(addonUrl)) + .execute() + .expectResponseCode(200) + .returnContent() + .asJson(CurseProject.class); + } } diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseMod.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseMod.java index f033c8c..beb4685 100644 --- a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseMod.java +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/CurseMod.java @@ -2,9 +2,13 @@ package com.skcraft.plugin.curse; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.skcraft.launcher.model.modpack.Feature; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data +@AllArgsConstructor +@NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class CurseMod { private String projectId; diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java index fa82f2e..a6842bc 100644 --- a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/CurseModsDialog.java @@ -1,54 +1,275 @@ package com.skcraft.plugin.curse.creator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.skcraft.concurrency.DefaultProgress; import com.skcraft.launcher.creator.model.creator.Pack; +import com.skcraft.launcher.creator.plugin.MenuContext; import com.skcraft.launcher.creator.plugin.PluginMenu; +import com.skcraft.launcher.dialog.ProgressDialog; +import com.skcraft.launcher.swing.EmptyIcon; import com.skcraft.launcher.swing.LinedBoxPanel; -import com.skcraft.plugin.curse.model.CurseProject; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.SwingExecutor; +import com.skcraft.plugin.curse.CurseApi; +import com.skcraft.plugin.curse.model.*; +import lombok.RequiredArgsConstructor; +import lombok.extern.java.Log; +import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.function.Consumer; +import static com.skcraft.launcher.util.HttpRequest.url; + +@Log public class CurseModsDialog extends JDialog { - private final LinedBoxPanel panel = new LinedBoxPanel(false).fullyPadded(); - private final JList searchResults = new JList<>(); - private final JList selected = new JList<>(); - private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, searchResults, selected); - private final JButton addMod = new JButton("Add Mod >>"); - private final JButton removeMod = new JButton("Remove Mod"); + private final CurseSearchResults searchResults = new CurseSearchResults(); + private final LoadedModList modList = new LoadedModList(); + private final JPanel panel = new JPanel(new BorderLayout(0, 5)); + private final JTextField searchBox = new JTextField(); + private final JButton searchButton = new JButton("Search"); + private final JList searchPane = new JList<>(searchResults); + private final JList selectedPane = new JList<>(modList); + private final JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(searchPane), new JScrollPane(selectedPane)); + private final JButton addMod = new JButton("Add Mods >>"); + private final JButton removeMod = new JButton("Remove Mods"); + private final JButton done = new JButton("Done"); + + private final CurseProjectListRenderer projectRenderer = new CurseProjectListRenderer(); + + private final ListeningExecutorService executor; + private final ObjectMapper mapper; private final Pack pack; + private final File curseModsDir; - public CurseModsDialog(Window owner, Pack pack) { + public CurseModsDialog(Window owner, ListeningExecutorService executor, ObjectMapper mapper, Pack pack, List currentMods) { super(owner); + this.executor = executor; + this.mapper = mapper; this.pack = pack; + this.curseModsDir = new File(pack.getDirectory(), "cursemods"); + + modList.addAll(currentMods); initComponents(); - setMinimumSize(new Dimension(500, 250)); + setMinimumSize(new Dimension(500, 450)); pack(); setLocationRelativeTo(owner); } private void initComponents() { - splitPane.setDividerLocation(0.5D); + searchPane.setCellRenderer(projectRenderer); + searchPane.setLayoutOrientation(JList.VERTICAL); + searchPane.setVisibleRowCount(0); + searchPane.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + selectedPane.setCellRenderer(projectRenderer); + selectedPane.setLayoutOrientation(JList.VERTICAL); + selectedPane.setVisibleRowCount(0); + selectedPane.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + splitPane.setDividerLocation(250); splitPane.setDividerSize(10); + LinedBoxPanel searchBarPanel = new LinedBoxPanel(true); + searchBarPanel.addElement(searchBox); + searchBarPanel.addElement(searchButton); + searchBarPanel.setAlignmentX(CENTER_ALIGNMENT); + LinedBoxPanel buttonsPanel = new LinedBoxPanel(true); buttonsPanel.addElement(addMod); buttonsPanel.addGlue(); + buttonsPanel.addElement(done); + buttonsPanel.addGlue(); buttonsPanel.addElement(removeMod); + buttonsPanel.setAlignmentX(CENTER_ALIGNMENT); - panel.addElement(splitPane); - panel.addElement(buttonsPanel); + panel.add(searchBarPanel, BorderLayout.NORTH); + panel.add(Box.createVerticalStrut(5)); + panel.add(splitPane, BorderLayout.CENTER); + panel.add(buttonsPanel, BorderLayout.SOUTH); add(panel); - addMod.addActionListener(e -> { + searchBox.addActionListener(e -> search()); + searchButton.addActionListener(e -> search()); + addMod.addActionListener(e -> { + for (CurseProject project : searchPane.getSelectedValuesList()) { + GameVersionFile forVersion = project.findFileForVersion(pack.getCachedConfig().getGameVersion()); + + if (forVersion == null) { + SwingHelper.showErrorDialog( + getOwner(), + String.format("Mod %s isn't available for this version.", project.getName()), + "Mod Unavailable" + ); + return; + } + + LoadedMod loadedMod = project.toLoadedMod(forVersion); + modList.add(loadedMod); + + executor.submit(() -> { + File target = loadedMod.getDiskLocation(curseModsDir); + + log.info(String.format("Saving mod %s", target.getName())); + mapper.writeValue(target, loadedMod.getMod()); + return null; + }); + } }); + + removeMod.addActionListener(e -> { + for (LoadedMod mod : selectedPane.getSelectedValuesList()) { + modList.remove(mod); + + File target = mod.getDiskLocation(curseModsDir); + log.info(String.format("Removing mod %s", target.getName())); + if (!target.delete()) { + SwingHelper.showErrorDialog(getOwner(), String.format("Failed to delete %s", target), "I/O error"); + } + } + }); + + done.addActionListener(e -> dispose()); + } + + @Override + public void dispose() { + // Let's make sure the image cache is emptied + projectRenderer.clearCache(); + super.dispose(); + } + + private void search() { + String query = searchBox.getText(); + ListenableFuture> future = executor.submit(() -> + CurseApi.searchForProjects(query, pack.getCachedConfig().getGameVersion())); + + Futures.addCallback(future, new FutureCallback>() { + @Override + public void onSuccess(List result) { + projectRenderer.clearCacheIfBig(); + searchResults.updateResults(result); + } + + @Override + public void onFailure(Throwable t) { + SwingHelper.showErrorDialog(getOwner(), t.getMessage(), "Search failure", t); + } + }, SwingExecutor.INSTANCE); + } + + @RequiredArgsConstructor + private static class ImageWorker extends SwingWorker { + private final String imgUrl; + private final Consumer consumer; + + @Override + protected BufferedImage doInBackground() throws Exception { + return ImageIO.read(url(imgUrl)); + } + + @Override + protected void done() { + try { + consumer.accept(this.get()); + } catch (InterruptedException | ExecutionException e) { + SwingHelper.showErrorDialog(null, e.getMessage(), "Worker error", e); + } + } + + public static void downloadImage(String imageUrl, Consumer consumer) { + ImageWorker worker = new ImageWorker(imageUrl, consumer); + worker.execute(); + } + } + + private static class CurseProjectListRenderer extends JLabel implements ListCellRenderer { + private HashMap cache; + + public CurseProjectListRenderer() { + this.cache = new HashMap<>(); + } + + private BufferedImage getCachedIcon(String imgUrl, Runnable cb) { + if (!cache.containsKey(imgUrl)) { + ImageWorker.downloadImage(imgUrl, img -> { + cache.put(imgUrl, img); + cb.run(); + }); + + return null; + } + + return cache.get(imgUrl); + } + + public void clearCache() { + this.cache.clear(); + } + + public void clearCacheIfBig() { + if (this.cache.size() > 200) { + this.cache.clear(); + } + } + + private static Dimension getScaledDimensions(int imageWidth, int imageHeight) { + double widthRatio = 32.0D / imageWidth; + double heightRatio = 32.0D / imageHeight; + double ratio = Math.min(widthRatio, heightRatio); + + int newWidth = (int) Math.floor(imageWidth * ratio); + int newHeight = (int) Math.floor(imageHeight * ratio); + + return new Dimension(newWidth, newHeight); + } + + @Override + public Component getListCellRendererComponent(JList list, ProjectHolder value, int index, boolean isSelected, boolean cellHasFocus) { + CurseProject project = value.getProject(); + + setText(project.getName()); + setToolTipText(project.getSummary()); + + BufferedImage ref = getCachedIcon(project.getDefaultIcon().getThumbnailUrl(), list::repaint); + if (ref != null) { + Dimension scaled = getScaledDimensions(ref.getWidth(), ref.getHeight()); + setIcon(new ImageIcon(ref.getScaledInstance(scaled.width, scaled.height, Image.SCALE_SMOOTH))); + } else { + setIcon(new EmptyIcon(32 ,32)); + } + + if (isSelected) { + setOpaque(true); + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } else { + setOpaque(false); + setForeground(list.getForeground()); + } + + return this; + } } public static class Menu implements PluginMenu { + private final ObjectMapper mapper = new ObjectMapper(); + @Override public String getTitle() { return "Add Mods"; @@ -60,9 +281,23 @@ public class CurseModsDialog extends JDialog { } @Override - public void onOpen(Window owner, ActionEvent e, Pack pack) { - CurseModsDialog dialog = new CurseModsDialog(owner, pack); - dialog.setVisible(true); + public void onOpen(MenuContext ctx, ActionEvent e, Pack pack) { + PackModScanner scanner = new PackModScanner(mapper); + + ListenableFuture future = ctx.getExecutor().submit(() -> { + scanner.walk(new File(pack.getDirectory(), "cursemods")); + return null; + }); + + ProgressDialog.showProgress(ctx.getOwner(), future, + new DefaultProgress(-1, "Loading mods..."),"Curse", "Contacting Curse API"); + SwingHelper.addErrorDialogCallback(ctx.getOwner(), future); + + future.addListener(() -> { + CurseModsDialog dialog = new CurseModsDialog(ctx.getOwner(), ctx.getExecutor(), mapper, pack, scanner.getResult()); + dialog.setTitle(getTitle()); + dialog.setVisible(true); + }, SwingExecutor.INSTANCE); } } } diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/PackModScanner.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/PackModScanner.java new file mode 100644 index 0000000..f92515c --- /dev/null +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/creator/PackModScanner.java @@ -0,0 +1,32 @@ +package com.skcraft.plugin.curse.creator; + +import com.beust.jcommander.internal.Lists; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.skcraft.launcher.builder.DirectoryWalker; +import com.skcraft.plugin.curse.CurseApi; +import com.skcraft.plugin.curse.CurseMod; +import com.skcraft.plugin.curse.model.CurseProject; +import com.skcraft.plugin.curse.model.LoadedMod; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +@RequiredArgsConstructor +public class PackModScanner extends DirectoryWalker { + private final ObjectMapper mapper; + + @Getter private final List result = Lists.newArrayList(); + + @Override + @SneakyThrows + protected void onFile(File file, String relPath) throws IOException { + CurseMod mod = mapper.readValue(file, CurseMod.class); + CurseProject project = CurseApi.getById(mod.getProjectId()); + + result.add(new LoadedMod(mod, project)); + } +} diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseProject.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseProject.java index d9d05b2..5a6635d 100644 --- a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseProject.java +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseProject.java @@ -1,19 +1,27 @@ package com.skcraft.plugin.curse.model; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.skcraft.plugin.curse.CurseMod; import lombok.Data; import java.util.List; @Data @JsonIgnoreProperties(ignoreUnknown = true) -public class CurseProject { +public class CurseProject implements ProjectHolder { private String id; private String name; private String websiteUrl; private String summary; private String slug; private List gameVersionLatestFiles; + private List attachments; + + @Override + public CurseProject getProject() { + return this; + } public GameVersionFile findFileForVersion(String version) { for (GameVersionFile file : gameVersionLatestFiles) { @@ -24,4 +32,34 @@ public class CurseProject { return null; } + + public LoadedMod toLoadedMod(GameVersionFile versionFile) { + CurseMod mod = new CurseMod(id, versionFile.getProjectFileId(), null); + return new LoadedMod(mod, this); + } + + public Attachment getDefaultIcon() { + for (Attachment attachment : attachments) { + if (attachment.isDefault()) { + return attachment; + } + } + + if (!attachments.isEmpty()) { + return attachments.get(0); + } + + return null; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Attachment { + private String title; + private String thumbnailUrl; + private String url; + + @JsonProperty("isDefault") + private boolean isDefault; + } } diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseSearchResults.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseSearchResults.java new file mode 100644 index 0000000..6757707 --- /dev/null +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/CurseSearchResults.java @@ -0,0 +1,27 @@ +package com.skcraft.plugin.curse.model; + +import com.beust.jcommander.internal.Lists; + +import javax.swing.*; +import java.util.List; + +public class CurseSearchResults extends AbstractListModel { + private List results = Lists.newArrayList(); + + @Override + public int getSize() { + return results.size(); + } + + @Override + public CurseProject getElementAt(int index) { + return results.get(index); + } + + public void updateResults(List newResults) { + int before = results.size(); + + results = newResults; + this.fireContentsChanged(this, 0, before - 1); + } +} diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedMod.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedMod.java new file mode 100644 index 0000000..e1edd39 --- /dev/null +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedMod.java @@ -0,0 +1,19 @@ +package com.skcraft.plugin.curse.model; + +import com.skcraft.plugin.curse.CurseMod; +import lombok.Data; + +import java.io.File; + +/** + * Represents mod metadata cached in-memory for the purpose of rendering the "Add Mods" screen. + */ +@Data +public class LoadedMod implements ProjectHolder { + private final CurseMod mod; + private final CurseProject project; + + public File getDiskLocation(File curseModsDir) { + return new File(curseModsDir, String.format("%s.json", project.getSlug())); + } +} diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedModList.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedModList.java new file mode 100644 index 0000000..0c26551 --- /dev/null +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/LoadedModList.java @@ -0,0 +1,43 @@ +package com.skcraft.plugin.curse.model; + +import com.google.common.collect.Lists; + +import javax.swing.*; +import java.util.Collection; +import java.util.List; + +public class LoadedModList extends AbstractListModel { + private List mods = Lists.newArrayList(); + + @Override + public int getSize() { + return mods.size(); + } + + @Override + public LoadedMod getElementAt(int index) { + return mods.get(index); + } + + public void addAll(Collection other) { + int start = mods.size(); + mods.addAll(other); + fireIntervalAdded(this, start, start + other.size()); + } + + public void add(LoadedMod mod) { + mods.add(mod); + + int idx = mods.size() - 1; + fireIntervalAdded(this, idx, idx); + } + + public void remove(LoadedMod mod) { + int index = mods.indexOf(mod); + + if (index > -1) { + mods.remove(index); + fireIntervalRemoved(this, index, index); + } + } +} diff --git a/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/ProjectHolder.java b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/ProjectHolder.java new file mode 100644 index 0000000..fc4d2fb --- /dev/null +++ b/builder-plugin-curse/src/main/java/com/skcraft/plugin/curse/model/ProjectHolder.java @@ -0,0 +1,5 @@ +package com.skcraft.plugin.curse.model; + +public interface ProjectHolder { + CurseProject getProject(); +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java index 32891dc..270b113 100644 --- a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java @@ -33,6 +33,7 @@ import com.skcraft.launcher.creator.model.creator.*; import com.skcraft.launcher.creator.model.swing.PackTableModel; import com.skcraft.launcher.creator.plugin.CreatorPluginWrapper; import com.skcraft.launcher.creator.plugin.CreatorToolsPlugin; +import com.skcraft.launcher.creator.plugin.MenuContext; import com.skcraft.launcher.creator.plugin.PluginMenu; import com.skcraft.launcher.creator.server.TestServer; import com.skcraft.launcher.creator.server.TestServerBuilder; @@ -335,7 +336,7 @@ public class PackManagerController { return; } - menu.onOpen(frame, e, pack.orNull()); + menu.onOpen(new MenuContext(frame, creator.getExecutor()), e, pack.orNull()); }); submenu.add(item); diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/MenuContext.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/MenuContext.java new file mode 100644 index 0000000..e096008 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/MenuContext.java @@ -0,0 +1,12 @@ +package com.skcraft.launcher.creator.plugin; + +import com.google.common.util.concurrent.ListeningExecutorService; +import lombok.Data; + +import java.awt.*; + +@Data +public class MenuContext { + private final Window owner; + private final ListeningExecutorService executor; +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/PluginMenu.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/PluginMenu.java index fcf4fbc..631b029 100644 --- a/creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/PluginMenu.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/plugin/PluginMenu.java @@ -2,14 +2,13 @@ package com.skcraft.launcher.creator.plugin; import com.skcraft.launcher.creator.model.creator.Pack; -import java.awt.*; import java.awt.event.ActionEvent; public interface PluginMenu { String getTitle(); /** - * Return true if {@link PluginMenu#onOpen(Window, ActionEvent, Pack)} should only be called + * Return true if {@link PluginMenu#onOpen(MenuContext, ActionEvent, Pack)} should only be called * if the user has selected a pack. * @return True to require a pack, false if you don't need one. */ @@ -17,10 +16,9 @@ public interface PluginMenu { /** * Called when the menu item was clicked. - * - * @param owner Window reference for parenting dialogs. + * @param ctx Context object with various useful objects. * @param e Action event that triggered this call. * @param pack Pack to operate on; guaranteed to be non-null if {@link PluginMenu#requiresPack()} returns true. */ - void onOpen(Window owner, ActionEvent e, Pack pack); + void onOpen(MenuContext ctx, ActionEvent e, Pack pack); }