package com.kartoflane.superluminal2.ui;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DragSource;
import org.eclipse.swt.dnd.DragSourceEvent;
import org.eclipse.swt.dnd.DragSourceListener;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import com.kartoflane.superluminal2.Superluminal;
import com.kartoflane.superluminal2.components.Hotkey;
import com.kartoflane.superluminal2.components.interfaces.Action;
import com.kartoflane.superluminal2.core.Cache;
import com.kartoflane.superluminal2.core.Database;
import com.kartoflane.superluminal2.core.DatabaseEntry;
import com.kartoflane.superluminal2.core.Manager;
import com.kartoflane.superluminal2.utils.UIUtils;
import com.kartoflane.superluminal2.utils.Utils;
public class ModManagementDialog {
private static final Logger log = LogManager.getLogger(ModManagementDialog.class);
private static ModManagementDialog instance = null;
private static String prevPath = null;
private ArrayList<DatabaseEntry> entries = null;
private TreeItem dragItem = null;
private DatabaseEntry dragData = null;
private Shell shell;
private Tree tree;
private Button btnRemove;
private Button btnLoad;
private TreeItem trtmCore;
private Color disabledColor = null;
private Button btnConfirm;
private Button btnCancel;
private DragSource dragSource;
private DropTarget dropTarget;
public ModManagementDialog(Shell parent) {
if (instance != null)
throw new IllegalStateException("Previous instance has not been disposed!");
instance = this;
final Database db = Database.getInstance();
entries = new ArrayList<DatabaseEntry>();
shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
shell.setText(Superluminal.APP_NAME + " - Mod Management");
shell.setLayout(new GridLayout(4, false));
tree = new Tree(shell, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI);
tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
trtmCore = new TreeItem(tree, SWT.NONE);
trtmCore.setText("DatabaseCore");
trtmCore.setData(db.getCore());
RGB rgb = trtmCore.getBackground().getRGB();
rgb.red = (int) (0.85 * rgb.red);
rgb.green = (int) (0.85 * rgb.green);
rgb.blue = (int) (0.85 * rgb.blue);
disabledColor = Cache.checkOutColor(this, rgb);
trtmCore.setBackground(disabledColor);
// Need to specify a transfer type, even if it's not used, because
// otherwise it's not even possible to initiate drag and drop...
Transfer[] sourceTypes = new Transfer[] { TextTransfer.getInstance() };
dragSource = new DragSource(tree, DND.DROP_MOVE);
dragSource.setTransfer(sourceTypes);
Transfer[] dropTypes = new Transfer[] { TextTransfer.getInstance(), FileTransfer.getInstance() };
dropTarget = new DropTarget(tree, DND.DROP_MOVE | DND.DROP_DEFAULT);
dropTarget.setTransfer(dropTypes);
btnLoad = new Button(shell, SWT.NONE);
GridData gd_btnLoad = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
gd_btnLoad.widthHint = 80;
btnLoad.setLayoutData(gd_btnLoad);
btnLoad.setText("Load Mod");
btnRemove = new Button(shell, SWT.NONE);
GridData gd_btnRemove = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
gd_btnRemove.widthHint = 80;
btnRemove.setLayoutData(gd_btnRemove);
btnRemove.setText("Remove");
btnRemove.setEnabled(false);
btnConfirm = new Button(shell, SWT.NONE);
GridData gd_btnConfirm = new GridData(SWT.RIGHT, SWT.CENTER, true, false, 1, 1);
gd_btnConfirm.widthHint = 80;
btnConfirm.setLayoutData(gd_btnConfirm);
btnConfirm.setText("Confirm");
btnCancel = new Button(shell, SWT.NONE);
GridData gd_btnCancel = new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1);
gd_btnCancel.widthHint = 80;
btnCancel.setLayoutData(gd_btnCancel);
btnCancel.setText("Cancel");
for (DatabaseEntry de : db.getEntries()) {
if (de == db.getCore())
continue;
createTreeItem(de);
entries.add(de);
}
dragSource.addDragListener(new DragSourceListener() {
@Override
public void dragStart(DragSourceEvent e) {
TreeItem[] selection = tree.getSelection();
if (selection.length > 0 && selection[0].getItemCount() == 0 && selection[0] != trtmCore) {
e.doit = true;
dragItem = selection[0];
} else {
e.doit = false;
}
}
@Override
public void dragSetData(DragSourceEvent e) {
e.data = "whatever"; // This needs not be an empty string, otherwise the drag mechanism freaks out...
dragData = (DatabaseEntry) dragItem.getData();
}
@Override
public void dragFinished(DragSourceEvent e) {
if (dragItem != null && dragItem.getData() == null)
dragItem.dispose();
dragItem = null;
dragData = null;
}
});
dropTarget.addDropListener(new DropTargetAdapter() {
@Override
public void dragOver(DropTargetEvent e) {
e.detail = DND.DROP_MOVE;
e.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL;
if (dragItem != null) {
Point p = tree.toControl(e.x, e.y);
TreeItem item = tree.getItem(p);
if (item == null)
item = tree.getItem(tree.getItemCount() - 1);
Rectangle bounds = item.getBounds();
if (p.y < bounds.y + bounds.height / 2) {
if (item == trtmCore) {
e.detail = DND.DROP_NONE;
e.feedback = DND.FEEDBACK_NONE;
} else {
e.feedback |= DND.FEEDBACK_INSERT_BEFORE;
}
} else {
e.feedback |= DND.FEEDBACK_INSERT_AFTER;
}
} else {
Object object = FileTransfer.getInstance().nativeToJava(e.currentDataType);
if (object instanceof String[]) {
String fileList[] = (String[]) object;
for (String path : fileList) {
if (!path.endsWith(".ftl") && !path.endsWith(".zip")) {
e.detail = DND.DROP_NONE;
e.feedback = DND.FEEDBACK_NONE;
break;
}
}
} else {
e.detail = DND.DROP_NONE;
e.feedback = DND.FEEDBACK_NONE;
}
}
}
@Override
public void drop(DropTargetEvent e) {
if (dragData == null) {
Object object = FileTransfer.getInstance().nativeToJava(e.currentDataType);
if (object instanceof String[]) {
String fileList[] = (String[]) object;
// Already ensured that all items are .ftl or .zip
for (String path : fileList) {
addEntry(new File(path));
}
}
} else if (dragData != db.getCore()) {
if (dragItem == null) {
createTreeItem(dragData);
} else {
Point p = tree.toControl(e.x, e.y);
TreeItem item = tree.getItem(p);
if (item == null)
item = tree.getItem(tree.getItemCount() - 1);
Rectangle bounds = item.getBounds();
TreeItem[] items = tree.getItems();
int index = indexOf(items, item);
if (p.y < bounds.y + bounds.height / 2) {
if (item != trtmCore) {
dragItem.setData(null);
createTreeItem(dragData, index);
entries.remove(dragData);
entries.add(index - 1, dragData);
}
} else {
if (index == items.length)
index--;
dragItem.setData(null);
createTreeItem(dragData, index + 1);
entries.remove(dragData);
entries.add(Utils.limit(0, index - 1, items.length), dragData);
}
}
}
}
});
tree.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
if (Utils.contains(tree.getSelection(), trtmCore))
tree.deselect(trtmCore);
btnRemove.setEnabled(tree.getSelectionCount() > 0);
}
});
btnLoad.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
FileDialog dialog = new FileDialog(shell, SWT.OPEN | SWT.MULTI);
dialog.setFilterExtensions(new String[] { "*.zip;*.ftl" });
dialog.setFilterPath(prevPath);
dialog.setFileName(prevPath);
dialog.setText("Load Mod");
String path = dialog.open();
File[] results = null;
if (path == null) {
// User aborted selection
// Nothing to do here
} else {
String[] paths = dialog.getFileNames();
results = new File[paths.length];
for (int i = 0; i < paths.length; i++) {
results[i] = new File(dialog.getFilterPath() + "/" + paths[i]);
}
}
if (results != null) {
for (File f : results) {
prevPath = f.getParent();
try {
DatabaseEntry de = new DatabaseEntry(f);
if (!entries.contains(de)) {
createTreeItem(de);
entries.add(de);
}
} catch (IOException ex) {
log.warn(String.format("An error has occured while loading mod file '%s': ", f.getName()), ex);
}
}
}
}
});
btnRemove.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
for (TreeItem trtm : tree.getSelection()) {
DatabaseEntry de = (DatabaseEntry) trtm.getData();
if (de == db.getCore()) // Redundant check to make sure that the core database is never unloaded
continue;
trtm.dispose();
entries.remove(de);
de = null;
}
btnRemove.setEnabled(tree.getSelectionCount() > 0);
}
});
btnConfirm.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
UIUtils.showLoadDialog(shell, null, "Loading mods, please wait...", new Action() {
public void execute() {
// Load added entries
DatabaseEntry[] dbEntries = db.getEntries();
for (DatabaseEntry de : entries) {
if (de == db.getCore())
continue;
if (!Utils.contains(dbEntries, de))
db.addEntry(de);
}
// Unload deleted entries
for (DatabaseEntry de : db.getEntries()) {
if (de == db.getCore())
continue;
if (!entries.contains(de))
db.removeEntry(de);
}
// Reorder entries to match user input
for (DatabaseEntry de : entries) {
if (de == db.getCore())
continue;
db.reorderEntry(de, entries.indexOf(de) + 1);
}
db.cacheAnimations();
}
});
dispose();
}
});
btnCancel.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
dispose();
}
});
shell.addListener(SWT.Close, new Listener() {
@Override
public void handleEvent(Event e) {
btnCancel.notifyListeners(SWT.Selection, null);
e.doit = false;
}
});
shell.setSize(450, 220);
Point size = shell.getSize();
Point parSize = parent.getSize();
Point parLoc = parent.getLocation();
shell.setLocation(parLoc.x + parSize.x / 3 - size.x / 2, parLoc.y + parSize.y / 3 - size.y / 2);
// Register hotkeys
Hotkey h = new Hotkey();
h.setKey(SWT.CR);
h.addNotifyAction(btnConfirm, true);
Manager.hookHotkey(shell, h);
h = new Hotkey();
h.setKey('l');
h.setCtrl(true);
h.addNotifyAction(btnLoad, true);
Manager.hookHotkey(shell, h);
h = new Hotkey();
h.setKey(SWT.DEL);
h.addNotifyAction(btnRemove, true);
Manager.hookHotkey(shell, h);
}
public static ModManagementDialog getInstance() {
return instance;
}
public void open() {
shell.open();
}
public void addEntry(File file) {
if (!file.getAbsolutePath().endsWith(".ftl") && !file.getAbsolutePath().endsWith(".zip"))
throw new IllegalArgumentException("Argument must be a .zip or .ftl file.");
try {
DatabaseEntry de = new DatabaseEntry(file);
if (!entries.contains(de)) {
createTreeItem(de);
entries.add(de);
}
} catch (IOException ex) {
log.warn(String.format("An error has occured while loading mod file '%s': ", file.getName(), ex));
}
}
public void dispose() {
Manager.unhookHotkeys(shell);
if (disabledColor != null)
Cache.checkInColor(this, disabledColor.getRGB());
disabledColor = null;
shell.dispose();
instance = null;
}
public boolean isActive() {
return !shell.isDisposed() && shell.isVisible();
}
private TreeItem createTreeItem(DatabaseEntry de) {
return createTreeItem(de, tree.getItemCount());
}
private TreeItem createTreeItem(DatabaseEntry de, int index) {
TreeItem trtm = new TreeItem(tree, SWT.NONE, index);
trtm.setText(de.getName());
trtm.setData(de);
return trtm;
}
private int indexOf(TreeItem[] items, TreeItem item) {
if (items == null)
throw new IllegalArgumentException("Array must not be null.");
if (item == null)
throw new IllegalArgumentException("Item must not be null.");
int result = -1;
for (int i = 0; i < items.length && result == -1; i++) {
if (items[i] == item)
result = i;
}
return result;
}
}