/*
* The MIT License
*
* Copyright 2015 Neil McAlister.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package dotaSoundEditor.Controls;
import dotaSoundEditor.Helpers.PortraitFinder;
import dotaSoundEditor.Helpers.ScriptParser;
import dotaSoundEditor.Helpers.Utility;
import dotaSoundEditor.*;
import dotaSoundEditor.Helpers.*;
import info.ata4.vpk.VPKArchive;
import info.ata4.vpk.VPKEntry;
import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.io.FilenameUtils;
/**
*
* @author Image 17
*/
public final class ItemPanel extends EditorPanel
{
PortraitFinder portraitFinder;
public ItemPanel()
{
initComponents();
}
public ItemPanel(String _vpkPath, String _installDir, CacheManager _cm, SoundPlayer _sp)
{
vpkPath = _vpkPath;
installDir = _installDir;
this.setName("Items");
initComponents();
soundPlayer = _sp;
cacheManager = _cm;
currentTree = itemTree;
portraitFinder = Utility.portraitFinder;
this.populateSoundList();
initTreeSelectionListener();
fillImageFrame("default");
ToolTipManager.sharedInstance().setDismissDelay(20000);
ToolTipManager.sharedInstance().setInitialDelay(0);
}
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents()
{
itemImageLabel = new javax.swing.JLabel();
jScrollPane2 = new javax.swing.JScrollPane();
itemTree = new javax.swing.JTree();
itemLabel = new javax.swing.JLabel();
itemHelpLabel = new javax.swing.JLabel();
itemImageLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
itemImageLabel.setMaximumSize(new java.awt.Dimension(128, 72));
itemImageLabel.setMinimumSize(new java.awt.Dimension(128, 72));
itemImageLabel.setPreferredSize(new java.awt.Dimension(124, 64));
jScrollPane2.setViewportView(itemTree);
itemLabel.setText("Item:");
itemHelpLabel.setForeground(new java.awt.Color(255, 0, 0));
itemHelpLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT);
itemHelpLabel.setText("<html><p align=\"right\">Sounds not working?<br>Mouseover here!</p></html>");
itemHelpLabel.setToolTipText("<html>Item sound replacements are more restricted than other sounds.\n<br>Item sounds must be the same file type (WAV or MP3) as the original. \n<brIf the new sound is longer than the original, it will be cut off.\n<br>They must also match the bitrate (128 kpbs for MP3s, 1141 kbps for WAVs) and frequency (44.1KHz) of the original.</html>");
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(itemLabel)
.addGap(0, 0, Short.MAX_VALUE))
.addComponent(jScrollPane2)
.addGroup(layout.createSequentialGroup()
.addGap(0, 120, Short.MAX_VALUE)
.addComponent(itemImageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 38, Short.MAX_VALUE)
.addComponent(itemHelpLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(itemLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(itemImageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 64, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(itemHelpLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 383, Short.MAX_VALUE)
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel itemHelpLabel;
private javax.swing.JLabel itemImageLabel;
private javax.swing.JLabel itemLabel;
private javax.swing.JTree itemTree;
private javax.swing.JScrollPane jScrollPane2;
// End of variables declaration//GEN-END:variables
@Override
void fillImageFrame(Object _selectedItem)
{
NamedItem selectedItem = new NamedItem();
try
{
if (_selectedItem instanceof NamedItem)
{
selectedItem = (NamedItem) _selectedItem;
itemImageLabel.setIcon(new ImageIcon(portraitFinder.getPortrait(selectedItem.getIconName())));
}
else
{
itemImageLabel.setIcon(new ImageIcon(portraitFinder.getPortrait("default")));
}
}
catch (NullPointerException ex)
{
System.err.println("Icon not found for item: " + selectedItem.getFriendlyName());
itemImageLabel.setIcon(new ImageIcon(""));
}
}
//This panel doesn't use a dropdown box. No need to implement.
@Override
void populateDropdownBox()
{
}
@Override
void populateSoundList()
{
inAdvancedMode = false;
currentTree.setEditable(false);
String scriptKey = "game_sounds_items.txt";
File scriptFile = new File(getCurrentScriptString());
VPKEntry entry;
boolean needsValidation = false;
if (!scriptFile.isFile())
{
entry = getItemScriptFile();
this.writeScriptFileToDisk(entry, false);
this.updateCache(scriptKey, entry.getCRC32());
scriptFile = new File(getCurrentScriptString());
}
else
{
needsValidation = true;
}
ScriptParser parser = new ScriptParser(scriptFile);
TreeModel scriptTree = parser.getTreeModel();
if (needsValidation)
{
boolean isUpToDate = this.validateScriptFile(scriptKey, "scripts/" + scriptKey);
if (!isUpToDate)
{
this.writeScriptFileToDisk(cacheManager.getCachedVpkEntry(), true);
mergeNewChanges(scriptTree, scriptFile);
this.updateCache(cacheManager.getCachedVpkEntry().getName() + ".txt", cacheManager.getCachedVpkEntry().getCRC32());
}
}
this.currentTreeModel = scriptTree;
currentTree.setModel(buildSoundListTree(scriptTree));
currentTree.setRootVisible(false);
currentTree.setShowsRootHandles(true);
}
@Override
String getCurrentScriptString()
{
String scriptPathString = Paths.get(installDir, "/dota/scripts/game_sounds_items.txt").toString();
File scriptFilePath = new File(scriptPathString);
if (scriptFilePath.isFile())
{
return scriptFilePath.getAbsolutePath();
}
else
{
return "";
}
}
@Override
String getCustomSoundPathString()
{
return "custom/items/";
}
private VPKEntry getItemScriptFile()
{
String internalScriptPath = "scripts/game_sounds_items.txt";
File vpkFile = new File(vpkPath);
VPKArchive vpk = new VPKArchive();
try
{
vpk.load(vpkFile);
}
catch (Exception ex)
{
JOptionPane.showMessageDialog(this,
"Error: Unable to open VPK file.\nDetails: " + ex.getMessage(),
"Error opening VPK", JOptionPane.ERROR_MESSAGE);
ex.printStackTrace();
return null;
}
VPKEntry entry = vpk.getEntry(internalScriptPath);
return entry;
}
@Override
void updateCache(String scriptKey, long internalCrc)
{
String internalPath = "scripts/game_sounds_items.txt";
cacheManager.putScript(scriptKey, internalPath, internalCrc);
}
private void initTreeSelectionListener()
{
currentTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
currentTree.addTreeSelectionListener(new TreeSelectionListener()
{
@Override
public void valueChanged(TreeSelectionEvent evt)
{
currentTreeSelectionChanged(evt);
}
});
attachDoubleClickListenerToTree();
}
//Need this for ItemPanel because it's only a single script file, and thus each node requires a different portrait
private void currentTreeSelectionChanged(TreeSelectionEvent evt)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) currentTree.getLastSelectedPathComponent();
if (node == null)
{
return;
}
//get highest parent of selected node
while (node.getParent() != null && !node.getParent().equals(node.getRoot()))
{
node = (DefaultMutableTreeNode) node.getParent();
}
NamedItem clickedItem = new NamedItem(node.getUserObject().toString(), this.getCurrentScriptString());
fillImageFrame(clickedItem);
itemLabel.setText("Item: " + clickedItem.getFriendlyName());
}
//For whatever reasons, item sounds replacements REFUSE to work unless the
//filename remains unchanged, so we're overriding the logic here to copy the
//custom sound, rename it and just note what it used to be in a wavestring
//comment. Additionally, it limits the filetypes we're allowed to use,
//because we have to use whatever the original was.
@Override
protected File promptUserForNewFile(String wavePath)
{
DefaultMutableTreeNode selectedTreeNode = (DefaultMutableTreeNode) getTreeNodeFromWavePath(wavePath);
String waveString = selectedTreeNode.getUserObject().toString();
String allowedExtension = FilenameUtils.getExtension(waveString).replace("\"", "");
JFileChooser chooser = new JFileChooser(new File(UserPrefs.getInstance().getWorkingDirectory()));
FileNameExtensionFilter filter = allowedExtension.equals("wav")
? new FileNameExtensionFilter("WAVs", "wav")
: new FileNameExtensionFilter("MP3s", "mp3");
chooser.setAcceptAllFileFilterUsed((false));
chooser.setFileFilter(filter);
chooser.setMultiSelectionEnabled(false);
int chooserRetVal = chooser.showOpenDialog(chooser);
if (chooserRetVal == JFileChooser.APPROVE_OPTION)
{
Path chosenFile = Paths.get(chooser.getSelectedFile().getAbsolutePath());
int startIndex = -1;
int endIndex = -1;
//Get the actual value for the wavestring key-value pair.
if (waveString.contains("\"wave\""))
{
startIndex = Utility.nthOccurrence(selectedTreeNode.getUserObject().toString(), '\"', 2);
endIndex = Utility.nthOccurrence(selectedTreeNode.getUserObject().toString(), '\"', 3);
}
else //Some wavestrings don't have the "wave" at the beginning for some reason
{
startIndex = Utility.nthOccurrence(selectedTreeNode.getUserObject().toString(), '\"', 0);
endIndex = Utility.nthOccurrence(selectedTreeNode.getUserObject().toString(), '\"', 1);
}
String waveStringFilePath = waveString.substring(startIndex, endIndex + 1);
String waveStringNormalizedFilePath = waveStringFilePath.substring(0, waveStringFilePath.lastIndexOf("\""));
waveStringNormalizedFilePath = waveStringNormalizedFilePath.replace(")", "");
waveStringNormalizedFilePath = waveStringNormalizedFilePath.replace("\"", "");
Path destPath = Paths.get(installDir, "/dota/sound/" + waveStringNormalizedFilePath);
UserPrefs.getInstance().setWorkingDirectory(chosenFile.getParent().toString());
try
{
new File(destPath.toString()).mkdirs();
Files.copy(chosenFile, destPath, StandardCopyOption.REPLACE_EXISTING);
if (waveString.contains("//")) { waveString = waveString.replace(waveString.substring(waveString.indexOf("//"), waveString.length()), ""); }
waveString = waveString.replace(waveStringFilePath, "\"" + waveStringNormalizedFilePath + "\" //Replaced by: " + chosenFile.getFileName().toString());
selectedTreeNode.setUserObject(waveString);
//Write out modified tree to scriptfile.
ScriptParser parser = new ScriptParser(this.currentTreeModel);
String scriptString = getCurrentScriptString();
Path scriptPath = Paths.get(scriptString);
parser.writeModelToFile(scriptPath.toString());
//Update UI
((DefaultMutableTreeNode) currentTree.getLastSelectedPathComponent()).setUserObject(waveString);
((DefaultTreeModel) currentTree.getModel()).nodeChanged((DefaultMutableTreeNode) currentTree.getLastSelectedPathComponent());
JOptionPane.showMessageDialog(this, "Sound file successfully replaced.");
}
catch (IOException ex)
{
JOptionPane.showMessageDialog(null, "Unable to replace sound.\nDetails: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
return null;
}
}