From e7f767562f79b63943747767e769ae09a9f2e695 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 29 Jul 2023 17:17:49 +0200 Subject: [PATCH 01/23] Limit icon preview to ResourceBitmap subtype IwdRef Prevents ProRef projectile list from displaying preview icons. --- src/org/infinity/gui/TextListPanel.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/gui/TextListPanel.java b/src/org/infinity/gui/TextListPanel.java index 77b29feda..e749f5de6 100644 --- a/src/org/infinity/gui/TextListPanel.java +++ b/src/org/infinity/gui/TextListPanel.java @@ -41,6 +41,7 @@ import org.infinity.NearInfinity; import org.infinity.datatype.AbstractBitmap; +import org.infinity.datatype.IwdRef; import org.infinity.datatype.ResourceBitmap; import org.infinity.datatype.ResourceRef; import org.infinity.icon.Icons; @@ -345,7 +346,8 @@ public Component getListCellRendererComponent(JList list, Object value, int i } else if (value instanceof AbstractBitmap.FormattedData) { // resolving Resource Bitmap final AbstractBitmap.FormattedData fmt = (AbstractBitmap.FormattedData) value; - if (fmt.getParent() != null) { + // Limit icon preview to parent type: IwdRef + if (fmt.getParent() instanceof IwdRef) { final AbstractBitmap bmp = fmt.getParent(); Object o = bmp.getDataOf(fmt.getValue()); if (o instanceof ResourceBitmap.RefEntry) { From fd480aa4703f1be5deebe613e83f14ca012b5325 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 17 Aug 2023 18:45:41 +0400 Subject: [PATCH 02/23] fixed #123 --- src/org/infinity/datatype/ResourceRef.java | 24 ++++++++++++++- .../resource/sound/SoundResource.java | 29 +++++++++++++------ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index 99888d640..a38d648ba 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -40,6 +40,7 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.sound.SoundResource; import org.infinity.util.Misc; import org.infinity.util.io.StreamUtils; @@ -80,6 +81,9 @@ public class ResourceRef extends Datatype /** Button that used to open editor of current selected element in the list. */ private JButton bView; + /** Button that used to play sound of current selected element in the list. */ + private JButton bPlay; + /** * GUI component that lists all available resources that can be set to this resource reference and have edit field for * ability to enter resource reference manually. @@ -117,6 +121,12 @@ public void actionPerformed(ActionEvent event) { if (isEditable(selected)) { new ViewFrame(list.getTopLevelAncestor(), ResourceFactory.getResource(selected.entry)); } + } else if (event.getSource() == bPlay) { + final ResourceRefEntry selected = list.getSelectedValue(); + if (isSound(selected)) { + SoundResource res = (SoundResource) ResourceFactory.getResource(selected.entry); + res.playSound(); + } } } @@ -183,12 +193,15 @@ public void mouseClicked(MouseEvent event) { bView = new JButton("View/Edit", Icons.ICON_ZOOM_16.getIcon()); bView.addActionListener(this); bView.setEnabled(isEditable(list.getSelectedValue())); + bPlay = new JButton("Play", Icons.ICON_PLAY_16.getIcon()); + bPlay.addActionListener(this); + bPlay.setEnabled(isSound(list.getSelectedValue())); list.addListSelectionListener(this); GridBagConstraints gbc = null; JPanel panel = new JPanel(new GridBagLayout()); - gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 2, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, + gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 3, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); panel.add(list, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, @@ -198,6 +211,10 @@ public void mouseClicked(MouseEvent event) { new Insets(3, 6, 3, 0), 0, 0); panel.add(bView, gbc); + gbc = ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 0.0, 1.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, + new Insets(3, 6, 3, 0), 0, 0); + panel.add(bPlay, gbc); + panel.setMinimumSize(Misc.getScaledDimension(DIM_MEDIUM)); panel.setPreferredSize(panel.getMinimumSize()); return panel; @@ -252,6 +269,7 @@ public boolean updateValue(AbstractStruct struct) { @Override public void valueChanged(ListSelectionEvent e) { bView.setEnabled(isEditable(list.getSelectedValue())); + bPlay.setEnabled(isSound(list.getSelectedValue())); } @Override @@ -377,6 +395,10 @@ private boolean isEditable(ResourceRefEntry ref) { return ref != null && ref != NONE && ref.entry != null; } + private boolean isSound(ResourceRefEntry ref) { + return ref != null && ref != NONE && ref.entry != null && ref.entry.getExtension().equalsIgnoreCase("WAV"); + } + private void setValue(String newValue) { final String oldValue = NONE.name.equals(resname) ? null : resname; resname = newValue; diff --git a/src/org/infinity/resource/sound/SoundResource.java b/src/org/infinity/resource/sound/SoundResource.java index 83b3731a8..214a8ee14 100644 --- a/src/org/infinity/resource/sound/SoundResource.java +++ b/src/org/infinity/resource/sound/SoundResource.java @@ -168,8 +168,10 @@ public void searchReferences(Component parent) { @Override public void run() { - bPlay.setIcon(PLAY_ICONS.get(false)); - bStop.setEnabled(true); + if (bPlay != null) { + bPlay.setIcon(PLAY_ICONS.get(false)); + bStop.setEnabled(true); + } if (audioBuffer != null) { final TimerElapsedTask timerTask = new TimerElapsedTask(250L); try { @@ -182,8 +184,10 @@ public void run() { player.stopPlay(); timerTask.stop(); } - bStop.setEnabled(false); - bPlay.setIcon(PLAY_ICONS.get(true)); + if (bPlay != null) { + bStop.setEnabled(false); + bPlay.setIcon(PLAY_ICONS.get(true)); + } } // --------------------- End Interface Runnable --------------------- @@ -294,10 +298,11 @@ private synchronized void setLoaded(boolean b) { if (bPlay != null) { bPlay.setEnabled(b); bPlay.setIcon(PLAY_ICONS.get(true)); + + updateTimeLabel(0); + miConvert.setEnabled(b); + buttonPanel.getControlByType(PROPERTIES).setEnabled(true); } - updateTimeLabel(0); - miConvert.setEnabled(b); - buttonPanel.getControlByType(PROPERTIES).setEnabled(true); } private synchronized void setClosed(boolean b) { @@ -448,16 +453,22 @@ public void stop() { timer.cancel(); timer = null; paused = false; - updateTimeLabel(0L); + if (bPlay != null) { + updateTimeLabel(0L); + } } } @Override public void run() { - if (!paused && timer != null && player != null && player.getDataLine() != null) { + if (!paused && timer != null && player != null && player.getDataLine() != null && bPlay != null) { updateTimeLabel(player.getDataLine().getMicrosecondPosition() / 1000L); } } + } + public void playSound() { + loadAudio(); + new Thread(this).start(); } } From 80a5c68d65070ce2a46a1418a28251b9864e28a0 Mon Sep 17 00:00:00 2001 From: Mark Date: Fri, 18 Aug 2023 15:10:11 +0400 Subject: [PATCH 03/23] introduce isSound in ResourceRef --- src/org/infinity/datatype/ResourceRef.java | 2 +- src/org/infinity/resource/key/ResourceEntry.java | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index a38d648ba..816f9cca2 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -396,7 +396,7 @@ private boolean isEditable(ResourceRefEntry ref) { } private boolean isSound(ResourceRefEntry ref) { - return ref != null && ref != NONE && ref.entry != null && ref.entry.getExtension().equalsIgnoreCase("WAV"); + return ref != null && ref != NONE && ref.entry != null && ref.entry.isSound(); } private void setValue(String newValue) { diff --git a/src/org/infinity/resource/key/ResourceEntry.java b/src/org/infinity/resource/key/ResourceEntry.java index cdf316a13..e67976704 100644 --- a/src/org/infinity/resource/key/ResourceEntry.java +++ b/src/org/infinity/resource/key/ResourceEntry.java @@ -280,4 +280,10 @@ public boolean isVisible() { public abstract ResourceTreeFolder getTreeFolder(); public abstract boolean hasOverride(); + + public boolean isSound() { + String extension = getExtension().toUpperCase(); + + return extension.equals("WAV") || extension.equals("MUS") || extension.equals("ACM"); + } } From 49fe2793d07f9493dbc14a2e980c3e929aad6596 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 3 Sep 2023 11:00:13 +0200 Subject: [PATCH 04/23] Improve sound playback in ResourceRef datatype - Improved placement of sound playback button. - Opened sound resource is properly closed when another resource is selected. - Current playback is stopped when a new sound playback is initiated to prevent overlapping sound playback. TODO: Sound resource should be closed when the parent resource is closed. --- src/org/infinity/datatype/ResourceRef.java | 62 ++++++++++++++++++---- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index 816f9cca2..c84033364 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -4,6 +4,7 @@ package org.infinity.datatype; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -38,6 +39,8 @@ import org.infinity.gui.menu.BrowserMenuBar; import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; +import org.infinity.resource.Closeable; +import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.sound.SoundResource; @@ -90,6 +93,9 @@ public class ResourceRef extends Datatype */ private TextListPanel list; + /** Contains the {@link Resource} of the currently selected resource reference. */ + private Resource currentResource; + /** * Returns a list of resource extensions that can be used to display associated icons. * @return String set with file extensions (without leading dot). @@ -124,8 +130,11 @@ public void actionPerformed(ActionEvent event) { } else if (event.getSource() == bPlay) { final ResourceRefEntry selected = list.getSelectedValue(); if (isSound(selected)) { + // prevent overlapping sound playback + closeResource(currentResource); SoundResource res = (SoundResource) ResourceFactory.getResource(selected.entry); res.playSound(); + currentResource = res; } } } @@ -192,29 +201,42 @@ public void mouseClicked(MouseEvent event) { bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); bView = new JButton("View/Edit", Icons.ICON_ZOOM_16.getIcon()); bView.addActionListener(this); - bView.setEnabled(isEditable(list.getSelectedValue())); bPlay = new JButton("Play", Icons.ICON_PLAY_16.getIcon()); bPlay.addActionListener(this); - bPlay.setEnabled(isSound(list.getSelectedValue())); list.addListSelectionListener(this); + setResourceEntryUpdated(list.getSelectedValue()); GridBagConstraints gbc = null; JPanel panel = new JPanel(new GridBagLayout()); - gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 3, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, + gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 5, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); panel.add(list, gbc); - gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, + + // spacer keeps controls in the center + final JPanel spacerTop = new JPanel(); + spacerTop.setMinimumSize(new Dimension()); + gbc = ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0); + panel.add(spacerTop, gbc); + + gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(3, 6, 3, 0), 0, 0); panel.add(bUpdate, gbc); - gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 1.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, + gbc = ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, new Insets(3, 6, 3, 0), 0, 0); panel.add(bView, gbc); - - gbc = ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 0.0, 1.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, - new Insets(3, 6, 3, 0), 0, 0); + gbc = ViewerUtil.setGBC(gbc, 1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(24, 6, 3, 0), 0, 0); panel.add(bPlay, gbc); + // spacer keeps controls in the center + final JPanel spacerBottom = new JPanel(); + spacerTop.setMinimumSize(new Dimension()); + gbc = ViewerUtil.setGBC(gbc, 1, 4, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(0, 0, 0, 0), 0, 0); + panel.add(spacerBottom, gbc); + panel.setMinimumSize(Misc.getScaledDimension(DIM_MEDIUM)); panel.setPreferredSize(panel.getMinimumSize()); return panel; @@ -268,8 +290,7 @@ public boolean updateValue(AbstractStruct struct) { @Override public void valueChanged(ListSelectionEvent e) { - bView.setEnabled(isEditable(list.getSelectedValue())); - bPlay.setEnabled(isSound(list.getSelectedValue())); + setResourceEntryUpdated(list.getSelectedValue()); } @Override @@ -391,6 +412,27 @@ public boolean isLegalEntry(ResourceEntry entry) { void addExtraEntries(List entries) { } + private void setResourceEntryUpdated(ResourceRefEntry entry) { + closeResource(currentResource); + if (entry != null) { + bView.setEnabled(isEditable(entry)); + bPlay.setEnabled(isSound(entry)); + } else { + bView.setEnabled(false); + bPlay.setEnabled(false); + } + } + + private void closeResource(Resource resource) { + if (resource instanceof Closeable) { + try { + ((Closeable) resource).close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + private boolean isEditable(ResourceRefEntry ref) { return ref != null && ref != NONE && ref.entry != null; } From d34292c293ccf62f81ff47dfc91730e2286fa83c Mon Sep 17 00:00:00 2001 From: Bubb13 <36863623+Bubb13@users.noreply.github.com> Date: Wed, 11 Oct 2023 17:18:46 -0700 Subject: [PATCH 05/23] Fix advanced search not opening the correct viewable - showResourceEntry() runs on the background thread, so subsequent calls to NearInfinity.getInstance().getViewable() might fetch the old viewable. --- src/org/infinity/NearInfinity.java | 6 ++- src/org/infinity/check/BCSIDSChecker.java | 11 ++++-- src/org/infinity/check/CreInvChecker.java | 11 ++++-- src/org/infinity/check/DialogChecker.java | 12 ++++-- src/org/infinity/check/ScriptChecker.java | 12 ++++-- src/org/infinity/gui/ResourceTree.java | 39 +++++++++++++------ .../infinity/search/ReferenceHitFrame.java | 17 +++++--- src/org/infinity/search/TextHitFrame.java | 15 ++++--- .../search/advanced/AdvancedSearch.java | 17 +++++--- 9 files changed, 98 insertions(+), 42 deletions(-) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 716c9958e..251f25904 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -888,7 +888,11 @@ public boolean removeViewable() { } public void showResourceEntry(ResourceEntry resourceEntry) { - tree.select(resourceEntry); + showResourceEntry(resourceEntry, null); + } + + public void showResourceEntry(ResourceEntry resourceEntry, Operation doneOperation) { + tree.select(resourceEntry, doneOperation); } public void quit() { diff --git a/src/org/infinity/check/BCSIDSChecker.java b/src/org/infinity/check/BCSIDSChecker.java index 30eb6a78d..e4e28163d 100644 --- a/src/org/infinity/check/BCSIDSChecker.java +++ b/src/org/infinity/check/BCSIDSChecker.java @@ -41,6 +41,7 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; +import org.infinity.util.Operation; /** Performs checking {@link BcsResource BCS} & {@code BS} resources. */ public final class BCSIDSChecker extends AbstractSearcher implements Runnable, ActionListener, ListSelectionListener { @@ -65,9 +66,13 @@ public void actionPerformed(ActionEvent event) { int row = table.getSelectedRow(); if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - NearInfinity.getInstance().showResourceEntry(resourceEntry); - BcsResource bcsfile = (BcsResource) NearInfinity.getInstance().getViewable(); - bcsfile.highlightText(((Integer) table.getValueAt(row, 2)), null); + NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { + @Override + public void perform() { + BcsResource bcsfile = (BcsResource) NearInfinity.getInstance().getViewable(); + bcsfile.highlightText(((Integer) table.getValueAt(row, 2)), null); + } + }); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); diff --git a/src/org/infinity/check/CreInvChecker.java b/src/org/infinity/check/CreInvChecker.java index f15a0baaf..f991d8f86 100644 --- a/src/org/infinity/check/CreInvChecker.java +++ b/src/org/infinity/check/CreInvChecker.java @@ -44,6 +44,7 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; +import org.infinity.util.Operation; /** Performs checking {@link CreResource CRE} & {@code CHR} resources. */ public final class CreInvChecker extends AbstractSearcher implements Runnable, ActionListener, ListSelectionListener { @@ -68,9 +69,13 @@ public void actionPerformed(ActionEvent event) { int row = table.getSelectedRow(); if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - NearInfinity.getInstance().showResourceEntry(resourceEntry); - ((AbstractStruct) NearInfinity.getInstance().getViewable()).getViewer() - .selectEntry(((Item) table.getValueAt(row, 2)).getName()); + NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { + @Override + public void perform() { + ((AbstractStruct) NearInfinity.getInstance().getViewable()).getViewer() + .selectEntry(((Item) table.getValueAt(row, 2)).getName()); + } + }); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); diff --git a/src/org/infinity/check/DialogChecker.java b/src/org/infinity/check/DialogChecker.java index d8e5841f4..2abe82778 100644 --- a/src/org/infinity/check/DialogChecker.java +++ b/src/org/infinity/check/DialogChecker.java @@ -48,6 +48,7 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; +import org.infinity.util.Operation; /** Performs checking {@link DlgResource DLG} resources. */ public final class DialogChecker extends AbstractSearcher @@ -84,9 +85,14 @@ public void actionPerformed(ActionEvent event) { int row = table.getSelectedRow(); if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - NearInfinity.getInstance().showResourceEntry(resourceEntry); - ((AbstractStruct) NearInfinity.getInstance().getViewable()).getViewer() - .selectEntry((String) table.getValueAt(row, 1)); + final SortableTable tableCapture = table; + NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { + @Override + public void perform() { + ((AbstractStruct) NearInfinity.getInstance().getViewable()).getViewer() + .selectEntry((String) tableCapture.getValueAt(row, 1)); + } + }); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); diff --git a/src/org/infinity/check/ScriptChecker.java b/src/org/infinity/check/ScriptChecker.java index 93d56cf35..ff4a5949c 100644 --- a/src/org/infinity/check/ScriptChecker.java +++ b/src/org/infinity/check/ScriptChecker.java @@ -43,6 +43,7 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; +import org.infinity.util.Operation; /** Performs checking {@link BcsResource BCS} & {@code BS} resources. */ public final class ScriptChecker extends AbstractSearcher @@ -76,9 +77,14 @@ public void actionPerformed(ActionEvent event) { int row = table.getSelectedRow(); if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - NearInfinity.getInstance().showResourceEntry(resourceEntry); - ((BcsResource) NearInfinity.getInstance().getViewable()).highlightText(((Integer) table.getValueAt(row, 2)), - null); + final SortableTable tableCapture = table; + NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { + @Override + public void perform() { + ((BcsResource) NearInfinity.getInstance().getViewable()) + .highlightText(((Integer) tableCapture.getValueAt(row, 2)), null); + } + }); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); diff --git a/src/org/infinity/gui/ResourceTree.java b/src/org/infinity/gui/ResourceTree.java index c1e61779d..9b7330c93 100644 --- a/src/org/infinity/gui/ResourceTree.java +++ b/src/org/infinity/gui/ResourceTree.java @@ -68,6 +68,7 @@ import org.infinity.resource.key.ResourceTreeFolder; import org.infinity.resource.key.ResourceTreeModel; import org.infinity.util.IconCache; +import org.infinity.util.Operation; import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; @@ -192,24 +193,38 @@ public void select(ResourceEntry entry) { } public void select(ResourceEntry entry, boolean forced) { + select(entry, forced, null); + } + + public void select(ResourceEntry entry, Operation doneOperation) { + select(entry, false, doneOperation); + } + + public void select(ResourceEntry entry, boolean forced, Operation doneOperation) { new SwingWorker() { @Override protected Void doInBackground() throws Exception { - if (entry == null) { - tree.clearSelection(); - } else if (forced || entry != shownResource) { - TreePath tp = ResourceFactory.getResourceTreeModel().getPathToNode(entry); - try { - expandListener.treeWillExpand(new TreeExpansionEvent(tree, tp)); - tree.scrollPathToVisible(tp); - tree.addSelectionPath(tp); - tree.repaint(); - } finally { - expandListener.treeExpanded(new TreeExpansionEvent(tree, tp)); - } + if (entry == null) { + tree.clearSelection(); + } else if (forced || entry != shownResource) { + TreePath tp = ResourceFactory.getResourceTreeModel().getPathToNode(entry); + try { + expandListener.treeWillExpand(new TreeExpansionEvent(tree, tp)); + tree.scrollPathToVisible(tp); + tree.addSelectionPath(tp); + tree.repaint(); + } finally { + expandListener.treeExpanded(new TreeExpansionEvent(tree, tp)); } + } return null; } + @Override + protected void done() { + if (doneOperation != null) { + doneOperation.perform(); + } + } }.execute(); } diff --git a/src/org/infinity/search/ReferenceHitFrame.java b/src/org/infinity/search/ReferenceHitFrame.java index ae290c188..d43f88c07 100644 --- a/src/org/infinity/search/ReferenceHitFrame.java +++ b/src/org/infinity/search/ReferenceHitFrame.java @@ -43,6 +43,7 @@ import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; +import org.infinity.util.Operation; public final class ReferenceHitFrame extends ChildFrame implements ActionListener, ListSelectionListener { private static final String QUERY_STRING = "string reference"; @@ -130,12 +131,16 @@ public void actionPerformed(ActionEvent event) { ((ViewFrame) parent).toFront(); } } else { - NearInfinity.getInstance().showResourceEntry(entry); - Viewable viewable = NearInfinity.getInstance().getViewable(); - showEntryInViewer(row, viewable); - if (viewable instanceof DlgResource) { - NearInfinity.getInstance().toFront(); - } + NearInfinity.getInstance().showResourceEntry(entry, new Operation() { + @Override + public void perform() { + Viewable viewable = NearInfinity.getInstance().getViewable(); + showEntryInViewer(row, viewable); + if (viewable instanceof DlgResource) { + NearInfinity.getInstance().toFront(); + } + } + }); } } } else if (event.getSource() == bopennew) { diff --git a/src/org/infinity/search/TextHitFrame.java b/src/org/infinity/search/TextHitFrame.java index 5c1a11f4a..a4438836a 100644 --- a/src/org/infinity/search/TextHitFrame.java +++ b/src/org/infinity/search/TextHitFrame.java @@ -36,6 +36,7 @@ import org.infinity.resource.Viewable; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; +import org.infinity.util.Operation; final class TextHitFrame extends ChildFrame implements ActionListener, ListSelectionListener { private final Component parent; @@ -119,11 +120,15 @@ public void actionPerformed(ActionEvent event) { ((TextResource) res).highlightText(((Integer) table.getValueAt(row, 2)), query); } } else { - NearInfinity.getInstance().showResourceEntry(entry); - Viewable viewable = NearInfinity.getInstance().getViewable(); - if (viewable instanceof TextResource) { - ((TextResource) viewable).highlightText(((Integer) table.getValueAt(row, 2)), query); - } + NearInfinity.getInstance().showResourceEntry(entry, new Operation() { + @Override + public void perform() { + Viewable viewable = NearInfinity.getInstance().getViewable(); + if (viewable instanceof TextResource) { + ((TextResource) viewable).highlightText(((Integer) table.getValueAt(row, 2)), query); + } + } + }); } } } else if (event.getSource() == bopennew) { diff --git a/src/org/infinity/search/advanced/AdvancedSearch.java b/src/org/infinity/search/advanced/AdvancedSearch.java index ef82f8f22..69d76f552 100644 --- a/src/org/infinity/search/advanced/AdvancedSearch.java +++ b/src/org/infinity/search/advanced/AdvancedSearch.java @@ -77,6 +77,7 @@ import org.infinity.search.ReferenceHitFrame; import org.infinity.util.Debugging; import org.infinity.util.Misc; +import org.infinity.util.Operation; import org.infinity.util.SimpleListModel; public class AdvancedSearch extends ChildFrame implements Runnable { @@ -819,12 +820,16 @@ public void actionPerformed(ActionEvent event) { if (row != -1) { ResourceEntry entry = (ResourceEntry) listResults.getValueAt(row, 0); if (entry != null) { - NearInfinity.getInstance().showResourceEntry(entry); - Viewable viewable = NearInfinity.getInstance().getViewable(); - showEntryInViewer(row, viewable); - if (viewable instanceof DlgResource) { - NearInfinity.getInstance().toFront(); - } + NearInfinity.getInstance().showResourceEntry(entry, new Operation() { + @Override + public void perform() { + Viewable viewable = NearInfinity.getInstance().getViewable(); + showEntryInViewer(row, viewable); + if (viewable instanceof DlgResource) { + NearInfinity.getInstance().toFront(); + } + } + }); } } } else if (event.getSource() == bOpenNew) { From 4037ee7b8acd9170b3576f2e5a3c1fba5bed7ae0 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:30:46 +0200 Subject: [PATCH 06/23] Code cleanup --- src/org/infinity/check/BCSIDSChecker.java | 10 +++------- src/org/infinity/check/CreInvChecker.java | 11 +++-------- src/org/infinity/check/DialogChecker.java | 11 +++-------- src/org/infinity/check/ScriptChecker.java | 11 +++-------- src/org/infinity/gui/ResourceTree.java | 1 + src/org/infinity/search/ReferenceHitFrame.java | 14 +++++--------- src/org/infinity/search/TextHitFrame.java | 12 ++++-------- .../infinity/search/advanced/AdvancedSearch.java | 14 +++++--------- 8 files changed, 27 insertions(+), 57 deletions(-) diff --git a/src/org/infinity/check/BCSIDSChecker.java b/src/org/infinity/check/BCSIDSChecker.java index e4e28163d..dd931a119 100644 --- a/src/org/infinity/check/BCSIDSChecker.java +++ b/src/org/infinity/check/BCSIDSChecker.java @@ -41,7 +41,6 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; -import org.infinity.util.Operation; /** Performs checking {@link BcsResource BCS} & {@code BS} resources. */ public final class BCSIDSChecker extends AbstractSearcher implements Runnable, ActionListener, ListSelectionListener { @@ -66,12 +65,9 @@ public void actionPerformed(ActionEvent event) { int row = table.getSelectedRow(); if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { - @Override - public void perform() { - BcsResource bcsfile = (BcsResource) NearInfinity.getInstance().getViewable(); - bcsfile.highlightText(((Integer) table.getValueAt(row, 2)), null); - } + NearInfinity.getInstance().showResourceEntry(resourceEntry, () -> { + final BcsResource bcsfile = (BcsResource) NearInfinity.getInstance().getViewable(); + bcsfile.highlightText(((Integer) table.getValueAt(row, 2)), null); }); } } else if (event.getSource() == bopennew) { diff --git a/src/org/infinity/check/CreInvChecker.java b/src/org/infinity/check/CreInvChecker.java index f991d8f86..23a8aeecf 100644 --- a/src/org/infinity/check/CreInvChecker.java +++ b/src/org/infinity/check/CreInvChecker.java @@ -44,7 +44,6 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; -import org.infinity.util.Operation; /** Performs checking {@link CreResource CRE} & {@code CHR} resources. */ public final class CreInvChecker extends AbstractSearcher implements Runnable, ActionListener, ListSelectionListener { @@ -69,13 +68,9 @@ public void actionPerformed(ActionEvent event) { int row = table.getSelectedRow(); if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { - @Override - public void perform() { - ((AbstractStruct) NearInfinity.getInstance().getViewable()).getViewer() - .selectEntry(((Item) table.getValueAt(row, 2)).getName()); - } - }); + NearInfinity.getInstance().showResourceEntry(resourceEntry, + () -> ((AbstractStruct)NearInfinity.getInstance().getViewable()).getViewer() + .selectEntry(((Item)table.getValueAt(row, 2)).getName())); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); diff --git a/src/org/infinity/check/DialogChecker.java b/src/org/infinity/check/DialogChecker.java index 2abe82778..3d0915621 100644 --- a/src/org/infinity/check/DialogChecker.java +++ b/src/org/infinity/check/DialogChecker.java @@ -48,7 +48,6 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; -import org.infinity.util.Operation; /** Performs checking {@link DlgResource DLG} resources. */ public final class DialogChecker extends AbstractSearcher @@ -86,13 +85,9 @@ public void actionPerformed(ActionEvent event) { if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); final SortableTable tableCapture = table; - NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { - @Override - public void perform() { - ((AbstractStruct) NearInfinity.getInstance().getViewable()).getViewer() - .selectEntry((String) tableCapture.getValueAt(row, 1)); - } - }); + NearInfinity.getInstance().showResourceEntry(resourceEntry, + () -> ((AbstractStruct)NearInfinity.getInstance().getViewable()).getViewer() + .selectEntry((String)tableCapture.getValueAt(row, 1))); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); diff --git a/src/org/infinity/check/ScriptChecker.java b/src/org/infinity/check/ScriptChecker.java index ff4a5949c..096dc2b99 100644 --- a/src/org/infinity/check/ScriptChecker.java +++ b/src/org/infinity/check/ScriptChecker.java @@ -43,7 +43,6 @@ import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; import org.infinity.util.Misc; -import org.infinity.util.Operation; /** Performs checking {@link BcsResource BCS} & {@code BS} resources. */ public final class ScriptChecker extends AbstractSearcher @@ -78,13 +77,9 @@ public void actionPerformed(ActionEvent event) { if (row != -1) { ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); final SortableTable tableCapture = table; - NearInfinity.getInstance().showResourceEntry(resourceEntry, new Operation() { - @Override - public void perform() { - ((BcsResource) NearInfinity.getInstance().getViewable()) - .highlightText(((Integer) tableCapture.getValueAt(row, 2)), null); - } - }); + NearInfinity.getInstance().showResourceEntry(resourceEntry, + () -> ((BcsResource)NearInfinity.getInstance().getViewable()) + .highlightText(((Integer)tableCapture.getValueAt(row, 2)), null)); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); diff --git a/src/org/infinity/gui/ResourceTree.java b/src/org/infinity/gui/ResourceTree.java index 9b7330c93..05f751c4e 100644 --- a/src/org/infinity/gui/ResourceTree.java +++ b/src/org/infinity/gui/ResourceTree.java @@ -219,6 +219,7 @@ protected Void doInBackground() throws Exception { } return null; } + @Override protected void done() { if (doneOperation != null) { diff --git a/src/org/infinity/search/ReferenceHitFrame.java b/src/org/infinity/search/ReferenceHitFrame.java index d43f88c07..609b46248 100644 --- a/src/org/infinity/search/ReferenceHitFrame.java +++ b/src/org/infinity/search/ReferenceHitFrame.java @@ -43,7 +43,6 @@ import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; -import org.infinity.util.Operation; public final class ReferenceHitFrame extends ChildFrame implements ActionListener, ListSelectionListener { private static final String QUERY_STRING = "string reference"; @@ -131,14 +130,11 @@ public void actionPerformed(ActionEvent event) { ((ViewFrame) parent).toFront(); } } else { - NearInfinity.getInstance().showResourceEntry(entry, new Operation() { - @Override - public void perform() { - Viewable viewable = NearInfinity.getInstance().getViewable(); - showEntryInViewer(row, viewable); - if (viewable instanceof DlgResource) { - NearInfinity.getInstance().toFront(); - } + NearInfinity.getInstance().showResourceEntry(entry, () -> { + Viewable viewable = NearInfinity.getInstance().getViewable(); + showEntryInViewer(row, viewable); + if (viewable instanceof DlgResource) { + NearInfinity.getInstance().toFront(); } }); } diff --git a/src/org/infinity/search/TextHitFrame.java b/src/org/infinity/search/TextHitFrame.java index a4438836a..08484ff87 100644 --- a/src/org/infinity/search/TextHitFrame.java +++ b/src/org/infinity/search/TextHitFrame.java @@ -36,7 +36,6 @@ import org.infinity.resource.Viewable; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.Misc; -import org.infinity.util.Operation; final class TextHitFrame extends ChildFrame implements ActionListener, ListSelectionListener { private final Component parent; @@ -120,13 +119,10 @@ public void actionPerformed(ActionEvent event) { ((TextResource) res).highlightText(((Integer) table.getValueAt(row, 2)), query); } } else { - NearInfinity.getInstance().showResourceEntry(entry, new Operation() { - @Override - public void perform() { - Viewable viewable = NearInfinity.getInstance().getViewable(); - if (viewable instanceof TextResource) { - ((TextResource) viewable).highlightText(((Integer) table.getValueAt(row, 2)), query); - } + NearInfinity.getInstance().showResourceEntry(entry, () -> { + Viewable viewable = NearInfinity.getInstance().getViewable(); + if (viewable instanceof TextResource) { + ((TextResource) viewable).highlightText(((Integer) table.getValueAt(row, 2)), query); } }); } diff --git a/src/org/infinity/search/advanced/AdvancedSearch.java b/src/org/infinity/search/advanced/AdvancedSearch.java index 69d76f552..5796bf657 100644 --- a/src/org/infinity/search/advanced/AdvancedSearch.java +++ b/src/org/infinity/search/advanced/AdvancedSearch.java @@ -77,7 +77,6 @@ import org.infinity.search.ReferenceHitFrame; import org.infinity.util.Debugging; import org.infinity.util.Misc; -import org.infinity.util.Operation; import org.infinity.util.SimpleListModel; public class AdvancedSearch extends ChildFrame implements Runnable { @@ -820,14 +819,11 @@ public void actionPerformed(ActionEvent event) { if (row != -1) { ResourceEntry entry = (ResourceEntry) listResults.getValueAt(row, 0); if (entry != null) { - NearInfinity.getInstance().showResourceEntry(entry, new Operation() { - @Override - public void perform() { - Viewable viewable = NearInfinity.getInstance().getViewable(); - showEntryInViewer(row, viewable); - if (viewable instanceof DlgResource) { - NearInfinity.getInstance().toFront(); - } + NearInfinity.getInstance().showResourceEntry(entry, () -> { + Viewable viewable = NearInfinity.getInstance().getViewable(); + showEntryInViewer(row, viewable); + if (viewable instanceof DlgResource) { + NearInfinity.getInstance().toFront(); } }); } From ea553b15e9c6bface95ece9a6917c8c2e278a3a9 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:52:36 +0200 Subject: [PATCH 07/23] Allow opening games in hidden folders from the "Open Game" file dialog --- src/org/infinity/NearInfinity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 251f25904..96ad70ec9 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -247,6 +247,7 @@ private static Path findKeyfile() { } else { chooser = new JFileChooser(Profile.getGameRoot().toFile()); } + chooser.setFileHidingEnabled(false); chooser.setDialogTitle("Open game: Locate keyfile"); chooser.setFileFilter(new FileFilter() { @Override From db7efe9688a50489d1799bae4d4f5c86560644dd Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 25 Oct 2023 21:58:24 +0200 Subject: [PATCH 08/23] Update GH Action --- .github/workflows/ant.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index f1fda9b3a..6e111fdb5 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -23,7 +23,7 @@ jobs: sed -i 's/debug="false"/debug="true"/' build.xml ant -noinput -buildfile build.xml - name: Upload artifact - if: ${{ github.actor != 'NearInfinityBrowser' }} + if: ${{ github.actor == 'Argent77' }} uses: pyTooling/Actions/releaser@r0 with: tag: nightly From 907d7dff600b35e84811b7eddd9b4c4eb2865987 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 28 Oct 2023 19:02:37 +0200 Subject: [PATCH 09/23] Update GH actions --- .github/workflows/ant.yml | 2 +- .github/workflows/deploy.yml | 120 +++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 6e111fdb5..5be229a07 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -23,7 +23,7 @@ jobs: sed -i 's/debug="false"/debug="true"/' build.xml ant -noinput -buildfile build.xml - name: Upload artifact - if: ${{ github.actor == 'Argent77' }} + if: ${{ github.repository == 'Argent77/NearInfinity' }} uses: pyTooling/Actions/releaser@r0 with: tag: nightly diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..261a61659 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,120 @@ +# This workflow will build a Java project with Apache Ant and upload the created artifacts +# on pushes to the master branch. + +name: Java CD with Apache Ant + +on: + push: + branches: [ "master" ] + workflow_dispatch: + branches: [ "master", "devel" ] + +permissions: + contents: read + +jobs: + # Build and upload NearInfinity.jar + deploy-jar: + if: ${{ github.repository == 'Argent77/NearInfinity' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'temurin' + + - name: Build with Ant + run: | + ant -noinput -buildfile build.xml + + - name: Upload JAR artifact + uses: actions/upload-artifact@v3 + with: + name: NearInfinity + path: NearInfinity.jar + + # Build and upload installer versions for Windows and macOS + deploy-installer: + if: ${{ github.repository == 'Argent77/NearInfinity' }} + needs: deploy-jar + strategy: + fail-fast: false + matrix: + os: [ windows-latest, macos-latest ] + java: [ '21' ] + runs-on: ${{ matrix.os }} + name: Create installer for ${{ matrix.os }}, JDK ${{ matrix.java }} + steps: + # Initializations + - name: Git checkout + uses: actions/checkout@v4 + + - name: Set up JDK + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + + - name: Echo JAVA_HOME (windows) + if: (matrix.os == 'windows-latest') + run: echo $env:JAVA_HOME + + - name: Echo JAVA_HOME (macos) + if: (matrix.os != 'windows-latest') + run: echo $JAVA_HOME + + # Preparations + - name: Download JAR artifact + uses: actions/download-artifact@v3 + with: + name: NearInfinity + path: jar + + - name: Set up installer data + uses: actions/checkout@v4 + with: + repository: NearInfinityBrowser/NearInfinity-assets + path: assets + + # Building + - name: Build installer (windows) + if: (matrix.os == 'windows-latest') + run: | + move assets\redistributable\windows\package . + move assets\redistributable\windows\build.cmd . + .\build.cmd + + - name: Build installer (macos) + if: (matrix.os == 'macos-latest') + run: | + mv assets/redistributable/macos/package . + mv assets/redistributable/macos/build.command . + chmod +x build.command + ./build.command + + # Validation + - name: List built files (windows) + if: (matrix.os == 'windows-latest') + run: dir + + - name: List built files (macos) + if: (matrix.os == 'macos-latest') + run: ls -l + + # Uploading + - name: Upload exe artifact (windows) + if: (matrix.os == 'windows-latest') + uses: actions/upload-artifact@v3 + with: + name: installer-windows + path: NearInfinity-*.exe + + - name: Upload pkg artifact (macos) + if: (matrix.os == 'macos-latest') + uses: actions/upload-artifact@v3 + with: + name: installer-macos + path: NearInfinity-*.pkg From 0188894d0da1965c93057dacf215d0dcb0e4cdfe Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 29 Oct 2023 10:34:48 +0100 Subject: [PATCH 10/23] Improve deploy.yml --- .github/workflows/deploy.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 261a61659..0e54bec05 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -17,6 +17,9 @@ jobs: deploy-jar: if: ${{ github.repository == 'Argent77/NearInfinity' }} runs-on: ubuntu-latest + name: Build NearInfinity.jar + outputs: + ni_version: ${{ steps.ni-build.outputs.NI_VERSION }} steps: - uses: actions/checkout@v4 @@ -27,13 +30,15 @@ jobs: distribution: 'temurin' - name: Build with Ant + id: ni-build run: | ant -noinput -buildfile build.xml + echo "NI_VERSION=$(java -jar "NearInfinity.jar" -version 2>/dev/null | grep -Eo '[0-9]{8}')" >> "$GITHUB_OUTPUT" - name: Upload JAR artifact uses: actions/upload-artifact@v3 with: - name: NearInfinity + name: NearInfinity-${{ steps.ni-build.outputs.NI_VERSION }} path: NearInfinity.jar # Build and upload installer versions for Windows and macOS @@ -70,7 +75,7 @@ jobs: - name: Download JAR artifact uses: actions/download-artifact@v3 with: - name: NearInfinity + name: NearInfinity-${{ needs.deploy-jar.outputs.ni_version }} path: jar - name: Set up installer data From f97dac6bef74517e447970c677f23661d3b8be72 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 29 Oct 2023 18:10:53 +0100 Subject: [PATCH 11/23] WMP: Show travel distances for all areas if no area is selected --- src/org/infinity/resource/wmp/ViewerMap.java | 168 ++++++++++--------- 1 file changed, 93 insertions(+), 75 deletions(-) diff --git a/src/org/infinity/resource/wmp/ViewerMap.java b/src/org/infinity/resource/wmp/ViewerMap.java index 1363294e7..9785611b8 100644 --- a/src/org/infinity/resource/wmp/ViewerMap.java +++ b/src/org/infinity/resource/wmp/ViewerMap.java @@ -24,6 +24,8 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import javax.imageio.ImageIO; @@ -346,86 +348,102 @@ private void showMapIconLabels() { } } - /** Displays all map distances from the specified area (by index). */ + /** + * Displays all map distances from the specified area (by index). + * + * @param areaIndex Map index for showing distances. Specify negative value to show distances for all available maps. + */ private void showMapDistances(int areaIndex) { - AreaEntry area = getAreaEntry(areaIndex, true); - if (area != null) { - final Direction[] srcDir = { Direction.NORTH, Direction.WEST, Direction.SOUTH, Direction.EAST }; - final Color[] dirColor = { Color.GREEN, Color.RED, Color.CYAN, Color.YELLOW }; - final int[] links = new int[8]; - final int linkSize = 216; // size of a single area link structure - int ofsLinkBase = ((IsNumeric) getEntry().getAttribute(MapEntry.WMP_MAP_OFFSET_AREA_LINKS)).getValue(); - - links[0] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_NORTH)).getValue(); - links[1] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_NORTH)).getValue(); - links[2] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_WEST)).getValue(); - links[3] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_WEST)).getValue(); - links[4] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_SOUTH)).getValue(); - links[5] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_SOUTH)).getValue(); - links[6] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_EAST)).getValue(); - links[7] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_EAST)).getValue(); - for (int dir = 0; dir < srcDir.length; dir++) { - Direction curDir = srcDir[dir]; - Point ptOrigin = getMapIconCoordinate(areaIndex, curDir, true); - for (int dirIndex = 0, dirCount = links[dir * 2 + 1]; dirIndex < dirCount; dirIndex++) { - int ofsLink = ofsLinkBase + (links[dir * 2] + dirIndex) * linkSize; - AreaLink destLink = (AreaLink) area.getAttribute(ofsLink, false); - - if (destLink != null) { - int dstAreaIndex = ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); - Flag flag = (Flag) destLink.getAttribute(AreaLink.WMP_LINK_DEFAULT_ENTRANCE); - Direction dstDir = Direction.NORTH; - if (flag.isFlagSet(1)) { - dstDir = Direction.EAST; - } else if (flag.isFlagSet(2)) { - dstDir = Direction.SOUTH; - } else if (flag.isFlagSet(3)) { - dstDir = Direction.WEST; - } - Point ptTarget = getMapIconCoordinate(dstAreaIndex, dstDir, false); - - // checking for random encounters during travels - boolean hasRandomEncounters = false; - if (((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_PROBABILITY)).getValue() > 0) { - for (int rnd = 1; rnd < 6; rnd++) { - String rndArea = ((IsReference) destLink - .getAttribute(String.format(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_AREA_FMT, rnd))).getResourceName(); - if (ResourceFactory.resourceExists(rndArea)) { - hasRandomEncounters = true; - break; + final List areaIndices = new ArrayList<>(); + if (areaIndex >= 0) { + areaIndices.add(areaIndex); + } else { + int numAreas = ((IsNumeric) mapEntry.getAttribute(MapEntry.WMP_MAP_NUM_AREAS)).getValue(); + for (int i = 0; i < numAreas; i++) { + areaIndices.add(i); + } + } + + for (final int curAreaIndex : areaIndices) { + AreaEntry area = getAreaEntry(curAreaIndex, true); + if (area != null) { + final Direction[] srcDir = { Direction.NORTH, Direction.WEST, Direction.SOUTH, Direction.EAST }; + final Color[] dirColor = { Color.GREEN, Color.RED, Color.CYAN, Color.YELLOW }; + final int[] links = new int[8]; + final int linkSize = 216; // size of a single area link structure + int ofsLinkBase = ((IsNumeric) getEntry().getAttribute(MapEntry.WMP_MAP_OFFSET_AREA_LINKS)).getValue(); + + links[0] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_NORTH)).getValue(); + links[1] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_NORTH)).getValue(); + links[2] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_WEST)).getValue(); + links[3] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_WEST)).getValue(); + links[4] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_SOUTH)).getValue(); + links[5] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_SOUTH)).getValue(); + links[6] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_EAST)).getValue(); + links[7] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_EAST)).getValue(); + for (int dir = 0; dir < srcDir.length; dir++) { + Direction curDir = srcDir[dir]; + Point ptOrigin = getMapIconCoordinate(curAreaIndex, curDir, true); + for (int dirIndex = 0, dirCount = links[dir * 2 + 1]; dirIndex < dirCount; dirIndex++) { + int ofsLink = ofsLinkBase + (links[dir * 2] + dirIndex) * linkSize; + AreaLink destLink = (AreaLink) area.getAttribute(ofsLink, false); + + if (destLink != null) { + int dstAreaIndex = ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); + Flag flag = (Flag) destLink.getAttribute(AreaLink.WMP_LINK_DEFAULT_ENTRANCE); + Direction dstDir = Direction.NORTH; + if (flag.isFlagSet(1)) { + dstDir = Direction.EAST; + } else if (flag.isFlagSet(2)) { + dstDir = Direction.SOUTH; + } else if (flag.isFlagSet(3)) { + dstDir = Direction.WEST; + } + Point ptTarget = getMapIconCoordinate(dstAreaIndex, dstDir, false); + + // checking for random encounters during travels + boolean hasRandomEncounters = false; + if (((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_PROBABILITY)).getValue() > 0) { + for (int rnd = 1; rnd < 6; rnd++) { + String rndArea = ((IsReference) destLink + .getAttribute(String.format(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_AREA_FMT, rnd))).getResourceName(); + if (ResourceFactory.resourceExists(rndArea)) { + hasRandomEncounters = true; + break; + } } } - } - Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); - g.setFont(g.getFont().deriveFont(g.getFont().getSize2D() * 0.8f)); - try { - // drawing line - g.setColor(dirColor[dir]); - if (hasRandomEncounters) { - g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, - new float[] { 6.0f, 4.0f }, 0.0f)); - } else { - g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); + g.setFont(g.getFont().deriveFont(g.getFont().getSize2D() * 0.8f)); + try { + // drawing line + g.setColor(dirColor[dir]); + if (hasRandomEncounters) { + g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, + new float[] { 6.0f, 4.0f }, 0.0f)); + } else { + g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + } + g.drawLine(ptOrigin.x, ptOrigin.y, ptTarget.x, ptTarget.y); + + // printing travel time (in hours) + String duration = String.format("%d h", + ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_DISTANCE_SCALE)).getValue() * 4); + LineMetrics lm = g.getFont().getLineMetrics(duration, g.getFontRenderContext()); + Rectangle2D rectText = g.getFont().getStringBounds(duration, g.getFontRenderContext()); + int textX = ptOrigin.x + ((ptTarget.x - ptOrigin.x) - rectText.getBounds().width) / 3; + int textY = ptOrigin.y + ((ptTarget.y - ptOrigin.y) - rectText.getBounds().height) / 3; + int textWidth = rectText.getBounds().width; + int textHeight = rectText.getBounds().height; + g.setColor(Color.LIGHT_GRAY); + g.fillRect(textX - 2, textY, textWidth + 4, textHeight); + g.setColor(Color.BLUE); + g.drawString(duration, textX, textY + lm.getAscent() + lm.getLeading()); + } finally { + g.dispose(); + g = null; } - g.drawLine(ptOrigin.x, ptOrigin.y, ptTarget.x, ptTarget.y); - - // printing travel time (in hours) - String duration = String.format("%d h", - ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_DISTANCE_SCALE)).getValue() * 4); - LineMetrics lm = g.getFont().getLineMetrics(duration, g.getFontRenderContext()); - Rectangle2D rectText = g.getFont().getStringBounds(duration, g.getFontRenderContext()); - int textX = ptOrigin.x + ((ptTarget.x - ptOrigin.x) - rectText.getBounds().width) / 2; - int textY = ptOrigin.y + ((ptTarget.y - ptOrigin.y) - rectText.getBounds().height) / 2; - int textWidth = rectText.getBounds().width; - int textHeight = rectText.getBounds().height; - g.setColor(Color.LIGHT_GRAY); - g.fillRect(textX - 2, textY, textWidth + 4, textHeight); - g.setColor(Color.BLUE); - g.drawString(duration, textX, textY + lm.getAscent() + lm.getLeading()); - } finally { - g.dispose(); - g = null; } } } From 5e96fbcb2a7696e70c5d8b75fb3e96aa66fae1b4 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:33:55 +0100 Subject: [PATCH 12/23] Update GH action --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0e54bec05..8877f1fe4 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -89,8 +89,8 @@ jobs: if: (matrix.os == 'windows-latest') run: | move assets\redistributable\windows\package . - move assets\redistributable\windows\build.cmd . - .\build.cmd + move assets\redistributable\windows\build-installer.cmd . + .\build-installer.cmd - name: Build installer (macos) if: (matrix.os == 'macos-latest') From 45836e3e507a191ed16527c6f3eeed8ed0925f92 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 30 Oct 2023 12:26:35 +0100 Subject: [PATCH 13/23] WMP: Make area list multi-selectable Allows selective display of travel distances between maps. --- src/org/infinity/resource/wmp/ViewerMap.java | 23 ++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/org/infinity/resource/wmp/ViewerMap.java b/src/org/infinity/resource/wmp/ViewerMap.java index 9785611b8..ac65775ae 100644 --- a/src/org/infinity/resource/wmp/ViewerMap.java +++ b/src/org/infinity/resource/wmp/ViewerMap.java @@ -41,6 +41,7 @@ import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSplitPane; +import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; @@ -169,6 +170,7 @@ private enum Direction { listPanel = (StructListPanel) ViewerUtil.makeListPanel("Areas", wmpMap, AreaEntry.class, AreaEntry.WMP_AREA_CURRENT, new WmpAreaListRenderer(mapIcons), listeners); + listPanel.getList().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); JScrollPane mapScroll = new JScrollPane(rcMap); mapScroll.getVerticalScrollBar().setUnitIncrement(16); mapScroll.getHorizontalScrollBar().setUnitIncrement(16); @@ -232,7 +234,7 @@ private void showOverlays(boolean showIcons, boolean showIconLabels, boolean sho showMapIconLabels(); } if (showDistances) { - showMapDistances(listPanel.getList().getSelectedIndex()); + showMapDistances(listPanel.getList().getSelectedIndices()); } } showDot((AreaEntry) listPanel.getList().getSelectedValue(), false); @@ -351,20 +353,23 @@ private void showMapIconLabels() { /** * Displays all map distances from the specified area (by index). * - * @param areaIndex Map index for showing distances. Specify negative value to show distances for all available maps. + * @param areaIndices Sequence of map indices for showing distances. Specify no parameters to show distances for all + * available maps. */ - private void showMapDistances(int areaIndex) { - final List areaIndices = new ArrayList<>(); - if (areaIndex >= 0) { - areaIndices.add(areaIndex); - } else { + private void showMapDistances(int... areaIndices) { + final List areaIndicesList = new ArrayList<>(); + if (areaIndices.length == 0) { int numAreas = ((IsNumeric) mapEntry.getAttribute(MapEntry.WMP_MAP_NUM_AREAS)).getValue(); for (int i = 0; i < numAreas; i++) { - areaIndices.add(i); + areaIndicesList.add(i); + } + } else { + for (final int idx : areaIndices) { + areaIndicesList.add(idx); } } - for (final int curAreaIndex : areaIndices) { + for (final int curAreaIndex : areaIndicesList) { AreaEntry area = getAreaEntry(curAreaIndex, true); if (area != null) { final Direction[] srcDir = { Direction.NORTH, Direction.WEST, Direction.SOUTH, Direction.EAST }; From ff1caff2626d258cdd4d4c8fa69a814eaa7a20b5 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 30 Oct 2023 15:17:20 +0100 Subject: [PATCH 14/23] WMP: Show only corresponding travel routes between selected areas Current area selection behavior: - no selection: show all travel routes for all areas - single selection: show all travel routes starting from the selected area - multi-selection: show corresponding travel routes between selected areas --- src/org/infinity/resource/wmp/ViewerMap.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/org/infinity/resource/wmp/ViewerMap.java b/src/org/infinity/resource/wmp/ViewerMap.java index ac65775ae..7ba4e3c28 100644 --- a/src/org/infinity/resource/wmp/ViewerMap.java +++ b/src/org/infinity/resource/wmp/ViewerMap.java @@ -393,6 +393,23 @@ private void showMapDistances(int... areaIndices) { int ofsLink = ofsLinkBase + (links[dir * 2] + dirIndex) * linkSize; AreaLink destLink = (AreaLink) area.getAttribute(ofsLink, false); + // finding corresponding travel distances between selected areas + if (destLink != null && areaIndices.length > 1) { + final int destAreaIdx = ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); + final AreaEntry destArea = getAreaEntry(destAreaIdx, false); + boolean found = false; + if (destArea != null) { + found = areaIndicesList + .stream() + .filter(idx -> curAreaIndex != idx && destArea.equals(getAreaEntry(idx, true))) + .findAny() + .isPresent(); + } + if (!found) { + destLink = null; + } + } + if (destLink != null) { int dstAreaIndex = ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); Flag flag = (Flag) destLink.getAttribute(AreaLink.WMP_LINK_DEFAULT_ENTRANCE); From 21e3ff3662765afa21245d54fb9150d3cb25a276 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 30 Oct 2023 17:54:43 +0100 Subject: [PATCH 15/23] Update GH action --- .github/workflows/deploy.yml | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8877f1fe4..fb03d6f35 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -65,13 +65,32 @@ jobs: - name: Echo JAVA_HOME (windows) if: (matrix.os == 'windows-latest') - run: echo $env:JAVA_HOME + run: | + echo $env:JAVA_HOME + java -version - name: Echo JAVA_HOME (macos) if: (matrix.os != 'windows-latest') - run: echo $JAVA_HOME + run: | + echo $JAVA_HOME + java -version # Preparations + - name: Download and install zip tool (windows) + if: (matrix.os == 'windows-latest') + run: | + $BaseDir=".\tools" + $UrlZip="http://downloads.sourceforge.net/gnuwin32/zip-3.0-bin.zip" + $PathZip="zip-3.0-bin.zip" + $UrlZipDep="http://downloads.sourceforge.net/gnuwin32/zip-3.0-dep.zip" + $PathZipDep="zip-3.0-dep.zip" + (New-Object System.Net.WebClient).DownloadFile($UrlZip, $PathZip) + (New-Object System.Net.WebClient).DownloadFile($UrlZipDep, $PathZipDep) + Expand-Archive -LiteralPath $PathZip -DestinationPath $BaseDir + Expand-Archive -LiteralPath $PathZipDep -DestinationPath $BaseDir + del $PathZip + del $PathZipDep + - name: Download JAR artifact uses: actions/download-artifact@v3 with: @@ -85,11 +104,15 @@ jobs: path: assets # Building - - name: Build installer (windows) + - name: Build portable archive and installer (windows) if: (matrix.os == 'windows-latest') run: | + $BaseDir=".\tools" + $env:Path += ";$([IO.Path]::GetFullPath($BaseDir))\bin" move assets\redistributable\windows\package . + move assets\redistributable\windows\build-image.cmd . move assets\redistributable\windows\build-installer.cmd . + .\build-image.cmd .\build-installer.cmd - name: Build installer (macos) @@ -110,6 +133,13 @@ jobs: run: ls -l # Uploading + - name: Upload portable artifact (windows) + if: (matrix.os == 'windows-latest') + uses: actions/upload-artifact@v3 + with: + name: portable-windows + path: NearInfinity-*.zip + - name: Upload exe artifact (windows) if: (matrix.os == 'windows-latest') uses: actions/upload-artifact@v3 From 5577b93b47bcf1fa278c93ed03be523385c1d84b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 31 Oct 2023 13:14:51 +0100 Subject: [PATCH 16/23] Update GH action --- .github/workflows/deploy.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fb03d6f35..07fadba21 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -76,21 +76,6 @@ jobs: java -version # Preparations - - name: Download and install zip tool (windows) - if: (matrix.os == 'windows-latest') - run: | - $BaseDir=".\tools" - $UrlZip="http://downloads.sourceforge.net/gnuwin32/zip-3.0-bin.zip" - $PathZip="zip-3.0-bin.zip" - $UrlZipDep="http://downloads.sourceforge.net/gnuwin32/zip-3.0-dep.zip" - $PathZipDep="zip-3.0-dep.zip" - (New-Object System.Net.WebClient).DownloadFile($UrlZip, $PathZip) - (New-Object System.Net.WebClient).DownloadFile($UrlZipDep, $PathZipDep) - Expand-Archive -LiteralPath $PathZip -DestinationPath $BaseDir - Expand-Archive -LiteralPath $PathZipDep -DestinationPath $BaseDir - del $PathZip - del $PathZipDep - - name: Download JAR artifact uses: actions/download-artifact@v3 with: From 60c2c05520ee9cafb7c7b7ff6551ad041fffd84b Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 31 Oct 2023 19:45:42 +0100 Subject: [PATCH 17/23] Update GH action --- .github/workflows/deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 07fadba21..fd85e7579 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -92,8 +92,6 @@ jobs: - name: Build portable archive and installer (windows) if: (matrix.os == 'windows-latest') run: | - $BaseDir=".\tools" - $env:Path += ";$([IO.Path]::GetFullPath($BaseDir))\bin" move assets\redistributable\windows\package . move assets\redistributable\windows\build-image.cmd . move assets\redistributable\windows\build-installer.cmd . From 0ec3c89c87202f1d4b10a18c81d3e35424542a5e Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 1 Nov 2023 11:45:16 +0100 Subject: [PATCH 18/23] Auto-update indices in names of removable substructures after add/insert/remove operations in structured resources --- src/org/infinity/resource/AbstractStruct.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/org/infinity/resource/AbstractStruct.java b/src/org/infinity/resource/AbstractStruct.java index f025e3462..920119d94 100644 --- a/src/org/infinity/resource/AbstractStruct.java +++ b/src/org/infinity/resource/AbstractStruct.java @@ -19,6 +19,8 @@ import java.util.ListIterator; import java.util.Map; import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.swing.JComponent; @@ -588,6 +590,7 @@ public int addDatatype(AddRemovable addedEntry, int index) { } setStructChanged(true); fireTableRowsInserted(index, index); + updateRemovableIndices(addedEntry.getClass()); return index; } @@ -947,6 +950,7 @@ public void removeDatatype(AddRemovable removedEntry, boolean removeRecurse) { superStruct.datatypeRemovedInChild(this, removedEntry); } fireTableRowsDeleted(index, index); + updateRemovableIndices(removedEntry.getClass()); setStructChanged(true); } @@ -1047,6 +1051,52 @@ public void setStructChanged(boolean changed) { } } + /** + * Ensures that all {@link AddRemovable} of the specified type are properly indexed in sequential order. + * + * @param cls Class type of the AddRemovables to update. + */ + public void updateRemovableIndices(Class cls) { + if (cls == null) { + return; + } + + final List fieldList = new ArrayList<>(getFields(cls)); + if (fieldList.isEmpty()) { + return; + } + fieldList.sort((f1, f2) -> f1.getOffset() - f2.getOffset()); + + int minIndex = Integer.MAX_VALUE; + int maxIndex = Integer.MIN_VALUE; + final Pattern patName = Pattern.compile("^(.+) ([0-9]+)$"); + for (int i = 0, size = fieldList.size(); i < size; i++) { + final StructEntry entry = fieldList.get(i); + final Matcher m = patName.matcher(entry.getName()); + final String name; + if (m.matches()) { + // existing or copy-pasted entry + name = m.group(1); +// System.out.printf("Existing/pasted: name=%s, newIndex=%d\n", entry.getName(), i); + } else { + // newly added entry + name = entry.getName(); +// System.out.printf("Newly added: name=%s, newIndex=%d\n", name, i); + } + + final String newName = name + " " + i; + entry.setName(newName); + + final int fieldIdx = fields.indexOf(entry); + minIndex = Math.min(minIndex, fieldIdx); + maxIndex = Math.max(maxIndex, fieldIdx); + } + + if (minIndex != Integer.MAX_VALUE && maxIndex != Integer.MIN_VALUE) { + fireTableRowsUpdated(minIndex, maxIndex); + } + } + public String toMultiLineString() { final StringBuilder sb = new StringBuilder(30 * fields.size()); for (final StructEntry e : fields) { From 9bc14e20eba8dcabbfb0dc81dfac523cb0826994 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 6 Nov 2023 11:01:04 +0100 Subject: [PATCH 19/23] Added new game variant: IWD2EE Includes: - Specific game title - LUA resource type support - Updated executable to launch game --- src/org/infinity/resource/Profile.java | 21 +++++++++++++++++-- .../infinity/resource/ResourceFactory.java | 9 ++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index 809596f41..0a078e4e9 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -111,6 +111,8 @@ public enum Game { IWDHowTotLM(Engine.IWD, "Icewind Dale: Trials of the Luremaster"), /** Icewind Dale II */ IWD2(Engine.IWD2, "Icewind Dale II"), + /** Icewind Dale II: Enhanced Edition */ + IWD2EE(Engine.IWD2, "Icewind Dale II: Enhanced Edition"), /** Baldur's Gate: Enhanced Edition */ BG1EE(Engine.EE, "Baldur's Gate: Enhanced Edition"), /** Baldur's Gate: Siege of Dragonspear */ @@ -512,6 +514,7 @@ public enum Key { GAME_EXTRA_FOLDERS.put(Game.IWDHoW, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.IWDHowTotLM, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.IWD2, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); + GAME_EXTRA_FOLDERS.put(Game.IWD2EE, new ArrayList<>(Arrays.asList(BG_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG1EE, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG1SoD, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); GAME_EXTRA_FOLDERS.put(Game.BG2EE, new ArrayList<>(Arrays.asList(EE_EXTRA_FOLDERS))); @@ -533,6 +536,7 @@ public enum Key { GAME_SAVE_FOLDERS.put(Game.IWDHoW, new ArrayList<>(Arrays.asList(BG_SAVE_FOLDERS))); GAME_SAVE_FOLDERS.put(Game.IWDHowTotLM, new ArrayList<>(Arrays.asList(BG_SAVE_FOLDERS))); GAME_SAVE_FOLDERS.put(Game.IWD2, new ArrayList<>(Arrays.asList(BG_SAVE_FOLDERS))); + GAME_SAVE_FOLDERS.put(Game.IWD2EE, new ArrayList<>(Arrays.asList(BG_SAVE_FOLDERS))); GAME_SAVE_FOLDERS.put(Game.BG1EE, new ArrayList<>(Arrays.asList(EE_SAVE_FOLDERS))); GAME_SAVE_FOLDERS.put(Game.BG1SoD, new ArrayList<>(Arrays.asList(EE_SAVE_FOLDERS))); GAME_SAVE_FOLDERS.put(Game.BG2EE, new ArrayList<>(Arrays.asList(EE_SAVE_FOLDERS))); @@ -1527,6 +1531,15 @@ private static void initDefaultGameBinaries() { osMap.put(Platform.OS.WINDOWS, list); DEFAULT_GAME_BINARIES.put(Game.IWD2, osMap); + // IWD2EE (Windows) + osMap = new EnumMap<>(Platform.OS.class); + osMap.put(Platform.OS.UNIX, emptyList); + osMap.put(Platform.OS.MAC_OS, emptyList); + list = new ArrayList<>(); + list.add("iwd2ee.exe"); + osMap.put(Platform.OS.WINDOWS, list); + DEFAULT_GAME_BINARIES.put(Game.IWD2EE, osMap); + // BG1EE (Linux, macOS, Windows) osMap = new EnumMap<>(Platform.OS.class); list = new ArrayList<>(); @@ -1828,7 +1841,9 @@ && getLuaValue(FileManager.query(gameRoots, "engine.lua"), "engine_mode", "0", f if (ini != null && FileEx.create(ini).isFile()) { addEntry(Key.GET_GAME_INI_FILE, Type.PATH, ini); } - } else if (game == Game.IWD2 || (FileEx.create(FileManager.query(gameRoots, "iwd2.exe")).isFile()) + } else if (game == Game.IWD2 || game == Game.IWD2EE + || (FileEx.create(FileManager.query(gameRoots, "iwd2.exe")).isFile() + || FileEx.create(FileManager.query(gameRoots, "iwd2ee.exe")).isFile()) && (FileEx.create(FileManager.query(gameRoots, "Data/Credits.mve")).isFile())) { if (game == null) { game = Game.IWD2; @@ -1965,6 +1980,8 @@ && getLuaValue(FileManager.query(gameRoots, "engine.lua"), "engine_mode", "0", f } } else if (!isForced && game == Game.BG1 && ResourceFactory.resourceExists("DURLAG.MVE")) { game = Game.BG1TotSC; + } else if (!isForced && game == Game.IWD2 && FileEx.create(FileManager.query(gameRoots, "IEex.dll")).isFile()) { + game = Game.IWD2EE; } // updating game type @@ -2287,7 +2304,7 @@ private void initResourceTypes() { addEntry(Key.IS_SUPPORTED_LOG, Type.BOOLEAN, true); - addEntry(Key.IS_SUPPORTED_LUA, Type.BOOLEAN, isEnhancedEdition()); + addEntry(Key.IS_SUPPORTED_LUA, Type.BOOLEAN, isEnhancedEdition() || game == Game.IWD2EE); addEntry(Key.IS_SUPPORTED_MAZE, Type.BOOLEAN, game == Game.PSTEE); diff --git a/src/org/infinity/resource/ResourceFactory.java b/src/org/infinity/resource/ResourceFactory.java index c6d793c3a..eb9ae616e 100644 --- a/src/org/infinity/resource/ResourceFactory.java +++ b/src/org/infinity/resource/ResourceFactory.java @@ -164,10 +164,11 @@ public static Class getResourceType(ResourceEntry entry, Str } else if (ext.equals("MUS")) { cls = MusResource.class; } else if (ext.equals("IDS") || ext.equals("2DA") || ext.equals("BIO") || ext.equals("RES") || ext.equals("TXT") - || ext.equals("LOG") || // WeiDU log files - (ext.equals("SRC") && Profile.getEngine() == Profile.Engine.IWD2) - || (Profile.isEnhancedEdition() && (ext.equals("SQL") || ext.equals("GUI") || ext.equals("LUA") - || ext.equals("MENU") || ext.equals("GLSL")))) { + || ext.equals("LOG") // WeiDU log files + || (ext.equals("SRC") && Profile.getEngine() == Profile.Engine.IWD2) + || (ext.equals("LUA") && (Profile.isEnhancedEdition() || Profile.getGame() == Profile.Game.IWD2EE)) + || (Profile.isEnhancedEdition() + && (ext.equals("SQL") || ext.equals("GUI") || ext.equals("MENU") || ext.equals("GLSL")))) { cls = PlainTextResource.class; } else if (ext.equals("INI")) { final boolean isPST = Profile.getEngine() == Profile.Engine.PST; From 84a4e0f19ed063a3c271805e78c866dbc618e748 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sat, 18 Nov 2023 22:07:46 +0100 Subject: [PATCH 20/23] Remove "Nimbus" Look&Feel theme support Fixes #169 This theme was already incomplete in Java 8 and appears to be completely broken in later Java versions. --- src/org/infinity/gui/PreferencesDialog.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/gui/PreferencesDialog.java b/src/org/infinity/gui/PreferencesDialog.java index 547e5fc1c..256f45ac2 100644 --- a/src/org/infinity/gui/PreferencesDialog.java +++ b/src/org/infinity/gui/PreferencesDialog.java @@ -95,6 +95,14 @@ * that were formerly presented as individual entries in the "Options" menu. */ public class PreferencesDialog extends JDialog { + /** + * Blacklisted L&F themes: These themes should not be listed in the Preferences dialog. + *

+ * List contains the fully qualified class names of L&F themes. + *

+ */ + private static final List LOOK_AND_FEEL_BLACKLIST = Arrays.asList("javax.swing.plaf.nimbus.NimbusLookAndFeel"); + /** Definition of category names. */ public enum Category { DEFAULT(""), @@ -455,9 +463,7 @@ public String toString() { OptionGroupBox.create(AppOption.LOOK_AND_FEEL_CLASS.getName(), AppOption.LOOK_AND_FEEL_CLASS.getLabel(), "Choose a Look & Feel theme for the GUI." + "

Metal is the default L&F theme and provides the most consistent user experience. " - + "It is available on all platforms.

" - + "

Note: It is not recommended to use the \"Nimbus\" L&F theme. The theme " - + "initializes an incomplete set of UI properties, which can result in display errors.

", + + "It is available on all platforms.

", 0, new DataItem[0], AppOption.LOOK_AND_FEEL_CLASS) .setOnInit(this::lookAndFeelClassOnInit).setOnAccept(this::lookAndFeelClassOnAccept), OptionGroupBox.create(AppOption.TEXT_FONT.getName(), AppOption.TEXT_FONT.getLabel(), @@ -1379,6 +1385,12 @@ private void lookAndFeelClassOnInit(OptionGroupBox gb) { LookAndFeelInfo[] info = UIManager.getInstalledLookAndFeels(); for (int i = 0, curIdx = 0; i < info.length; i++) { final LookAndFeelInfo lf = info[i]; + + // check if theme is black-listed + if (lf != null && LOOK_AND_FEEL_BLACKLIST.contains(lf.getClassName())) { + continue; + } + try { // L&F description is only available from class instance final Class cls = Class.forName(lf.getClassName()); From dea0662c868a04cbb29e02da767208f00ea7a914 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Tue, 21 Nov 2023 20:16:30 +0100 Subject: [PATCH 21/23] Improve check for corrupted files: validate offset field values Triggered if offset points to static header data or outside of resource data --- src/org/infinity/check/StructChecker.java | 153 ++++++++++++++++++++-- 1 file changed, 141 insertions(+), 12 deletions(-) diff --git a/src/org/infinity/check/StructChecker.java b/src/org/infinity/check/StructChecker.java index f740bea03..15abd7a2b 100644 --- a/src/org/infinity/check/StructChecker.java +++ b/src/org/infinity/check/StructChecker.java @@ -11,6 +11,7 @@ import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -29,6 +30,7 @@ import org.infinity.datatype.IsNumeric; import org.infinity.datatype.IsReference; import org.infinity.datatype.IsTextual; +import org.infinity.datatype.SectionOffset; import org.infinity.datatype.StringRef; import org.infinity.datatype.TextString; import org.infinity.gui.Center; @@ -39,12 +41,16 @@ import org.infinity.gui.menu.BrowserMenuBar; import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; +import org.infinity.resource.AddRemovable; +import org.infinity.resource.Profile; import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; import org.infinity.resource.StructEntry; +import org.infinity.resource.Viewable; import org.infinity.resource.bcs.Compiler; import org.infinity.resource.bcs.ScriptMessage; import org.infinity.resource.bcs.ScriptType; +import org.infinity.resource.cre.CreResource; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.sto.ItemSale11; import org.infinity.resource.wed.Overlay; @@ -99,14 +105,12 @@ public void actionPerformed(ActionEvent event) { if (event.getSource() == bopen) { int row = table.getSelectedRow(); if (row != -1) { - ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - NearInfinity.getInstance().showResourceEntry(resourceEntry); + showInViewer((Corruption) table.getTableItemAt(row), false); } } else if (event.getSource() == bopennew) { int row = table.getSelectedRow(); if (row != -1) { - ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - new ViewFrame(resultFrame, ResourceFactory.getResource(resourceEntry)); + showInViewer((Corruption) table.getTableItemAt(row), true); } } else if (event.getSource() == bsave) { table.saveCheckResult(resultFrame, "Corrupted files"); @@ -170,10 +174,7 @@ public void mouseReleased(MouseEvent event) { if (event.getClickCount() == 2) { final int row = table.getSelectedRow(); if (row != -1) { - final ResourceEntry resourceEntry = (ResourceEntry) table.getValueAt(row, 0); - final Resource resource = ResourceFactory.getResource(resourceEntry); - new ViewFrame(resultFrame, resource); - ((AbstractStruct) resource).getViewer().selectEntry((String) table.getValueAt(row, 1)); + showInViewer((Corruption) table.getTableItemAt(row), true); } } } @@ -260,6 +261,67 @@ private void search(ResourceEntry entry, AbstractStruct struct) { } } + // Checking for valid substructure offsets and counts + // calculating size of static portion of the resource data + final HashSet> removableSet = new HashSet<>(); + int headerSize = 0; + if (Profile.hasProperty(Profile.Key.IS_SUPPORTED_CRE_V22) && entry.getExtension().equalsIgnoreCase("CRE")) { + // special: CRE V2.2 static size cannot be determined dynamically + final String version = ((TextString) struct.getAttribute(AbstractStruct.COMMON_VERSION)).getText(); + if ("V2.2".equalsIgnoreCase(version)) { + headerSize = 0x62e; + } + } + if (headerSize == 0) { + for (final StructEntry field : struct.getFields()) { + if (field instanceof SectionOffset) { + final Class cls = ((SectionOffset) field).getSection(); + removableSet.add(cls); + } + if (field instanceof AddRemovable || removableSet.contains(field.getClass())) { + headerSize = field.getOffset(); + break; + } else { + headerSize = field.getOffset() + field.getSize(); + } + } + } + removableSet.clear(); + + // CHR offset correction for embedded CRE data + int ofsOffset = 0; + if (entry.getExtension().equalsIgnoreCase("CHR")) { + final StructEntry se = struct.getAttribute(CreResource.CHR_SIGNATURE_2); + if (se != null) { + ofsOffset = se.getOffset(); + } + } + + // checking offsets + for (final StructEntry field : struct.getFields()) { + if (field.getOffset() >= headerSize) { + break; + } + if (field instanceof SectionOffset) { + final SectionOffset so = (SectionOffset) field; + if (so.getValue() + ofsOffset < headerSize) { + synchronized (table) { + table.addTableItem(new Corruption(entry, so.getOffset(), + "Offset field points to header data (field name: \"" + so.getName() + "\", offset: " + + Integer.toHexString(so.getValue()) + "h, header size: " + + Integer.toHexString(headerSize - ofsOffset) + "h)")); + } + } else if (so.getValue() + ofsOffset > struct.getSize()) { + synchronized (table) { + table.addTableItem(new Corruption(entry, so.getOffset(), + "Offset field value is out of range (field name: \"" + so.getName() + "\", offset: " + + Integer.toHexString(so.getValue()) + "h, resource size: " + + Integer.toHexString(struct.getSize() - ofsOffset) + "h)")); + } + } + } + } + // Type-specific checks if (entry.getExtension().equalsIgnoreCase("WED")) { List list = getWedCorruption(entry, struct); @@ -415,16 +477,71 @@ private List getWedCorruption(ResourceEntry entry, AbstractStruct st } return list; } + + /** + * Opens a view of the referenced resources and selects the field at the offset in question. + * + * @param corruption {@link Corruption} instance with error information. + * @param newWindow Whether to open the resource in a new window. + */ + private void showInViewer(Corruption corruption, boolean newWindow) { + if (corruption == null) { + return; + } + + if (newWindow) { + final ResourceEntry entry = corruption.getResourceEntry(); + final Resource res = ResourceFactory.getResource(entry); + final int offset = corruption.getOffset(); + new ViewFrame(resultFrame, res); + if (res instanceof AbstractStruct) { + try { + ((AbstractStruct) res).getViewer().selectEntry(offset); + } catch (Exception e) { + e.printStackTrace(); + } + } + } else { + final ResourceEntry entry = corruption.getResourceEntry(); + final int offset = corruption.getOffset(); + NearInfinity.getInstance().showResourceEntry(entry); + if (parent instanceof ViewFrame && parent.isVisible()) { + final Resource res = ResourceFactory.getResource(entry); + ((ViewFrame) parent).setViewable(res); + if (res instanceof AbstractStruct) { + try { + ((AbstractStruct) res).getViewer().selectEntry(offset); + } catch (Exception e) { + e.printStackTrace(); + } + } + } else { + NearInfinity.getInstance().showResourceEntry(entry, () -> { + final Viewable viewable = NearInfinity.getInstance().getViewable(); + if (viewable instanceof AbstractStruct) { + try { + ((AbstractStruct) viewable).getViewer().selectEntry(offset); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + } + // -------------------------- INNER CLASSES -------------------------- private static final class Corruption implements TableItem { private final ResourceEntry resourceEntry; - private final String offset; + private final int offset; + private final String offsetString; private final String errorMsg; private Corruption(ResourceEntry resourceEntry, int offset, String errorMsg) { this.resourceEntry = resourceEntry; - this.offset = Integer.toHexString(offset) + 'h'; + this.offset= offset; + this.offsetString = Integer.toHexString(offset) + 'h'; this.errorMsg = errorMsg; } @@ -433,15 +550,27 @@ public Object getObjectAt(int columnIndex) { if (columnIndex == 0) { return resourceEntry; } else if (columnIndex == 1) { - return offset; + return offsetString; } else { return errorMsg; } } + public ResourceEntry getResourceEntry() { + return resourceEntry; + } + + public int getOffset() { + return offset; + } + + public String getMessage() { + return errorMsg; + } + @Override public String toString() { - return "File: " + resourceEntry.getResourceName() + ", Offset: " + offset + ", Error: " + errorMsg; + return "File: " + resourceEntry.getResourceName() + ", Offset: " + offsetString + ", Error: " + errorMsg; } } From 6d1380adbb0e708e503a2165964cfbb56dac9a51 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Mon, 25 Dec 2023 20:41:48 +0100 Subject: [PATCH 22/23] Add option "Auto-align 2DA columns" Automatically aligns table columns when a 2DA resource is opened. Changes are discarded when resource is closed. --- src/org/infinity/AppOption.java | 3 ++ src/org/infinity/gui/PreferencesDialog.java | 10 ++++ .../infinity/gui/menu/OptionsMenuItem.java | 31 +++++++++++ .../resource/text/PlainTextResource.java | 52 +++++++++++++++++-- 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/org/infinity/AppOption.java b/src/org/infinity/AppOption.java index c693eaaf9..16034647b 100644 --- a/src/org/infinity/AppOption.java +++ b/src/org/infinity/AppOption.java @@ -206,6 +206,9 @@ public class AppOption { public static final AppOption BCS_INDENT = new AppOption(OptionsMenuItem.OPTION_BCS_INDENT, "Indentation", 1); // Category: Misc. Types + /** Menu Options > Misc. Types: AutoAlign2da (Integer, Default: 0) */ + public static final AppOption AUTO_ALIGN_2DA = new AppOption(OptionsMenuItem.OPTION_2DA_AUTOALIGN, + "Auto-Align 2DA Columns", 0); /** Menu Options > Misc. Types: GlslColorScheme (Integer, Default: 0) */ public static final AppOption GLSL_COLOR_SCHEME = new AppOption(OptionsMenuItem.OPTION_GLSL_COLORSCHEME, "GLSL Color Scheme", 0); diff --git a/src/org/infinity/gui/PreferencesDialog.java b/src/org/infinity/gui/PreferencesDialog.java index 256f45ac2..4d7465c49 100644 --- a/src/org/infinity/gui/PreferencesDialog.java +++ b/src/org/infinity/gui/PreferencesDialog.java @@ -285,6 +285,16 @@ public String toString() { ) ), OptionCategory.create(Category.MISC_RESOURCE_TYPES, + OptionGroup.create("2DA", + OptionGroupBox.create(AppOption.AUTO_ALIGN_2DA.getName(), AppOption.AUTO_ALIGN_2DA.getLabel(), + "Choose how to to automatically align 2DA table columns when the resource is opened.

