/* * $Id$ * * Copyright (c) 2000-2003 by Brent Easton, Rodney Kinney * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.build.module.map.boardPicker.board; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Stroke; import java.awt.datatransfer.StringSelection; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragSource; import java.awt.dnd.DragSourceDragEvent; import java.awt.dnd.DragSourceDropEvent; import java.awt.dnd.DragSourceEvent; import java.awt.dnd.DragSourceListener; import java.awt.dnd.DragSourceMotionListener; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.dnd.InvalidDnDOperationException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.Action; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import VASSAL.build.AbstractConfigurable; import VASSAL.build.Buildable; import VASSAL.build.module.documentation.HelpFile; import VASSAL.build.module.documentation.HelpWindow; import VASSAL.build.module.map.boardPicker.Board; import VASSAL.build.module.map.boardPicker.board.mapgrid.GridContainer; import VASSAL.build.module.map.boardPicker.board.mapgrid.GridNumbering; import VASSAL.configure.ConfigureTree; import VASSAL.configure.Configurer; import VASSAL.configure.EditPropertiesAction; import VASSAL.configure.PropertiesWindow; import VASSAL.configure.VisibilityCondition; import VASSAL.i18n.Resources; import VASSAL.tools.AdjustableSpeedScrollPane; import VASSAL.tools.ErrorDialog; import VASSAL.tools.image.ImageUtils; public class RegionGrid extends AbstractConfigurable implements MapGrid, ConfigureTree.Mutable { private static final long serialVersionUID = 1L; // AreaList is the table of Map areas // pointList is a cross-reference of points to Area names protected Map<Point,Region> regionList = new HashMap<Point,Region>(); protected GridContainer container; protected boolean visible = false; protected static boolean inConfig = false; protected int fontSize = 9; // Size square to display when configuring protected boolean snapTo = true; protected Config regionConfigurer; protected GridNumbering gridNumbering; RegionGrid me = this; public void addRegion(Region a) { regionList.put(a.getOrigin(), a); if (inConfig && regionConfigurer != null) { regionConfigurer.view.repaint(); } } public void removeRegion(Region a) { regionList.remove(a.getOrigin()); } public void removeAllRegions() { regionList.clear(); buildComponents.clear(); } public GridNumbering getGridNumbering() { return gridNumbering; } public void setGridNumbering(GridNumbering gridNumbering) { this.gridNumbering = gridNumbering; } public int getFontSize() { return fontSize; } public static final String SNAPTO = "snapto"; //$NON-NLS-1$ public static final String VISIBLE = "visible"; //$NON-NLS-1$ public static final String FONT_SIZE = "fontsize"; //$NON-NLS-1$ public String[] getAttributeNames() { return new String[]{ SNAPTO, VISIBLE, FONT_SIZE }; } public String[] getAttributeDescriptions() { return new String[]{ Resources.getString("Editor.Grid.snap"), //$NON-NLS-1$ Resources.getString("Editor.IrregularGrid.draw"), //$NON-NLS-1$ Resources.getString("Editor.IrregularGrid.fonts"), //$NON-NLS-1$ }; } public Class<?>[] getAttributeTypes() { return new Class<?>[]{ Boolean.class, Boolean.class, Integer.class }; } public Configurer getConfigurer() { final boolean buttonExists = config != null; final Configurer c = super.getConfigurer(); if (!buttonExists) { final JButton b = new JButton(Resources.getString("Editor.IrregularGrid.define_regions")); //$NON-NLS-1$ b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { configureRegions(); } }); ((Container) c.getControls()).add(b); } return c; } public void addTo(Buildable b) { container = (GridContainer) b; container.setGrid(this); } public void removeFrom(Buildable b) { container.removeGrid(this); container = null; } public static String getConfigureTypeName() { return Resources.getString("Editor.IrregularGrid.component_type"); //$NON-NLS-1$ } public String getConfigureName() { return null; } public VASSAL.build.module.documentation.HelpFile getHelpFile() { return HelpFile.getReferenceManualPage("IrregularGrid.htm"); //$NON-NLS-1$ } public String getAttributeValueString(String key) { if (VISIBLE.equals(key)) { return String.valueOf(visible); } else if (FONT_SIZE.equals(key)) { return String.valueOf(fontSize); } else if (SNAPTO.equals(key)) { return String.valueOf(snapTo); } return null; } public VisibilityCondition getAttributeVisibility(String name) { if (FONT_SIZE.equals(name)) { return new VisibilityCondition() { public boolean shouldBeVisible() { return visible; } }; } else { return super.getAttributeVisibility(name); } } public void setAttribute(String key, Object val) { if (val == null) return; if (VISIBLE.equals(key)) { if (val instanceof Boolean) { visible = ((Boolean) val).booleanValue(); } else if (val instanceof String) { visible = "true".equals(val); //$NON-NLS-1$ } } else if (FONT_SIZE.equals(key)) { if (val instanceof String) { val = Integer.valueOf((String) val); } fontSize = ((Integer) val).intValue(); } else if (SNAPTO.equals(key)) { if (val instanceof Boolean) { snapTo = ((Boolean) val).booleanValue(); } else if (val instanceof String) { snapTo = "true".equals(val); //$NON-NLS-1$ } } } public void configureRegions() { for (Region r : regionList.values()) { r.setSelected(false); } regionConfigurer = new Config(this); regionConfigurer.setVisible(true); inConfig = true; } // Force Regions to be drawn when configuring public boolean isVisible() { return (visible || inConfig); } public void setVisible(boolean b) { visible = b; } public Board getBoard() { return container.getBoard(); } public Class<?>[] getAllowableConfigureComponents() { return new Class<?>[]{Region.class}; } public Point getLocation(String name) throws BadCoords { Region reg = findRegion(name); if (reg == null) throw new BadCoords(); else return new Point(reg.getOrigin()); } public int range(Point p1, Point p2) { return (int)Math.round(p1.distance(p2)); } // // Locate nearest point // public Point snapTo(Point p) { // // Need at least one point to snap to and snapping needs to be pn. // if (!snapTo || regionList.isEmpty()) { return p; } return doSnap(p); } public boolean isLocationRestricted(Point p) { return snapTo; } // // Internal routine to find closest point for region name reporting // protected Point doSnap(Point p) { double distSq, minDistSq = Double.MAX_VALUE; Point snapPoint = p; // Iterate over each grid point and determine the closest. for (Point checkPoint : regionList.keySet()) { distSq = (p.x - checkPoint.x) * (p.x - checkPoint.x) + (p.y - checkPoint.y) * (p.y - checkPoint.y); if (distSq < minDistSq) { minDistSq = distSq; snapPoint = checkPoint; } } return new Point(snapPoint); } public String locationName(Point p) { if (regionList.isEmpty()) { return null; } final Region region = regionList.get(doSnap(p)); return region != null ? region.getName() : null; } public String localizedLocationName(Point p) { if (regionList.isEmpty()) { return null; } final Region region = regionList.get(doSnap(p)); return region != null ? region.getLocalizedName() : null; } /** * Return Region selected by Point */ public Region getRegion(Point p) { for (Region checkRegion : regionList.values()) { if (checkRegion.contains(p)) return checkRegion; } return null; } /** * Return Region by Name */ public Region findRegion(String name) { for (Region checkRegion : regionList.values()) { if (checkRegion.getConfigureName().equals(name)) { return checkRegion; } } return null; } // // Get each region to draw labels and dots // public void draw( Graphics g, Rectangle bounds, Rectangle visibleRect, double scale, boolean reversed) { if (visible) { forceDraw(g, bounds, visibleRect, scale, reversed); } } public void forceDraw( Graphics g, Rectangle bounds, Rectangle visibleRect, double scale, boolean reversed) { for (Region r : regionList.values()) { r.draw(g, bounds, visibleRect, scale, reversed); } return; } public void unSelectAll() { for (Region r : regionList.values()) { unSelect(r); } } public void unSelect(Region r) { r.setSelected(false); } public static class Config extends JFrame implements MouseListener, MouseMotionListener, ActionListener, KeyListener { private static final long serialVersionUID = 1L; protected RegionGrid grid; protected Board board; protected JPanel view; protected JScrollPane scroll; protected JPopupMenu myPopup; protected List<Region> selectedRegions = new ArrayList<Region>(); protected Region lastClickedRegion = null; protected Point lastClick; protected Rectangle selectionRect = null; protected Point anchor; protected List<Region> saveRegions; protected boolean dirty = false; public Config(RegionGrid grid) { super(Resources.getString("Editor.IrregularGrid.regions_for", grid.container.getBoard().getName())); //$NON-NLS-1$ board = grid.container.getBoard(); this.grid = grid; initComponents(); save(); } // Main Entry Point protected void initComponents() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { doCancel(); } }); view = new Config.View(board, grid, this); view.addMouseListener(this); view.addMouseMotionListener(this); view.addKeyListener(this); view.setFocusable(true); scroll = new AdjustableSpeedScrollPane( view, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); scroll.setPreferredSize(new Dimension(800, 600)); add(scroll, BorderLayout.CENTER); final Box bottomPanel = Box.createVerticalBox(); final JPanel buttonPanel = new JPanel(); final JButton okButton = new JButton(Resources.getString(Resources.OK)); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { close(); } }); buttonPanel.add(okButton); final JButton canButton = new JButton(Resources.getString(Resources.CANCEL)); canButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { doCancel(); } }); buttonPanel.add(canButton); final JLabel mess = new JLabel(Resources.getString("Editor.IrregularGrid.drag_and_drop")); //$NON-NLS-1$ mess.setAlignmentY(CENTER_ALIGNMENT); bottomPanel.add(mess); bottomPanel.add(buttonPanel); add(bottomPanel, BorderLayout.SOUTH); scroll.revalidate(); pack(); repaint(); } protected void setDirty(boolean b) { dirty = b; } protected void doCancel() { if (dirty) { if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this, Resources.getString("Editor.IrregularGrid.changes_made"), //$NON-NLS-1$ "", JOptionPane.YES_NO_OPTION)) { //$NON-NLS-1$ restore(); close(); } } else { close(); } } protected void close() { inConfig = false; Config.this.setVisible(false); } public void init() { for (Region r : selectedRegions) { r.setSelected(false); } } /* * Clone a list of the existing regions in case we have to restore * after changes */ public void save() { saveRegions = new ArrayList<Region>(grid.regionList.size()); for (Region r : grid.regionList.values()) { saveRegions.add(new Region(r)); } } /* * Restore the original list of regions. Remove all existing regions, * then add the originals back in */ public void restore() { grid.removeAllRegions(); for (Region r : saveRegions) { r.addTo(grid); grid.add(r); } } /* * Scrolls the map in the containing JScrollPane * @param dx number of pixels to scroll horizontally * @param dy number of pixels to scroll vertically */ protected void doScroll(int dx, int dy) { Rectangle r = new Rectangle(scroll.getViewport().getViewRect()); r.translate(dx, dy); r = r.intersection( new Rectangle(new Point(0, 0), view.getPreferredSize())); view.scrollRectToVisible(r); } /** * Scoll map so that the argument point is at least a certain distance from the visible edge * @param evtPt */ protected void scrollAtEdge(Point evtPt, int dist) { final Point p = new Point( evtPt.x - scroll.getViewport().getViewPosition().x, evtPt.y - scroll.getViewport().getViewPosition().y); int dx = 0, dy = 0; Dimension viewSize = scroll.getViewport().getSize(); if (p.x < dist && p.x >= 0) dx = -1; if (p.x >= viewSize.width - dist && p.x < viewSize.width) dx = 1; if (p.y < dist && p.y >= 0) dy = -1; if (p.y >= viewSize.height - dist && p.y < viewSize.height) dy = 1; if (dx != 0 || dy != 0) { doScroll(2 * dist * dx, 2 * dist * dy); } } /* ------------------------------------------------------------------ * The scrollpane client */ public static class View extends JPanel implements DropTargetListener, DragGestureListener, DragSourceListener, DragSourceMotionListener { private static final long serialVersionUID = 1L; protected Board myBoard; protected RegionGrid grid; protected Config config; protected DragSource ds = DragSource.getDefaultDragSource(); protected boolean isDragging = false; protected JLabel dragCursor; protected JLayeredPane drawWin; protected Point dragStart; protected Point lastDragLocation = new Point(); protected Point drawOffset = new Point(); protected Rectangle boundingBox; protected int currentPieceOffsetX; protected int currentPieceOffsetY; protected int originalPieceOffsetX; protected int originalPieceOffsetY; public View(Board b, RegionGrid grid, Config config) { myBoard = b; this.grid = grid; this.config = config; new DropTarget(this, DnDConstants.ACTION_MOVE, this); ds.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this); setFocusTraversalKeysEnabled(false); } public void paint(Graphics g) { Rectangle b = getVisibleRect(); g.clearRect(b.x, b.y, b.width, b.height); myBoard.draw(g, 0, 0, 1.0, this); final Rectangle bounds = new Rectangle(new Point(),myBoard.bounds().getSize()); grid.forceDraw(g,bounds,bounds,1.0,false); final Rectangle selection = config.getSelectionRect(); if (selection != null) { final Graphics2D g2d = (Graphics2D) g; final Stroke str = g2d.getStroke(); g2d.setStroke(new BasicStroke(2.0f)); g2d.setColor(Color.RED); g2d.drawRect(selection.x, selection.y, selection.width, selection.height); g2d.setStroke(str); } } public void update(Graphics g) { // To avoid flicker, don't clear the display first * paint(g); } public Dimension getPreferredSize() { return new Dimension( myBoard.bounds().width, myBoard.bounds().height); } public void dragEnter(DropTargetDragEvent arg0) { } public void dragExit(DropTargetEvent arg0) { } public void dragOver(DropTargetDragEvent arg0) { } public void drop(DropTargetDropEvent event) { removeDragCursor(); final Point dragEnd = event.getLocation(); int x = dragEnd.x - dragStart.x; int y = dragEnd.y - dragStart.y; for (Region r : config.selectedRegions) { r.move(x, y, this); config.setDirty(true); } repaint(); } public void dropActionChanged(DropTargetDragEvent arg0) { } public void dragGestureRecognized(DragGestureEvent dge) { final Point mousePosition = dge.getDragOrigin(); dragStart = new Point(mousePosition); final Region r = grid.getRegion(mousePosition); if (r == null) { return; } Point piecePosition = new Point(r.getOrigin()); originalPieceOffsetX = piecePosition.x - mousePosition.x; originalPieceOffsetY = piecePosition.y - mousePosition.y; drawWin = null; makeDragCursor(); setDragCursor(); SwingUtilities.convertPointToScreen(drawOffset, drawWin); SwingUtilities.convertPointToScreen(mousePosition, drawWin); moveDragCursor(mousePosition.x, mousePosition.y); // begin dragging try { dge.startDrag(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR), new StringSelection(""), this); //$NON-NLS-1$ dge.getDragSource().addDragSourceMotionListener(this); } catch (InvalidDnDOperationException e) { ErrorDialog.bug(e); } } public void dragDropEnd(DragSourceDropEvent arg0) { removeDragCursor(); return; } public void dragEnter(DragSourceDragEvent arg0) { } public void dragExit(DragSourceEvent arg0) { } public void dragOver(DragSourceDragEvent arg0) { } public void dropActionChanged(DragSourceDragEvent arg0) { } public void dragMouseMoved(DragSourceDragEvent event) { if (!event.getLocation().equals(lastDragLocation)) { lastDragLocation = event.getLocation(); moveDragCursor(event.getX(), event.getY()); if (dragCursor != null && !dragCursor.isVisible()) { dragCursor.setVisible(true); } } } private void removeDragCursor() { if (drawWin != null) { if (dragCursor != null) { dragCursor.setVisible(false); drawWin.remove(dragCursor); } drawWin = null; } } /** Moves the drag cursor on the current draw window */ protected void moveDragCursor(int dragX, int dragY) { if (drawWin != null) { dragCursor.setLocation(dragX - drawOffset.x, dragY - drawOffset.y); } } protected void setDragCursor() { final JRootPane rootWin = SwingUtilities.getRootPane(this); if (rootWin != null) { // remove cursor from old window if (dragCursor.getParent() != null) { dragCursor.getParent().remove(dragCursor); } drawWin = rootWin.getLayeredPane(); dragCursor.setVisible(true); drawWin.add(dragCursor, JLayeredPane.DRAG_LAYER); } } private void makeDragCursor() { // FIXME: make this an ImageOp? // create the cursor if necessary if (dragCursor == null) { dragCursor = new JLabel(); dragCursor.setVisible(false); } currentPieceOffsetX = originalPieceOffsetX; currentPieceOffsetY = originalPieceOffsetY; // Record sizing info and resize our cursor boundingBox = config.getSelectedBox(); drawOffset.move(dragStart.x - boundingBox.x, dragStart.y - boundingBox.y); final BufferedImage cursorImage = ImageUtils.createCompatibleTranslucentImage( boundingBox.width, boundingBox.height ); final Graphics2D g = cursorImage.createGraphics(); g.setComposite( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); // Draw each region into the drag cursor in the correct place for (Region r : config.selectedRegions) { int x = -boundingBox.x*2; int y = -boundingBox.y*2; r.draw(g, boundingBox, getVisibleRect(), 1.0f, false, x, y); } g.dispose(); dragCursor.setSize(boundingBox.width, boundingBox.height); // store the bitmap in the cursor dragCursor.setIcon(new ImageIcon(cursorImage)); } } /* ------------------------------------------------------------------ * End View */ /* * Mouse Listeners */ // Mouse clicked, see if it is on a Region Point public void mouseClicked(MouseEvent e) { final Point p = e.getPoint(); lastClick = p; if (e.isMetaDown()) { // Right click...menu doPopupMenu(e); } else if (lastClickedRegion != null) { if (e.getClickCount() >= 2) { // Double click show properties if (lastClickedRegion.getConfigurer() != null) { final Action a = new EditPropertiesAction(lastClickedRegion, null, this); if (a != null) { a.actionPerformed( new ActionEvent( e.getSource(), ActionEvent.ACTION_PERFORMED, "Edit")); //$NON-NLS-1$ } } } } view.repaint(); // Clean up selection } protected static final String ADD_REGION = Resources.getString("Editor.IrregularGrid.add_region"); //$NON-NLS-1$ protected static final String DELETE_REGION = Resources.getString("Editor.IrregularGrid.delete_region"); //$NON-NLS-1$ protected static final String PROPERTIES = Resources.getString("Editor.ModuleEditor.properties"); //$NON-NLS-1$ protected void doPopupMenu(MouseEvent e) { myPopup = new JPopupMenu(); JMenuItem menuItem = new JMenuItem(ADD_REGION); menuItem.addActionListener(this); menuItem.setEnabled(lastClickedRegion == null); myPopup.add(menuItem); menuItem = new JMenuItem(DELETE_REGION); menuItem.addActionListener(this); menuItem.setEnabled(lastClickedRegion != null); myPopup.add(menuItem); myPopup.addSeparator(); menuItem = new JMenuItem(PROPERTIES); menuItem.addActionListener(this); menuItem.setEnabled(lastClickedRegion != null); myPopup.add(menuItem); final Point p = e.getPoint(); myPopup .addPopupMenuListener(new javax.swing.event.PopupMenuListener() { public void popupMenuCanceled( javax.swing.event.PopupMenuEvent evt) { view.repaint(); } public void popupMenuWillBecomeInvisible( javax.swing.event.PopupMenuEvent evt) { view.repaint(); } public void popupMenuWillBecomeVisible( javax.swing.event.PopupMenuEvent evt) { } }); myPopup.show(e.getComponent(), p.x, p.y); } public void actionPerformed(ActionEvent e) { final String command = e.getActionCommand(); if (command.equals("close")) { this.setVisible(false); } else if (command.equals("showhide")) { grid.setVisible(!grid.isVisible()); view.repaint(); } else if (command.equals(ADD_REGION)) { final Region r = new Region(lastClick); r.addTo(grid); grid.add(r); select(r); lastClickedRegion = r; setDirty(true); final Action a = new EditPropertiesAction(lastClickedRegion, null, this); if (a != null) { a.actionPerformed( new ActionEvent( e.getSource(), ActionEvent.ACTION_PERFORMED, "Edit")); //$NON-NLS-1$ } view.repaint(); } else if (command.equals(DELETE_REGION)) { for (Region r : selectedRegions) { r.removeFrom(grid); grid.remove(r); lastClickedRegion=null; setDirty(true); } selectedRegions.clear(); view.repaint(); } else if (command.equals(PROPERTIES)) { //$NON-NLS-1$ if (lastClickedRegion != null) { final Action a = new EditRegionAction(lastClickedRegion, null, this); if (a != null) { a.actionPerformed( new ActionEvent( e.getSource(), ActionEvent.ACTION_PERFORMED, "Edit")); //$NON-NLS-1$ } } } } /* * Version of EditProperties Action that repaints it's owning frame */ protected static class EditRegionAction extends EditPropertiesAction { Config owner; Region origRegion; Region region; private static final long serialVersionUID = 1L; public EditRegionAction(Region target, HelpWindow helpWindow, Config dialogOwner) { super(target, helpWindow, dialogOwner); owner = dialogOwner; origRegion = new Region(target); region = target; } public void actionPerformed(ActionEvent evt) { PropertiesWindow w = openWindows.get(target); if (w == null) { w = new PropertiesWindow(dialogOwner,false,target,helpWindow); w.addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { openWindows.remove(target); owner.setDirty( !region.getName().equals(origRegion.getName()) || !region.getOrigin().equals(origRegion.getOrigin())); owner.repaint(); } }); openWindows.put(target,w); w.setVisible(true); } w.toFront(); } } protected void select(Region r) { r.setSelected(true); if (!selectedRegions.contains(r)) { selectedRegions.add(r); } view.repaint(r.getSelectionRect()); } protected void unselect(Region r) { if (r != null) { r.setSelected(false); selectedRegions.remove(r); if (lastClickedRegion==r) { lastClickedRegion=null; } view.repaint(r.getSelectionRect()); } } protected void unSelectAll() { for (Region r : selectedRegions) { r.setSelected(false); view.repaint(r.getSelectionRect()); } selectedRegions.clear(); } public Rectangle getSelectionRect() { return selectionRect; } public Rectangle getSelectedBox() { Rectangle rect = null; for (Region r : selectedRegions) { final Rectangle sel = r.getSelectionRect(); if (rect == null) { rect = sel; } else { rect.add(sel); } } return rect; } public void mouseEntered(MouseEvent evPt) { } public void mouseExited(MouseEvent evPt) { } public void mousePressed(MouseEvent e) { final Point p = e.getPoint(); lastClick = p; lastClickedRegion = grid.getRegion(p); if (!e.isShiftDown() && !e.isControlDown() && (lastClickedRegion==null || !lastClickedRegion.isSelected())) { unSelectAll(); } if (lastClickedRegion == null) { anchor = p; selectionRect = new Rectangle(anchor.x, anchor.y, 0, 0); } else { if (e.isControlDown()) { unselect(lastClickedRegion); } else { select(lastClickedRegion); } } } public void mouseReleased(MouseEvent evPt) { if (selectionRect != null) { for (Region r : grid.regionList.values()) { if (selectionRect.contains(r.getOrigin())) { if (evPt.isControlDown()) { unselect(r); } else { select(r); } } } selectionRect = null; view.repaint(); } } public void mouseMoved(MouseEvent evPt) { } // Scroll map if necessary public void mouseDragged(MouseEvent e) { if (!e.isMetaDown()) { scrollAtEdge(e.getPoint(), 15); } if (selectionRect != null) { // FIXME: inefficient, could be done with only one new Rectangle final Rectangle repaintRect = new Rectangle(selectionRect.x-1, selectionRect.y-1, selectionRect.width+3, selectionRect.height+3); selectionRect.x = Math.min(e.getX(), anchor.x); selectionRect.y = Math.min(e.getY(), anchor.y); selectionRect.width = Math.abs(e.getX() - anchor.x); selectionRect.height = Math.abs(e.getY() - anchor.y); repaintRect.add( new Rectangle(selectionRect.x-1, selectionRect.y-1, selectionRect.width+3, selectionRect.height+3)); view.repaint(repaintRect); } } public void keyPressed(KeyEvent e) { /* * Pass key onto window scroller if no region selected * or control key not used. */ if (selectedRegions.isEmpty() || !e.isControlDown()) return; int dx = 0, dy = 0, delta = 1; if (e.isShiftDown()) { delta = 5; } switch (e.getKeyCode()) { case KeyEvent.VK_UP: dy = -delta; break; case KeyEvent.VK_DOWN: dy = delta; break; case KeyEvent.VK_LEFT: dx = -delta; break; case KeyEvent.VK_RIGHT: dx = delta; break; default : return; } for (Region r : selectedRegions) { r.move(dx, dy, view); } view.repaint(); e.consume(); } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } } }