/* * Copyright (C) 2007, 2008, 2010, 2011 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2007, 2008, 2009 Quadduc <quadduc@gmail.com> * * This file is part of LateralGM. * LateralGM is free software and comes with ABSOLUTELY NO WARRANTY. * See LICENSE for details. */ package org.lateralgm.components.visual; import static org.lateralgm.main.Util.deRef; import static org.lateralgm.main.Util.gcd; import static org.lateralgm.main.Util.negDiv; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.EnumMap; import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import javax.swing.JCheckBoxMenuItem; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import org.lateralgm.main.LGM; import org.lateralgm.messages.Messages; import org.lateralgm.resources.Background; import org.lateralgm.resources.GmObject; import org.lateralgm.resources.ResourceReference; import org.lateralgm.resources.Room; import org.lateralgm.resources.Background.PBackground; import org.lateralgm.resources.Room.PRoom; import org.lateralgm.resources.Room.Piece; import org.lateralgm.resources.sub.Instance; import org.lateralgm.resources.sub.Tile; import org.lateralgm.resources.sub.Instance.PInstance; import org.lateralgm.resources.sub.Tile.PTile; import org.lateralgm.subframes.RoomFrame; import org.lateralgm.subframes.CodeFrame; import org.lateralgm.ui.swing.visuals.RoomVisual; import org.lateralgm.util.ActiveArrayList; import org.lateralgm.util.PropertyMap; import org.lateralgm.util.PropertyMap.PropertyUpdateEvent; import org.lateralgm.util.PropertyMap.PropertyUpdateListener; import org.lateralgm.util.PropertyMap.PropertyValidator; public class RoomEditor extends VisualPanel { private static final long serialVersionUID = 1L; public static final int ZOOM_MIN = -1; public static final int ZOOM_MAX = 2; private final Room room; protected final RoomFrame frame; private Piece cursor; public final PropertyMap<PRoomEditor> properties; private final RoomVisual roomVisual; private final RoomPropertyListener rpl = new RoomPropertyListener(); private final RoomEditorPropertyValidator repv = new RoomEditorPropertyValidator(); public enum PRoomEditor { SHOW_GRID,SHOW_OBJECTS(RoomVisual.Show.INSTANCES),SHOW_TILES,SHOW_BACKGROUNDS,SHOW_FOREGROUNDS, SHOW_VIEWS,DELETE_UNDERLYING_OBJECTS,DELETE_UNDERLYING_TILES,GRID_OFFSET_X,GRID_OFFSET_Y,ZOOM; final RoomVisual.Show rvBinding; private PRoomEditor() { String n = name(); if (n.startsWith("SHOW_")) rvBinding = RoomVisual.Show.valueOf(n.substring(5)); else rvBinding = null; } private PRoomEditor(RoomVisual.Show b) { rvBinding = b; } } private static final EnumMap<PRoomEditor,Object> DEFS = PropertyMap.makeDefaultMap( PRoomEditor.class,true,true,true,true,true,false,true,true,0,0,1); public RoomEditor(Room r, RoomFrame frame) { if (r.get(PRoom.REMEMBER_WINDOW_SIZE)) { EnumMap<PRoomEditor,Object> m = new EnumMap<PRoomEditor,Object>(PRoomEditor.class); for (PRoomEditor pre : PRoomEditor.values()) try { m.put(pre,r.get(PRoom.valueOf(pre.toString()))); } catch (IllegalArgumentException e) { m.put(pre,DEFS.get(pre)); } properties = new PropertyMap<PRoomEditor>(PRoomEditor.class,repv,m); } else properties = new PropertyMap<PRoomEditor>(PRoomEditor.class,repv,DEFS); room = r; this.frame = frame; zoomOrigin = ORIGIN_MOUSE; r.properties.updateSource.addListener(rpl); enableEvents(MouseEvent.MOUSE_EVENT_MASK | MouseEvent.MOUSE_MOTION_EVENT_MASK); EnumSet<RoomVisual.Show> s = EnumSet.noneOf(RoomVisual.Show.class); for (PRoomEditor p : PRoomEditor.values()) if (p.rvBinding != null && (Boolean) properties.get(p)) s.add(p.rvBinding); lockBounds(); roomVisual = new RoomVisual(container,r,s); unlockBounds(); put(0,roomVisual); setZoom((Integer) properties.get(PRoomEditor.ZOOM)); refresh(); } public void refresh() { revalidate(); repaint(); } protected void processMouseEvent(MouseEvent e) { super.processMouseEvent(e); mouseEdit(e); } protected void processMouseMotionEvent(MouseEvent e) { super.processMouseMotionEvent(e); mouseEdit(e); } public void releaseCursor(Point p) { //it must be guaranteed that cursor != null boolean duo = properties.get(PRoomEditor.DELETE_UNDERLYING_OBJECTS); boolean dut = properties.get(PRoomEditor.DELETE_UNDERLYING_TILES); if (duo && cursor instanceof Instance) deleteUnderlying(roomVisual.intersectInstances(new Rectangle(p.x,p.y,1,1)),room.instances); else if (dut && cursor instanceof Tile) deleteUnderlying(roomVisual.intersectTiles(new Rectangle(p.x,p.y,1,1)),room.tiles); unlockBounds(); cursor = null; } private <T>void deleteUnderlying(Iterator<T> i, ActiveArrayList<T> l) { HashSet<T> s = new HashSet<T>(); while (i.hasNext()) { T t = i.next(); if (t != cursor) s.add(t); } l.removeAll(s); } /** Do not call with null */ public void setCursor(Piece ds) { cursor = ds; if (ds instanceof Instance) { frame.oList.setSelectedValue(ds,true); frame.fireObjUpdate(); } else if (ds instanceof Tile) { frame.tList.setSelectedValue(ds,true); frame.fireTileUpdate(); } lockBounds(); } private void processLeftButton(int modifiers, boolean pressed, Piece mc, Point p) { boolean shift = ((modifiers & MouseEvent.SHIFT_DOWN_MASK) != 0); if ((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) { if (pressed && mc != null && !mc.isLocked()) setCursor(mc); } else { if (shift && cursor != null) if (!roomVisual.intersects(new Rectangle(p.x,p.y,1,1),cursor)) { releaseCursor(p); pressed = true; //ensures that a new instance is created below } if (pressed && cursor == null) { if (frame.tabs.getSelectedIndex() == Room.TAB_TILES) { ResourceReference<Background> bkg = frame.taSource.getSelected(); if (bkg == null) return; //I'd rather just break out of this IF, but this works Background b = bkg.get(); Tile t = new Tile(room,LGM.currentFile); t.properties.put(PTile.BACKGROUND,bkg); t.setBackgroundPosition(new Point(frame.tSelect.tx,frame.tSelect.ty)); t.setRoomPosition(p); if (!(Boolean) b.get(PBackground.USE_AS_TILESET)) t.setSize(new Dimension(b.getWidth(),b.getHeight())); else t.setSize(new Dimension((Integer) b.get(PBackground.TILE_WIDTH), (Integer) b.get(PBackground.TILE_HEIGHT))); t.setDepth((Integer) frame.taDepth.getValue()); room.tiles.add(t); setCursor(t); shift = true; //prevents unnecessary coordinate update below } else if (frame.tabs.getSelectedIndex() == Room.TAB_OBJECTS) { ResourceReference<GmObject> obj = frame.oNew.getSelected(); if (obj == null) return; //I'd rather just break out of this IF, but this works Instance i = room.addInstance(); i.properties.put(PInstance.OBJECT,obj); i.setPosition(p); setCursor(i); shift = true; //prevents unnecessary coordinate update below } } } if (cursor != null && !shift) { if (cursor instanceof Instance) { Instance i = (Instance) cursor; i.setPosition(p); } else if (cursor instanceof Tile) { Tile t = (Tile) cursor; t.setRoomPosition(p); } } } private void processRightButton(int modifiers, boolean pressed, final Piece mc, Point p) { if ((modifiers & MouseEvent.CTRL_DOWN_MASK) != 0) { if (!pressed) return; JPopupMenu jp = new JPopupMenu(); JCheckBoxMenuItem cb = new JCheckBoxMenuItem( Messages.getString("RoomEditor.LOCKED"),mc.isLocked()); //$NON-NLS-1$ cb.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { mc.setLocked(((JCheckBoxMenuItem) e.getSource()).isSelected()); } }); jp.add(cb); if (mc instanceof Instance) { final Instance i = (Instance) mc; JMenuItem mi = new JMenuItem(Messages.getString("RoomEditor.CREATION_CODE")); //$NON-NLS-1$ mi.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { frame.openCodeFrame(i); } }); jp.add(mi); } Point cp = p.getLocation(); visualToComponent(cp); jp.show(this,cp.x,cp.y); } else if (!mc.isLocked()) { ArrayList<?> alist = null; int i = -1; JList jlist = null; if (mc instanceof Instance) { i = room.instances.indexOf(mc); if (i == -1) return; alist = room.instances; jlist = frame.oList; CodeFrame fr = frame.codeFrames.get(mc); if (fr != null) fr.dispose(); } else if (mc instanceof Tile) { i = room.tiles.indexOf(mc); if (i == -1) return; alist = room.tiles; jlist = frame.tList; } if (i == -1) return; int i2 = jlist.getSelectedIndex(); alist.remove(i); jlist.setSelectedIndex(Math.min(alist.size() - 1,i2)); } } protected void mouseEdit(MouseEvent e) { int modifiers = e.getModifiersEx(); int type = e.getID(); Point p = e.getPoint().getLocation(); componentToVisual(p); int x = p.x; int y = p.y; if ((modifiers & MouseEvent.ALT_DOWN_MASK) == 0) { int sx = room.get(PRoom.SNAP_X); int sy = room.get(PRoom.SNAP_Y); int ox = properties.get(PRoomEditor.GRID_OFFSET_X); int oy = properties.get(PRoomEditor.GRID_OFFSET_Y); if (room.get(PRoom.ISOMETRIC)) { int gx = ox + negDiv(x - ox,sx) * sx; int gy = oy + negDiv(y - oy,sy) * sy; boolean d = (Math.abs(x - gx - sx / 2) * sy + Math.abs(y - gy - sy / 2) * sx) < sx * sy / 2; x = gx + (d ? sx / 2 : x > gx + sx / 2 ? sx : 0); y = gy + (d ? sy / 2 : y > gy + sy / 2 ? sy : 0); } else { x = ox + negDiv(x - ox,sx) * sx; y = oy + negDiv(y - oy,sy) * sy; } } frame.statX.setText(Messages.getString("RoomFrame.STAT_X") + x); //$NON-NLS-1$ frame.statY.setText(Messages.getString("RoomFrame.STAT_Y") + y); //$NON-NLS-1$ frame.statId.setText(""); //$NON-NLS-1$ frame.statSrc.setText(""); //$NON-NLS-1$ Piece mc = null; if (frame.tabs.getSelectedIndex() == Room.TAB_TILES) { Tile tile = getTopPiece(p,Tile.class); mc = tile; if (mc != null) { String idt = Messages.getString("RoomFrame.STAT_ID") //$NON-NLS-1$ + tile.properties.get(PTile.ID); if (mc.isLocked()) idt += " X"; //$NON-NLS-1$ frame.statId.setText(idt); ResourceReference<Background> rb = tile.properties.get(PTile.BACKGROUND); Background b = deRef(rb); String name = b == null ? Messages.getString("RoomFrame.NO_BACKGROUND") : b.getName(); idt = Messages.getString("RoomFrame.STAT_TILESET") + name; //$NON-NLS-1$ frame.statSrc.setText(idt); } } else { Instance instance = getTopPiece(p,Instance.class); mc = instance; if (instance != null) { String idt = Messages.getString("RoomFrame.STAT_ID") //$NON-NLS-1$ + instance.properties.get(PInstance.ID); if (mc.isLocked()) idt += " X"; //$NON-NLS-1$ frame.statId.setText(idt); ResourceReference<GmObject> or = instance.properties.get(PInstance.OBJECT); GmObject o = deRef(or); String name = o == null ? Messages.getString("RoomFrame.NO_OBJECT") : o.getName(); idt = Messages.getString("RoomFrame.STAT_OBJECT") + name; //$NON-NLS-1$ frame.statSrc.setText(idt); } if (frame.tabs.getSelectedIndex() != Room.TAB_OBJECTS) return; } if ((modifiers & MouseEvent.BUTTON1_DOWN_MASK) != 0) processLeftButton(modifiers,type == MouseEvent.MOUSE_PRESSED,mc,new Point(x,y)); else if (cursor != null) releaseCursor(new Point(x,y)); if ((modifiers & MouseEvent.BUTTON3_DOWN_MASK) != 0 && mc != null) processRightButton(modifiers,type == MouseEvent.MOUSE_PRESSED,mc,p); //use mouse point } private <P extends Piece>P getTopPiece(Point p, Class<P> c) { Iterator<P> pi = roomVisual.intersect(new Rectangle(p.x,p.y,1,1),c); P piece = null; while (pi.hasNext()) piece = pi.next(); return piece; } public static interface CommandHandler { void openCodeFrame(Instance i); } private class RoomPropertyListener extends PropertyUpdateListener<PRoom> { public void updated(PropertyUpdateEvent<PRoom> e) { switch (e.key) { case SNAP_X: case SNAP_Y: setZoom((Integer) properties.get(PRoomEditor.ZOOM)); break; case DELETE_UNDERLYING_OBJECTS: case DELETE_UNDERLYING_TILES: case SHOW_BACKGROUNDS: case SHOW_FOREGROUNDS: case SHOW_GRID: case SHOW_OBJECTS: case SHOW_TILES: case SHOW_VIEWS: if (!validating) properties.put(PRoomEditor.valueOf(e.key.name()),room.get(e.key)); break; case REMEMBER_WINDOW_SIZE: if (room.get(PRoom.REMEMBER_WINDOW_SIZE)) for (PRoomEditor pre : PRoomEditor.values()) try { room.put(PRoom.valueOf(pre.name()),properties.get(pre)); } catch (IllegalArgumentException iae) { } } } } @Override public void setZoom(int z) { super.setZoom(z); if (z >= 1) roomVisual.setGridFactor(1); else { int sx = room.get(PRoom.SNAP_X); int sy = room.get(PRoom.SNAP_Y); roomVisual.setGridFactor((2 - z) / gcd(2 - z,gcd(sx < 2 ? 0 : sx,sy < 2 ? 0 : sy))); } } private boolean validating; private class RoomEditorPropertyValidator implements PropertyValidator<PRoomEditor> { public Object validate(PRoomEditor k, Object v) { switch (k) { case GRID_OFFSET_X: roomVisual.setGridXOffset((Integer) v); break; case GRID_OFFSET_Y: roomVisual.setGridYOffset((Integer) v); break; case ZOOM: int i = Math.max(ZOOM_MIN,Math.min(ZOOM_MAX,(Integer) v)); setZoom(i); return i; case SHOW_BACKGROUNDS: case SHOW_FOREGROUNDS: case SHOW_GRID: case SHOW_OBJECTS: case SHOW_TILES: case SHOW_VIEWS: roomVisual.setVisible(k.rvBinding,(Boolean) v); break; case DELETE_UNDERLYING_OBJECTS: case DELETE_UNDERLYING_TILES: if (room.get(PRoom.REMEMBER_WINDOW_SIZE)) { PRoom prk = PRoom.valueOf(k.name()); validating = true; try { room.put(prk,v); } finally { validating = false; } return room.get(prk); } } return v; } } }