/*
* Copyright (C) 2011 Thedeath<www.fseek.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package mpq.Tree;
import java.awt.Desktop;
import java.awt.GridLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DropMode;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.WindowConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import mpq.ExtMpqArchive;
import mpq.MpqArchiveComparator;
import mpq.MpqUtil;
import mwt.wow.mpq.MpqFile;
import wowimage.ConversionException;
public class MpqTree extends JTree
{
private DefaultTreeModel model;
private ExtMpqArchive[] archives;
private HashMap<String, ExtMpqArchive> archiveMap = new HashMap<String, ExtMpqArchive>();
private boolean finishedBuildTree = false;
public MpqTree(ExtMpqArchive ext)
{
this(new ExtMpqArchive[]
{
ext
});
}
public MpqTree(ExtMpqArchive[] ext)
{
super();
this.archives = ext;
intModel();
intListener();
intDragAndDrop();
}
public MpqTree()
{
super();
intModel();
intListener();
intDragAndDrop();
}
private void intDragAndDrop()
{
this.setTransferHandler(new MpqTreeTransferHandler(this));
this.setDragEnabled(true);
this.setDropMode(DropMode.ON_OR_INSERT);
this.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
}
private void implMouseClicked(MouseEvent e)
{
TreePath path = getClosestPathForLocation(e.getX(), e.getY());
if (e.getButton() == 1 && e.getClickCount() >= 2)
{
doubleClickOnNode(path);
}
}
private void rightClickOnNode(TreePath path, MouseEvent e)
{
if (path == null)
{
return;
}
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
String toLowerCase = node.getUserObject().toString().toLowerCase();
if (toLowerCase.endsWith(".mpq"))
{
if (e.isPopupTrigger())
{
MpqTreePopupMenu menu = new MpqTreePopupMenu(this, node, true);
menu.show(e.getComponent(),
e.getX(), e.getY());
}
}
else
{
if (e.isPopupTrigger())
{
MpqTreePopupMenu menu = new MpqTreePopupMenu(this, node, false);
menu.show(e.getComponent(),
e.getX(), e.getY());
}
}
}
public void removeSelectedArchives()
{
TreePath[] selectionPaths = this.getSelectionPaths();
for (int i = 0; i < selectionPaths.length; i++)
{
TreePath path = selectionPaths[i];
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
String toLowerCase = node.getUserObject().toString().toLowerCase();
if (toLowerCase.endsWith(".mpq"))
{
ExtMpqArchive mpqArchiveOfPath = getMpqArchiveOfPath(path);
removeMpqArchive(mpqArchiveOfPath);
}
}
}
private void doubleClickOnNode(TreePath path)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
if (node.getUserObject().toString().toLowerCase().endsWith(".blp"))
{
try
{
MpqFile file = getMqpFileOfPath(path);
showImage(MpqUtil.convertMpqFileToImage(file), MpqUtil.getMpqFileName(file));
} catch (ConversionException ex)
{
JOptionPane.showMessageDialog(this, "This file cant be opened ! (Reason: " + ex.getMessage() + ")");
} catch (IOException ex)
{
JOptionPane.showMessageDialog(this, "This file cant be opened ! (Reason: Some IO Problem)");
} catch (RuntimeException ex)
{
ex.printStackTrace();
JOptionPane.showMessageDialog(this, "This file cant be opened ! (Reason: unknown)");
}
}
else
{
try
{
final MpqFile file = getMqpFileOfPath(path);
if(file == null)return;
if(file.getFileSize() > 1140615)
{
if (Desktop.isDesktopSupported())
{
File createTempFile = File.createTempFile(MpqUtil.getMpqFileName(file), null);
file.extractTo(createTempFile);
Desktop.getDesktop().edit(createTempFile);
return;
}
}
final JFrame frame = new JFrame();
frame.setTitle("TextViewer");
frame.setLayout(new GridLayout(1, 1));
final JTextArea jTextArea = new JTextArea();
jTextArea.setText("Loading...");
jTextArea.setEditable(false);
JScrollPane scrollpane = new JScrollPane(jTextArea);
frame.add(scrollpane);
new Thread()
{
@Override
public void run()
{
try
{
String convertMpqFileToString = MpqUtil.convertMpqFileToString(file);
if(convertMpqFileToString != null)
{
jTextArea.setText(convertMpqFileToString);
}
else
{
frame.dispose();
}
} catch (IOException ex)
{
Logger.getLogger(MpqTree.class.getName()).log(Level.SEVERE, null, ex);
}
}
}.start();
frame.setSize(600, 800);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setVisible(true);
} catch (IOException ex)
{
Logger.getLogger(MpqTree.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public MpqFile getMqpFileOfPath(TreePath path) throws IOException
{
String[] convertTreePath = convertTreePath(path);
// get ExtMpqArchive to mpqName
ExtMpqArchive get = getMpqArchiveOfPath(convertTreePath);
MpqFile file = get.getFile(convertTreePath[1], 0, 0);
return file;
}
public ExtMpqArchive getMpqArchiveOfPath(TreePath path)
{
String[] convertTreePath = convertTreePath(path);
return getMpqArchiveOfPath(convertTreePath);
}
public boolean isOnlyMpqArchive(TreePath path)
{
String[] convertTreePath = convertTreePath(path);
if (convertTreePath[1] == null || convertTreePath[1].equals(""))
{
return true;
}
return false;
}
private ExtMpqArchive getMpqArchiveOfPath(String[] convertTreePath)
{
// get ExtMpqArchive to mpqName
ExtMpqArchive get = this.archiveMap.get(convertTreePath[0]);
return get;
}
private void showImage(BufferedImage img)
{
showImage(img, "");
}
private void showImage(BufferedImage img, String windowTitle)
{
JFrame frame = new JFrame();
frame.setTitle(windowTitle);
frame.setLayout(new GridLayout(1, 1));
frame.add(new JLabel(new ImageIcon(img)));
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
private String convertTreePathImpl(String s, TreePath path)
{
if (path == null)
{
return s;
}
DefaultMutableTreeNode lastPathComponent = (DefaultMutableTreeNode) path.getLastPathComponent();
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
if (lastPathComponent == root)
{
return s;
}
s = s + lastPathComponent.getUserObject().toString() + "\\";
s = convertTreePathImpl(s, path.getParentPath());
if (s.endsWith("\\"))
{
int indexOf = s.lastIndexOf("\\");
if (indexOf != -1)
{
s = s.substring(0, indexOf);
}
}
return s;
}
/*
* @return String[0] = mpqName || String[1] = fileName
*/
private String[] convertTreePath(TreePath path)
{
String convertedTreePath = convertTreePathImpl(new String(), path);
String[] split = convertedTreePath.split("\\\\");
String reversePath = "";
String mpqName = split[split.length - 1];
// -2 because index starts with 0 and last index is the mpqName
int startIndex = split.length - 2;
//reverse path
for (int i = startIndex; i >= 0; i--)
{
if (i == startIndex)
{
reversePath = reversePath + split[i];
} else
{
reversePath = reversePath + "\\" + split[i];
}
}
return new String[]
{
mpqName, reversePath
};
}
private void intListener()
{
this.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
implMouseClicked(e);
}
@Override
public void mousePressed(MouseEvent e)
{
TreePath path = getClosestPathForLocation(e.getX(), e.getY());
rightClickOnNode(path, e);
}
@Override
public void mouseReleased(MouseEvent e)
{
TreePath path = getClosestPathForLocation(e.getX(), e.getY());
rightClickOnNode(path, e);
}
});
this.addKeyListener(new KeyAdapter()
{
@Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_DELETE)
{
removeSelectedArchives();
}
}
});
}
private void intModel()
{
DefaultMutableTreeNode root = new DefaultMutableTreeNode("MpqFiles");
model = new DefaultTreeModel(root);
this.setModel(model);
this.setRootVisible(false);
if (archives != null)
{
Arrays.sort(archives, new MpqArchiveComparator(true));
for (ExtMpqArchive arch : this.archives)
{
addMpqArchive(arch);
}
this.archives = null;
}
}
public void addMpqArchive(ExtMpqArchive arch, int index)
{
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
String archiveName = arch.getArchiveFile().getName();
this.archiveMap.put(archiveName, arch);
DefaultMutableTreeNode defaultMutableTreeNode = new DefaultMutableTreeNode(archiveName);
model.insertNodeInto(defaultMutableTreeNode, root, index);
Thread t = new MpqTreeBuilder(defaultMutableTreeNode, arch);
t.start();
this.expandPath(new TreePath(root));
}
public void addMpqArchive(ExtMpqArchive arch)
{
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
int childCount = root.getChildCount();
if (childCount == -1)
{
childCount = 0;
}
addMpqArchive(arch, childCount);
}
public void removeMpqArchive(ExtMpqArchive arch)
{
String name = arch.getArchiveFile().getName();
removeMpqArchive(name);
}
public void removeMpqArchive(String archiveName)
{
ExtMpqArchive remove = this.archiveMap.remove(archiveName);
try
{
remove.close();
} catch (IOException ex)
{
Logger.getLogger(MpqTree.class.getName()).log(Level.SEVERE, null, ex);
}
DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot();
for (int i = 0; i < root.getChildCount(); i++)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) root.getChildAt(i);
if (node.getUserObject().toString().equals(archiveName))
{
this.model.removeNodeFromParent(node);
break;
}
}
}
// builds the tree for the given archive and append it to the given node
public void buildTree(DefaultMutableTreeNode node, ExtMpqArchive arch)
{
try
{
ArrayList<String> files = arch.getFiles();
for (String s : files)
{
addDirectories(s, node);
}
finishedBuildTree = true;
} catch (IOException ex)
{
Logger.getLogger(MpqTree.class.getName()).log(Level.SEVERE, null, ex);
}
}
private void addDirectories(String completeFileName, DefaultMutableTreeNode node)
{
// I have to escape the "\" twice.
String[] split = completeFileName.split("\\\\");
addDirectoriesImpl(node, split, 0);
}
private void addDirectoriesImpl(DefaultMutableTreeNode parent, String[] split, int start)
{
if (start >= split.length)
{
return;
}
// check if there is already a child with that name
for (int i = 0; i < parent.getChildCount(); i++)
{
DefaultMutableTreeNode childAt = (DefaultMutableTreeNode) parent.getChildAt(i);
if (childAt.getUserObject().equals(split[start]))
{
addDirectoriesImpl(childAt, split, start + 1);
return;
}
}
DefaultMutableTreeNode defaultMutableTreeNode = new DefaultMutableTreeNode(split[start]);
parent.add(defaultMutableTreeNode);
addDirectoriesImpl(defaultMutableTreeNode, split, start + 1);
return;
}
// sorts the given node
public void sort(DefaultMutableTreeNode parent)
{
sortLevel(parent);
}
// slow but effective way to sort, this method should only be called in a thread !
private void sortLevel(DefaultMutableTreeNode node)
{
ArrayList<DefaultMutableTreeNode> tempList = new ArrayList<DefaultMutableTreeNode>();
HashMap<String, DefaultMutableTreeNode> map = new HashMap<String, DefaultMutableTreeNode>();
int childC = node.getChildCount();
for (int i = 0; i < childC; i++)
{
DefaultMutableTreeNode childAt = (DefaultMutableTreeNode) node.getChildAt(0);
String key = childAt.getUserObject().toString();
tempList.add(childAt);
map.put(key, childAt);
// sometimes there is a exception here, if yes try again x)
while (true)
{
try
{
// if the child should not have a parent break
if (childAt.getParent() == null)
{
break;
}
model.removeNodeFromParent(childAt);
break;
} catch (Exception ex)
{/* Ignore Exception */
}
}
}
Collections.sort(tempList, new MpqNodeComparator());
int index = 0;
for (DefaultMutableTreeNode s : tempList)
{
DefaultMutableTreeNode get = map.get(s.getUserObject().toString());
while (true)
{
try
{
if (get.getParent() == node)
{
break;
}
model.insertNodeInto(get, node, index);
} catch (Exception ex)
{
}
}
sortLevel(get);
index++;
}
tempList = null;
map = null;
}
class MpqTreeBuilder extends Thread
{
private DefaultMutableTreeNode parent;
private ExtMpqArchive archive;
public MpqTreeBuilder(DefaultMutableTreeNode parent, ExtMpqArchive archive)
{
super("MpqTreeBuilderThread - " + archive.getArchiveFile().getName());
this.parent = parent;
this.archive = archive;
}
@Override
public void run()
{
buildTree(parent, archive);
while (finishedBuildTree == false)
{
}
sort(parent);
// expand the first node in tree (root isnt shown)
if (parent.getChildCount() > 0)
{
//TreePath treePath = new TreePath(parent.getChildAt(0));
expandRow(0);
}
}
}
}