dotaSoundEditor.Controls.ItemPanel.java Source code

Java tutorial

Introduction

Here is the source code for dotaSoundEditor.Controls.ItemPanel.java

Source

/* 
 * 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;
    }
}