/* * 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.*; import dotaSoundEditor.NamedVoice; import info.ata4.vpk.VPKArchive; import info.ata4.vpk.VPKEntry; import java.awt.Dimension; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import javax.swing.ImageIcon; import javax.swing.JOptionPane; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeModel; import javax.swing.tree.TreePath; public class VoicePanel extends EditorPanel { Executor e = Executors.newSingleThreadExecutor(); PortraitFinder portraitFinder; Path voiceVpkPath; public VoicePanel() { initComponents(); } public VoicePanel(String _vpkPath, String _installDir, CacheManager _cm, SoundPlayer _sp) { vpkPath = _vpkPath; installDir = _installDir; voiceVpkPath = findVoiceVpkPath(); this.setName("Voices"); initComponents(); soundPlayer = _sp; cacheManager = _cm; portraitFinder = Utility.portraitFinder; currentDropdown = voiceDropdown; currentTree = voiceTree; this.populateDropdownBox(); this.attachDoubleClickListenerToTree(); this.setVisible(true); } @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { voiceImageLabel = new javax.swing.JLabel(); voiceLabel = new javax.swing.JLabel(); voiceDropdown = new javax.swing.JComboBox(); voicePanelScrollFrame = new javax.swing.JScrollPane(); voiceImageLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER); voiceImageLabel.setMaximumSize(new java.awt.Dimension(128, 72)); voiceImageLabel.setMinimumSize(new java.awt.Dimension(128, 72)); voiceImageLabel.setPreferredSize(new java.awt.Dimension(128, 72)); voiceLabel.setText("Voice"); voiceLabel.setName("voiceLabel"); // NOI18N voiceDropdown.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); voiceDropdown.setName("voiceDropdownBox"); // NOI18N voiceDropdown.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(java.awt.event.ItemEvent evt) { voiceDropdownStateChanged(evt); } }); voicePanelScrollFrame.setName("voiceListFrame"); // NOI18N voiceTree.setMinimumSize(new java.awt.Dimension(72, 64)); voicePanelScrollFrame.setViewportView(voiceTree); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 485, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(voiceLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(voiceDropdown, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, Short.MAX_VALUE)) .addComponent(voicePanelScrollFrame, javax.swing.GroupLayout.DEFAULT_SIZE, 465, Short.MAX_VALUE) .addGroup(layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) .addComponent(voiceImageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap())) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 495, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(voiceLabel) .addComponent(voiceDropdown, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(voiceImageLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(voicePanelScrollFrame, javax.swing.GroupLayout.DEFAULT_SIZE, 369, Short.MAX_VALUE) .addContainerGap())) ); }// </editor-fold>//GEN-END:initComponents private void voiceDropdownStateChanged(java.awt.event.ItemEvent evt)//GEN-FIRST:event_voiceDropdownStateChanged {//GEN-HEADEREND:event_voiceDropdownStateChanged //TODO: Find a way to inform the Advanced button that it needs to reset. if (evt.getStateChange() == ItemEvent.SELECTED) { System.out.println("Dropdown selected: " + currentDropdown.getSelectedItem().toString()); //In a background thread so the app doesn't choke on fast scroling if (!getAdvancedMode()) { e.execute(new Runnable() { @Override public void run() { populateSoundList(); } }); } else { String scriptPath = getCurrentScriptString(); ScriptParser parser = new ScriptParser(new File(Paths.get(scriptPath).toString())); TreeModel model = parser.getTreeModel(); currentTree.setModel(model); currentTree.setEditable(true); for (int i = 0; i < currentTree.getRowCount(); i++) { currentTree.expandRow(i); } } currentTree.setRootVisible(false); currentTree.setShowsRootHandles(true); try { fillImageFrame((NamedVoice) currentDropdown.getSelectedItem()); } catch (IOException ex) { System.err.println(ex.getMessage()); } } }//GEN-LAST:event_voiceDropdownStateChanged // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JComboBox voiceDropdown; private javax.swing.JLabel voiceImageLabel; private javax.swing.JLabel voiceLabel; private javax.swing.JScrollPane voicePanelScrollFrame; private final javax.swing.JTree voiceTree = new javax.swing.JTree(); // End of variables declaration//GEN-END:variables @Override void fillImageFrame(Object _selectedItem) throws IOException { NamedVoice selectedItem; if (_selectedItem instanceof NamedVoice) { selectedItem = (NamedVoice) _selectedItem; } else { return; } try { BufferedImage icon = portraitFinder.getPortrait(selectedItem.getIconName()); voiceImageLabel.setMaximumSize(new Dimension(icon.getWidth(), icon.getHeight())); voiceImageLabel.setPreferredSize(new Dimension(icon.getWidth(), icon.getHeight())); voiceImageLabel.setIcon(new ImageIcon(icon)); } catch (NullPointerException ex) { System.err.println("Icon not found for voice: " + selectedItem.getFriendlyName()); voiceImageLabel.setIcon(new ImageIcon("")); } } @Override void populateDropdownBox() { currentDropdown.removeAllItems(); ArrayList<NamedVoice> namedVoiceList = new ArrayList<>(); File file = new File(vpkPath); VPKArchive vpk = new VPKArchive(); try { vpk.load(file); } catch (Exception ex) { System.err.println("Cant open archive " + ex.getMessage()); return; } String vpkVoiceSoundsDir = "scripts/voscripts/"; for (VPKEntry entry : vpk.getEntriesForDir(vpkVoiceSoundsDir)) { String internalName = entry.getName(); if (internalName.contains("phonemes")) { continue; } internalName = internalName.replace("game_sounds_vo_", ""); NamedVoice nv = new NamedVoice(internalName, entry.getPath()); namedVoiceList.add(nv); } Collections.sort(namedVoiceList); for (NamedVoice nv : namedVoiceList) { currentDropdown.addItem(nv); } } @Override void populateSoundList() { inAdvancedMode = false; currentTree.setEditable(false); NamedVoice selectedVoice = (NamedVoice) currentDropdown.getSelectedItem(); Path scriptPath = Paths.get(this.installDir, "/dota/scripts/voscripts/game_sounds_vo_" + selectedVoice.getInternalName() + ".txt"); File scriptFile = new File(scriptPath.toString()); String scriptKey = "game_sounds_vo_" + selectedVoice.getInternalName() + ".txt".toLowerCase(); boolean needsValidation = false; VPKEntry entry; if (!scriptFile.isFile()) { entry = getVoiceScriptFile(selectedVoice.getInternalName()); writeScriptFileToDisk(entry, false); updateCache(scriptKey, entry.getCRC32()); } else { needsValidation = true; } ScriptParser parser = new ScriptParser(scriptPath.toFile()); TreeModel scriptTree = parser.getTreeModel(); if (needsValidation) { boolean isUpToDate = this.validateScriptFile(scriptKey, "scripts/voscripts/" + scriptKey); if (!isUpToDate) { this.writeScriptFileToDisk(cacheManager.getCachedVpkEntry(), true); mergeNewChanges(scriptTree, scriptPath); this.updateCache(cacheManager.getCachedVpkEntry().getName() + ".txt", cacheManager.getCachedVpkEntry().getCRC32()); } } this.currentTreeModel = scriptTree; currentTree.setModel(buildSoundListTree(scriptTree)); } @Override String getCurrentScriptString() { return this.getScriptPathByVoiceName(((NamedVoice) currentDropdown.getSelectedItem()).getInternalName()); } private String getScriptPathByVoiceName(String internalName) { String scriptPathString = Paths.get(installDir, "/dota/scripts/voscripts/game_sounds_vo_" + internalName + ".txt").toString(); File scriptFilePath = new File(scriptPathString); if (scriptFilePath.isFile()) { return scriptFilePath.getAbsolutePath(); } else { return null; } } @Override String getCustomSoundPathString() { return Paths.get("custom", ((NamedVoice) currentDropdown.getSelectedItem()).getInternalName()).toString() + File.separator; } private VPKEntry getVoiceScriptFile(String voiceName) { voiceName = voiceName.toLowerCase(); String internalScriptPath = "scripts/voscripts/game_sounds_vo_" + voiceName + ".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/voscripts/game_sounds_vo_" + ((NamedVoice) currentDropdown.getSelectedItem()).getInternalName() + ".txt"; cacheManager.putScript(scriptKey, internalPath, internalCrc); } @Override protected void attachDoubleClickListenerToTree() { MouseListener ml = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { int selRow = currentTree.getRowForLocation(e.getX(), e.getY()); TreePath selPath = currentTree.getPathForLocation(e.getX(), e.getY()); if (selRow != -1 && ((DefaultMutableTreeNode) selPath.getLastPathComponent()).isLeaf()) { if (e.getClickCount() == 2) { playSelectedTreeSound(selPath, voiceVpkPath); } } } }; currentTree.addMouseListener(ml); } @Override protected void playSoundButtonActionPerformed(ActionEvent evt) { playSoundButtonActionPerformed(evt, voiceVpkPath); } private Path findVoiceVpkPath() { return Paths.get(installDir + "/dota/sound_vo_english_dir.vpk"); } }