" + + "Disabled: Table data is not modified.
" + + "Compact: Column widths are calculated individually.
" + + "Uniform: Column widths are calculated evenly (comparable to Weidu's PRETTY_PRINT_2DA.)" + + "

Note: Formatting is discarded when the resource is closed unless the changes " + + "are explicitly saved.

", + 0, OptionsMenuItem.AutoAlign2da.values(), AppOption.AUTO_ALIGN_2DA) + ), OptionGroup.create("GLSL", OptionGroupBox.create(AppOption.GLSL_COLOR_SCHEME.getName(), AppOption.GLSL_COLOR_SCHEME.getLabel(), "Select a color scheme for GLSL resources.

" diff --git a/src/org/infinity/gui/menu/OptionsMenuItem.java b/src/org/infinity/gui/menu/OptionsMenuItem.java index 86604f6f8..9a31ef589 100644 --- a/src/org/infinity/gui/menu/OptionsMenuItem.java +++ b/src/org/infinity/gui/menu/OptionsMenuItem.java @@ -58,6 +58,28 @@ * Handles Option menu items for the {@link BrowserMenuBar}. */ public class OptionsMenuItem extends JMenuItem implements ActionListener { + /** Alignment types available for 2DA resources. */ + public enum AutoAlign2da { + /** Do not align columns. */ + DISABLED("Disabled"), + /** Align columns individually. */ + COMPACT("Compact"), + /** Align columns evenly (comparable to WeiDU's PRETTY_PRINT_2DA). */ + UNIFORM("Uniform"), + ; + + private final String label; + + private AutoAlign2da(String label) { + this.label = label; + } + + @Override + public String toString() { + return label; + } + } + // Symbolic name for the default character set private static final String DEFAULT_CHARSET = "Auto"; @@ -151,6 +173,7 @@ public class OptionsMenuItem extends JMenuItem implements ActionListener { public static final String OPTION_BCS_CODEFOLDING = "BcsCodeFolding"; public static final String OPTION_BCS_AUTO_INDENT = "BcsAutoIndent"; public static final String OPTION_BCS_INDENT = "BcsIndent"; + public static final String OPTION_2DA_AUTOALIGN = "AutoAlign2da"; public static final String OPTION_GLSL_SYNTAXHIGHLIGHTING = "GlslSyntaxHighlighting"; public static final String OPTION_GLSL_COLORSCHEME = "GlslColorScheme"; public static final String OPTION_GLSL_CODEFOLDING = "GlslCodeFolding"; @@ -648,6 +671,14 @@ public String getWeiDUColorScheme() { return COLOR_SCHEME.get(idx).getPath(); } + public AutoAlign2da getAutoAlign2da() { + int idx = AppOption.AUTO_ALIGN_2DA.getIntValue(); + if (idx >= 0 && idx < AutoAlign2da.values().length) { + return AutoAlign2da.values()[idx]; + } + return AutoAlign2da.DISABLED; + } + /** Returns whether the dialog tree viewer shows icons in front of state and response entries. */ public boolean showDlgTreeIcons() { return AppOption.DLG_SHOW_ICONS.getBoolValue(); diff --git a/src/org/infinity/resource/text/PlainTextResource.java b/src/org/infinity/resource/text/PlainTextResource.java index 5b0f99c77..fbe77f4c9 100644 --- a/src/org/infinity/resource/text/PlainTextResource.java +++ b/src/org/infinity/resource/text/PlainTextResource.java @@ -137,6 +137,26 @@ public static String trimSpaces(String text, boolean trailing, boolean leading) return retVal; } + /** + * Aligns table columns individually. + * + * @param text The text content with table columns. + * @return The aligned text. Returns {@code null} if {@code text} argument is {@code null}. + */ + public static String alignTableColumnsCompact(String text) { + return alignTableColumns(text, 2, true, 4); + } + + /** + * Aligns all table columns evenly, comparable to WeiDU's PRETTY_PRINT_2DA. + * + * @param text The text content with table columns. + * @return The aligned text. Returns {@code null} if {@code text} argument is {@code null}. + */ + public static String alignTableColumnsUniform(String text) { + return alignTableColumns(text, 1, false, 1); + } + /** * Aligns table columns to improve readability. * @@ -329,7 +349,7 @@ public PlainTextResource(ResourceEntry entry) throws Exception { buffer = StaticSimpleXorDecryptor.decrypt(buffer, 2); } final Charset cs = Misc.getCharsetFrom(BrowserMenuBar.getInstance().getOptions().getSelectedCharset()); - text = StreamUtils.readString(buffer, buffer.limit(), cs); + text = applyTransformText(StreamUtils.readString(buffer, buffer.limit(), cs)); } // --------------------- Begin Interface ActionListener --------------------- @@ -416,9 +436,9 @@ public void itemStateChanged(ItemEvent event) { if (bpmFormat.getSelectedItem() == miFormatTrim) { setText(trimSpaces(editor.getText(), true, false)); } else if (bpmFormat.getSelectedItem() == miFormatAlignCompact) { - setText(alignTableColumns(editor.getText(), 2, true, 4)); + setText(alignTableColumnsCompact(editor.getText())); } else if (bpmFormat.getSelectedItem() == miFormatAlignUniform) { - setText(alignTableColumns(editor.getText(), 1, false, 1)); + setText(alignTableColumnsUniform(editor.getText())); } else if (bpmFormat.getSelectedItem() == miFormatSort) { setText(sortTable(editor.getText(), true, entry.getResourceRef().equalsIgnoreCase("TRIGGER"))); } @@ -638,4 +658,30 @@ private void setSyntaxHighlightingEnabled(InfinityTextArea edit, InfinityScrollP pane.applyExtendedSettings(language); } } + + private String applyTransformText(String data) { + if (data == null) { + return data; + } + + final String ext = (entry != null) ? entry.getExtension() : ""; + if (ext.equals("2DA")) { + return applyAutoAlign2da(data); + } + + return data; + } + + private String applyAutoAlign2da(String data) { + switch (BrowserMenuBar.getInstance().getOptions().getAutoAlign2da()) { + case COMPACT: + return alignTableColumnsCompact(data); + case UNIFORM: + return alignTableColumnsUniform(data); + default: + } + + return data; + } + } From b1f75e628909eb0279c3a71f49d32021be528dae Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Sun, 31 Dec 2023 11:26:46 +0100 Subject: [PATCH 23/23] Version 2.4-20231231 --- src/org/infinity/NearInfinity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 96ad70ec9..f6517e91f 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -136,7 +136,7 @@ public final class NearInfinity extends JFrame implements ActionListener, ViewableContainer { // the current Near Infinity version - private static final String VERSION = "v2.4-20230729"; + private static final String VERSION = "v2.4-20231231"; // the minimum supported Java version private static final int JAVA_VERSION_MIN = 8;