package com.kartoflane.superluminal2.ui; import java.util.HashMap; 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.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.MenuAdapter; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.TraverseEvent; import org.eclipse.swt.events.TraverseListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; 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.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeColumn; import org.eclipse.swt.widgets.TreeItem; import com.kartoflane.superluminal2.Superluminal; import com.kartoflane.superluminal2.components.Hotkey; import com.kartoflane.superluminal2.components.enums.Hotkeys; import com.kartoflane.superluminal2.components.enums.OS; import com.kartoflane.superluminal2.components.interfaces.Alias; import com.kartoflane.superluminal2.components.interfaces.Indexable; import com.kartoflane.superluminal2.core.Cache; import com.kartoflane.superluminal2.core.Manager; import com.kartoflane.superluminal2.events.SLDeleteEvent; import com.kartoflane.superluminal2.events.SLDisposeEvent; import com.kartoflane.superluminal2.events.SLEvent; import com.kartoflane.superluminal2.events.SLListener; import com.kartoflane.superluminal2.events.SLRestoreEvent; import com.kartoflane.superluminal2.mvc.controllers.AbstractController; import com.kartoflane.superluminal2.mvc.controllers.CursorController; import com.kartoflane.superluminal2.mvc.controllers.DoorController; import com.kartoflane.superluminal2.mvc.controllers.GibController; import com.kartoflane.superluminal2.mvc.controllers.MountController; import com.kartoflane.superluminal2.mvc.controllers.ObjectController; import com.kartoflane.superluminal2.mvc.controllers.RoomController; import com.kartoflane.superluminal2.tools.ManipulationTool; import com.kartoflane.superluminal2.tools.Tool.Tools; import com.kartoflane.superluminal2.undo.UndoableOrderEdit; import com.kartoflane.superluminal2.utils.UIUtils; import com.kartoflane.superluminal2.utils.Utils; public class OverviewWindow implements SLListener { private static final OverviewWindow instance = new OverviewWindow(); private ShipContainer ship; private ObjectController highlightedController = null; private HashMap<AbstractController, TreeItem> controllerMap = new HashMap<AbstractController, TreeItem>(); private Color disabledColor = null; private TreeItem dragItem = null; private ObjectController dragData = null; private Shell shell; private TreeItem trtmRooms; private TreeItem trtmDoors; private TreeItem trtmMounts; private TreeItem trtmGibs; private Tree tree; private Menu overviewMenu; private ToolBar toolBar; private ToolItem tltmAlias; private ToolItem tltmRemove; private ToolItem tltmToggleVis; private TreeColumn trclmnName; private TreeColumn trclmnAlias; private DragSource dragSource; private DropTarget dropTarget; private OverviewWindow() { } /** * @wbp.parser.entryPoint */ public void init(Shell parent) { Image helpImage = Cache.checkOutImage(this, "cpath:/assets/help.png"); shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.MIN | SWT.RESIZE); shell.setText(String.format("%s - Ship Overview", Superluminal.APP_NAME)); shell.setLayout(new GridLayout(2, false)); toolBar = new ToolBar(shell, SWT.FLAT | SWT.RIGHT); toolBar.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 1, 1)); Label lblHelp = new Label(shell, SWT.NONE); lblHelp.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1)); lblHelp.setImage(helpImage); String msg = "Alias is a short name to help you distinguish between objects."; UIUtils.addTooltip(lblHelp, Utils.wrapOSNot(msg, Superluminal.WRAP_WIDTH, Superluminal.WRAP_TOLERANCE, OS.MACOSX())); tltmAlias = new ToolItem(toolBar, SWT.NONE); tltmAlias.setToolTipText("Set Alias"); tltmAlias.setImage(Cache.checkOutImage(this, "cpath:/assets/alias.png")); tltmRemove = new ToolItem(toolBar, SWT.NONE); tltmRemove.setToolTipText("Remove Alias"); tltmRemove.setImage(Cache.checkOutImage(this, "cpath:/assets/noalias.png")); tltmToggleVis = new ToolItem(toolBar, SWT.NONE); tltmToggleVis.setToolTipText("Show/Hide"); tltmToggleVis.setImage(Cache.checkOutImage(this, "cpath:/assets/cloak.png")); tree = new Tree(shell, SWT.BORDER | SWT.FULL_SELECTION); tree.setLinesVisible(true); tree.setHeaderVisible(true); tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); RGB rgb = tree.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); trclmnName = new TreeColumn(tree, SWT.NONE); trclmnName.setWidth(175); trclmnName.setText("Name"); trclmnAlias = new TreeColumn(tree, SWT.RIGHT); trclmnAlias.setWidth(100); trclmnAlias.setText("Alias"); trtmRooms = new TreeItem(tree, SWT.NONE); trtmRooms.setText("Rooms"); trtmDoors = new TreeItem(tree, SWT.NONE); trtmDoors.setText("Doors"); trtmMounts = new TreeItem(tree, SWT.NONE); trtmMounts.setText("Mounts"); trtmGibs = new TreeItem(tree, SWT.NONE); trtmGibs.setText("Gibs"); // 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); // Overview popup menu overviewMenu = new Menu(tree); tree.setMenu(overviewMenu); final MenuItem mntmSetAlias = new MenuItem(overviewMenu, SWT.NONE); mntmSetAlias.setText("Set Alias"); final MenuItem mntmRemoveAlias = new MenuItem(overviewMenu, SWT.NONE); mntmRemoveAlias.setText("Remove Alias"); dragSource.addDragListener(new DragSourceListener() { @Override public void dragStart(DragSourceEvent e) { TreeItem[] selection = tree.getSelection(); if (selection.length > 0 && selection[0].getData() instanceof Indexable) { e.doit = true; dragItem = selection[0]; dragData = (ObjectController) dragItem.getData(); } 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... } @Override public void dragFinished(DragSourceEvent e) { 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) { e.detail = DND.DROP_NONE; e.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL; } else { TreeItem parent = item.getParentItem(); if (canDrop(parent, dragData)) { e.detail = DND.DROP_MOVE; Rectangle bounds = item.getBounds(); if (p.y < bounds.y + bounds.height / 2) { e.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL | DND.FEEDBACK_INSERT_BEFORE; } else { e.feedback = DND.FEEDBACK_EXPAND | DND.FEEDBACK_SCROLL | DND.FEEDBACK_INSERT_AFTER; } } else { e.detail = DND.DROP_NONE; e.feedback |= DND.FEEDBACK_NONE; } } } } @Override public void drop(DropTargetEvent e) { if (dragData != null) { if ((e.detail & DND.DROP_MOVE) == DND.DROP_MOVE) { Point p = tree.toControl(e.x, e.y); TreeItem item = tree.getItem(p); if (item != null) { Rectangle bounds = item.getBounds(); TreeItem parent = item.getParentItem(); if (parent != null) { ShipContainer container = Manager.getCurrentShip(); TreeItem[] items = parent.getItems(); int from = indexOf(items, dragItem); int to = indexOf(items, item); if (dragItem != item) { if (p.y > bounds.y + bounds.height / 2) { to += from > to ? 1 : 0; } else { to += from > to ? 0 : -1; } } Indexable[] array = new Indexable[items.length]; for (int i = 0; i < items.length; i++) { array[i] = (Indexable) items[i].getData(); } UndoableOrderEdit edit = new UndoableOrderEdit(array.clone()); edit.setOld(from); edit.setCurrent(to); container.postEdit(edit); Utils.reorder(array, from, to); container.sort(); update(); } } } } } }); shell.addListener(SWT.Close, new Listener() { @Override public void handleEvent(Event event) { dispose(); event.doit = false; } }); ControlAdapter resizer = new ControlAdapter() { @Override public void controlResized(ControlEvent e) { final int BORDER_OFFSET = tree.getBorderWidth(); if (trclmnName.getWidth() > tree.getClientArea().width - BORDER_OFFSET) trclmnName.setWidth(tree.getClientArea().width - BORDER_OFFSET); trclmnAlias.setWidth(tree.getClientArea().width - trclmnName.getWidth() - BORDER_OFFSET); } }; tree.addControlListener(resizer); trclmnName.addControlListener(resizer); tree.addListener(SWT.MouseMove, new Listener() { @Override public void handleEvent(Event e) { if (Manager.getSelectedToolId() == Tools.POINTER) { TreeItem item = tree.getItem(new Point(e.x, e.y)); ObjectController controller = null; if (item != null && item.getData() != null) controller = (ObjectController) item.getData(); highlightController(controller); } } }); tree.addListener(SWT.MouseExit, new Listener() { @Override public void handleEvent(Event e) { highlightController(null); } }); tree.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { ObjectController controller = null; if (tree.getSelectionCount() != 0) { TreeItem item = tree.getSelection()[0]; controller = (ObjectController) item.getData(); } if (Manager.getSelectedToolId() == Tools.POINTER) { ManipulationTool tool = (ManipulationTool) Manager.getSelectedTool(); if (tool.isStateManipulate() && (controller == null || controller.isVisible())) { Manager.setSelected(controller); } else { AbstractController selected = Manager.getSelected(); if (tool.isStateDoorLinkLeft() || tool.isStateDoorLinkRight()) tool.linkDoor(selected, controller); else if (tool.isStateMountGibLink()) tool.linkGib(selected, controller); if (selected != null && selected != controller) CursorController.getInstance().setVisible(false); } } tltmAlias.setEnabled(controller != null); tltmRemove.setEnabled(controller != null && controller.getAlias() != null && !controller.getAlias().equals("")); tltmToggleVis.setEnabled(controller != null); } }); tree.addMouseListener(new MouseAdapter() { @Override public void mouseDoubleClick(MouseEvent e) { if (e.button == 1 && tree.getSelectionCount() != 0) { TreeItem selectedItem = tree.getSelection()[0]; if (selectedItem.getItemCount() != 0 && selectedItem.getBounds().contains(e.x, e.y)) selectedItem.setExpanded(!selectedItem.getExpanded()); } } }); tree.addTraverseListener(new TraverseListener() { @Override public void keyTraversed(TraverseEvent e) { if (e.detail == SWT.TRAVERSE_RETURN && tree.getSelectionCount() != 0) { TreeItem sel = tree.getSelection()[0]; if (sel.getItemCount() != 0) sel.setExpanded(!sel.getExpanded()); } } }); overviewMenu.addMenuListener(new MenuAdapter() { @Override public void menuShown(MenuEvent e) { if (tree.getSelectionCount() == 0 || tree.getSelection()[0].getData() == null) { overviewMenu.setVisible(false); } } }); SelectionAdapter setAliasListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Alias alias = (Alias) tree.getSelection()[0].getData(); AliasDialog dialog = new AliasDialog(shell); dialog.setAlias(alias); dialog.open(); } }; mntmSetAlias.addSelectionListener(setAliasListener); tltmAlias.addSelectionListener(setAliasListener); SelectionAdapter removeAliasListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { Alias alias = (Alias) tree.getSelection()[0].getData(); alias.setAlias(null); update(tree.getSelection()[0]); } }; mntmRemoveAlias.addSelectionListener(removeAliasListener); tltmRemove.addSelectionListener(removeAliasListener); tltmToggleVis.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (tree.getSelectionCount() != 0) { AbstractController ac = (AbstractController) tree.getSelection()[0].getData(); if (ac != null) { ac.setVisible(!ac.isVisible()); tree.getSelection()[0].setBackground(ac.isVisible() ? null : disabledColor); } } } }); registerHotkeys(); shell.setSize(300, 600); Point size = shell.getSize(); Point pSize = parent.getSize(); Point pLoc = parent.getLocation(); shell.setLocation(pLoc.x + pSize.x - size.x, pLoc.y + 50); } public void open() { shell.open(); update(); shell.setMinimized(false); } public void update() { if (isDisposed()) return; ship = Manager.getCurrentShip(); AbstractController prevSelection = Manager.getSelected(); if (!controllerMap.containsKey(prevSelection)) prevSelection = null; if (Manager.getSelectedToolId() != Tools.POINTER && tree.getSelectionCount() == 1 && tree.getSelection()[0] != null) prevSelection = (AbstractController) tree.getSelection()[0].getData(); for (TreeItem it : trtmRooms.getItems()) it.dispose(); for (TreeItem it : trtmDoors.getItems()) it.dispose(); for (TreeItem it : trtmMounts.getItems()) it.dispose(); for (TreeItem it : trtmGibs.getItems()) it.dispose(); controllerMap.clear(); if (ship != null) { for (RoomController r : ship.getRoomControllers()) createItem(r, -1); trtmRooms.setText(String.format("Rooms (%s)", trtmRooms.getItemCount())); for (DoorController d : ship.getDoorControllers()) createItem(d, -1); trtmDoors.setText(String.format("Doors (%s)", trtmDoors.getItemCount())); for (MountController m : ship.getMountControllers()) createItem(m, -1); trtmMounts.setText(String.format("Mounts (%s)", trtmMounts.getItemCount())); for (int i = 1; i <= ship.getGibControllers().length; i++) { GibController g = ship.getGibControllerById(i); if (g != null) createItem(g, -1); } trtmGibs.setText(String.format("Gibs (%s)", trtmGibs.getItemCount())); } TreeItem item = controllerMap.get(prevSelection); if (item == null) { tree.select(trtmRooms); } else { tree.select(item); Rectangle b = item.getBounds(); if (!tree.getClientArea().contains(b.x, b.y)) tree.setTopItem(item); } String alias = null; if (controllerMap.containsKey(prevSelection)) alias = ((Alias) prevSelection).getAlias(); tltmAlias.setEnabled(prevSelection != null); tltmRemove.setEnabled(alias != null && !alias.equals("")); tltmToggleVis.setEnabled(prevSelection != null); } public void update(ObjectController controller) { if (controller == null) throw new IllegalArgumentException("Argument must not be null."); TreeItem item = controllerMap.get(controller); if (item == null && !controller.isDeleted()) { item = createItem(controller, -1); } else if (item != null) { update(item); } } private void update(TreeItem item) { if (item.getData() instanceof ObjectController == false) return; ObjectController oc = (ObjectController) item.getData(); if (oc.isDeleted()) { item.dispose(); controllerMap.remove(oc); } else { if (oc instanceof RoomController) { RoomController controller = (RoomController) oc; item.setText(0, controller.getGameObject().toStringNoAlias()); } else if (oc instanceof DoorController) { DoorController controller = (DoorController) oc; item.setText(0, "Door " + (controller.isHorizontal() ? "H" : "V")); } else if (oc instanceof MountController) { MountController controller = (MountController) oc; item.setText(0, "Mount " + controller.getId()); } else if (oc instanceof GibController) { GibController controller = (GibController) oc; item.setText(0, "Gib " + controller.getId()); } String alias = oc.getAlias(); item.setText(1, alias == null ? "" : alias); item.setBackground(oc.isVisible() ? null : disabledColor); if (oc.isSelected()) { tree.select(item); Rectangle b = item.getBounds(); if (!tree.getClientArea().contains(b.x, b.y)) tree.setTopItem(item); } } tltmAlias.setEnabled(oc != null); tltmRemove.setEnabled(oc != null && oc.getAlias() != null && !oc.getAlias().equals("")); tltmToggleVis.setEnabled(oc != null); } /** * Updates the overview window if it exists, or does nothing if it does not. */ public static void staticUpdate() { if (instance != null && !instance.isDisposed()) { instance.update(); } } /** * Updates the overview window if it exists, or does nothing if it does not. */ public static void staticUpdate(ObjectController controller) { if (instance != null && !instance.isDisposed()) { instance.update(controller); } } public static OverviewWindow getInstance() { return instance; } public void setEnabled(boolean b) { tree.setEnabled(b); if (highlightedController != null && highlightedController.isHighlighted()) highlightedController.setHighlighted(false); } public boolean isEnabled() { return !isDisposed() && tree.isEnabled(); } public boolean isActive() { return !isDisposed() && tree.isFocusControl(); } public boolean isDisposed() { return shell == null || shell.isDisposed(); } private boolean canDrop(TreeItem newParent, ObjectController data) { return (newParent == trtmRooms && data instanceof RoomController) || (newParent == trtmMounts && data instanceof MountController) || (newParent == trtmGibs && data instanceof GibController); } 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; } private TreeItem createItem(AbstractController controller, int index) { TreeItem parent = identifyParent(controller); if (parent == null) return null; TreeItem item = null; if (index < 0 || parent.getItemCount() >= index) { item = new TreeItem(parent, SWT.NONE); } else { item = new TreeItem(parent, SWT.NONE, index); } item.setData(controller); update(item); controllerMap.put(controller, item); return item; } private TreeItem identifyParent(AbstractController controller) { if (controller instanceof RoomController) return trtmRooms; else if (controller instanceof DoorController) return trtmDoors; else if (controller instanceof MountController) return trtmMounts; else if (controller instanceof GibController) return trtmGibs; else return null; } public void dispose() { Cache.checkInColor(this, disabledColor.getRGB()); disabledColor = null; Cache.checkInImage(this, "cpath:/assets/help.png"); Cache.checkInImage(this, "cpath:/assets/alias.png"); Cache.checkInImage(this, "cpath:/assets/noalias.png"); Cache.checkInImage(this, "cpath:/assets/cloak.png"); Manager.unhookHotkeys(shell); shell.dispose(); controllerMap.clear(); } @Override public void handleEvent(SLEvent e) { if (e.data instanceof ObjectController) { ObjectController data = (ObjectController) e.data; if (e instanceof SLDeleteEvent) { update(data); } else if (e instanceof SLRestoreEvent) { update(); } else if (e instanceof SLDisposeEvent) { data.removeListener(this); } } } private void highlightController(ObjectController controller) { if (highlightedController != null && highlightedController != controller && highlightedController.isHighlighted()) highlightedController.setHighlighted(false); if (controller != null && !controller.isHighlighted()) controller.setHighlighted(true); highlightedController = controller; } private void registerHotkeys() { Hotkey h = Manager.getHotkey(Hotkeys.UNDO); Manager.hookHotkey(shell, h); h = Manager.getHotkey(Hotkeys.REDO); Manager.hookHotkey(shell, h); } }