mirror of https://github.com/FreeCol/freecol.git
1522 lines
49 KiB
Java
1522 lines
49 KiB
Java
/**
|
|
* Copyright (C) 2002-2022 The FreeCol Team
|
|
*
|
|
* This file is part of FreeCol.
|
|
*
|
|
* FreeCol 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 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* FreeCol 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 FreeCol. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package net.sf.freecol.client.gui;
|
|
|
|
import static net.sf.freecol.common.util.CollectionUtils.none;
|
|
import static net.sf.freecol.common.util.CollectionUtils.transform;
|
|
|
|
import java.awt.Component;
|
|
import java.awt.Container;
|
|
import java.awt.Dimension;
|
|
import java.awt.Graphics;
|
|
import java.awt.GraphicsDevice;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.ComponentEvent;
|
|
import java.awt.event.ComponentListener;
|
|
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.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.function.Predicate;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JDesktopPane;
|
|
import javax.swing.JInternalFrame;
|
|
import javax.swing.JLabel;
|
|
import javax.swing.JLayeredPane;
|
|
import javax.swing.JMenuBar;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.Timer;
|
|
import javax.swing.UIManager;
|
|
import javax.swing.border.EmptyBorder;
|
|
import javax.swing.plaf.basic.BasicInternalFrameUI;
|
|
|
|
import net.sf.freecol.client.ClientOptions;
|
|
import net.sf.freecol.client.FreeColClient;
|
|
import net.sf.freecol.client.gui.SwingGUI.PopupPosition;
|
|
import net.sf.freecol.client.gui.action.FreeColAction;
|
|
import net.sf.freecol.client.gui.dialog.FreeColDialog;
|
|
import net.sf.freecol.client.gui.mapviewer.CanvasMapViewer;
|
|
import net.sf.freecol.client.gui.mapviewer.MapViewer;
|
|
import net.sf.freecol.client.gui.menu.InGameMenuBar;
|
|
import net.sf.freecol.client.gui.menu.MapEditorMenuBar;
|
|
import net.sf.freecol.client.gui.menu.MenuMouseMotionListener;
|
|
// Special case panels, TODO: can we move these to Widgets?
|
|
import net.sf.freecol.client.gui.panel.FreeColPanel;
|
|
import net.sf.freecol.client.gui.panel.MainPanel;
|
|
import net.sf.freecol.client.gui.panel.MapControls;
|
|
import net.sf.freecol.client.gui.panel.MapEditorTransformPanel;
|
|
import net.sf.freecol.client.gui.panel.Utility;
|
|
import net.sf.freecol.client.gui.video.VideoComponent;
|
|
import net.sf.freecol.client.gui.video.VideoListener;
|
|
import net.sf.freecol.common.i18n.Messages;
|
|
import net.sf.freecol.common.model.Specification;
|
|
import net.sf.freecol.common.option.IntegerOption;
|
|
import net.sf.freecol.common.option.Option;
|
|
import net.sf.freecol.common.option.OptionGroup;
|
|
import net.sf.freecol.common.resources.Video;
|
|
|
|
|
|
/**
|
|
* The main container for the other GUI components in FreeCol.
|
|
* This is where lower level graphics coordination occurs.
|
|
* Specific panels and dialogs are over in Widgets
|
|
* (TODO) with a few exceptions.
|
|
*/
|
|
public final class Canvas extends JDesktopPane {
|
|
|
|
public static final String PROPERTY_RESIZABLE = "Canvas.resizable";
|
|
public static final String PROPERTY_POPUP_POSITION = "Canvas.popupPosition";
|
|
|
|
private static final Logger logger = Logger.getLogger(Canvas.class.getName());
|
|
|
|
/** Number of tries to find a clear spot on the canvas. */
|
|
private static final int MAXTRY = 3;
|
|
|
|
/** The cursor to show for goto operations. */
|
|
public static final java.awt.Cursor GO_CURSOR
|
|
= (java.awt.Cursor)UIManager.get("cursor.go");
|
|
|
|
/** A class for frames being used as tool boxes. */
|
|
private static class ToolBoxFrame extends JInternalFrame {}
|
|
|
|
/** The game client. */
|
|
private final FreeColClient freeColClient;
|
|
|
|
/** The graphics device to display to. */
|
|
private final GraphicsDevice graphicsDevice;
|
|
|
|
/** Is the canvas in windowed mode? */
|
|
private boolean windowed;
|
|
|
|
/** The parent frame, either a window or the full screen. */
|
|
private FreeColFrame parentFrame;
|
|
|
|
/** Remember the current size (from getSize()), check for changes. */
|
|
private Dimension oldSize;
|
|
|
|
/**
|
|
* The component that displays the map.
|
|
* Ideally this would be completely separate, but the displayMap
|
|
* (and possibly changeSize) call/s means we must retain a reference
|
|
* to the map viewer.
|
|
* We also use it for the Cursor, which remains in MapViewer so
|
|
* that displayMap can draw it when active needed.
|
|
*/
|
|
private final MapViewer mapViewer;
|
|
|
|
/** The various sorts of map controls. */
|
|
private MapControls mapControls;
|
|
|
|
/** The special overlay used when it is not the player turn. */
|
|
private GrayLayer greyLayer;
|
|
|
|
/** The dialogs in view. */
|
|
private final List<FreeColDialog<?>> dialogs = new ArrayList<>();
|
|
|
|
/**
|
|
* The main panel. Remember this because getExistingFreeColPanel
|
|
* gets confused across switches between the game and the map editor
|
|
* which makes it hard to remove.
|
|
*/
|
|
private MainPanel mainPanel;
|
|
|
|
private Scrolling scrolling;
|
|
|
|
/**
|
|
* The panel used for displaying the map and drawing the background of this class.
|
|
*/
|
|
private CanvasMapViewer canvasMapViewer;
|
|
|
|
/**
|
|
* A timer triggering repaints in order to display animations on the {@link CanvasMapViewer}.
|
|
*/
|
|
private Timer animationTimer;
|
|
|
|
|
|
/**
|
|
* The constructor to use.
|
|
*
|
|
* @param freeColClient The {@code FreeColClient} for the game.
|
|
* @param graphicsDevice The {@code GraphicsDevice} to display on.
|
|
* @param desiredSize The desired size of the parent frame.
|
|
* @param mapViewer The object responsible of drawing the map.
|
|
* @param mapControls The controls on the map.
|
|
*/
|
|
public Canvas(final FreeColClient freeColClient,
|
|
final GraphicsDevice graphicsDevice,
|
|
final Dimension desiredSize,
|
|
MapViewer mapViewer,
|
|
MapControls mapControls) {
|
|
assert SwingUtilities.isEventDispatchThread();
|
|
|
|
this.freeColClient = freeColClient;
|
|
this.graphicsDevice = graphicsDevice;
|
|
this.canvasMapViewer = new CanvasMapViewer(freeColClient, mapViewer);
|
|
this.scrolling = new Scrolling(freeColClient, this);
|
|
|
|
// Determine if windowed mode should be used and set the window size.
|
|
this.windowed = checkWindowed(graphicsDevice, desiredSize);
|
|
Rectangle windowBounds = null;
|
|
if (this.windowed && desiredSize != null
|
|
&& desiredSize.width > 0 && desiredSize.height > 0) {
|
|
windowBounds = new Rectangle(desiredSize);
|
|
}
|
|
|
|
this.parentFrame = createFrame(null, windowBounds);
|
|
this.oldSize = getSize();
|
|
|
|
add(canvasMapViewer, FRAME_CONTENT_LAYER);
|
|
canvasMapViewer.setSize(oldSize);
|
|
canvasMapViewer.setLocation(0, 0);
|
|
|
|
addComponentListener(new ComponentListener() {
|
|
@Override
|
|
public void componentResized(ComponentEvent e) {
|
|
updateSize();
|
|
}
|
|
|
|
@Override
|
|
public void componentMoved(ComponentEvent e) {
|
|
updateSize();
|
|
}
|
|
|
|
@Override
|
|
public void componentShown(ComponentEvent e) {
|
|
updateSize();
|
|
}
|
|
|
|
@Override
|
|
public void componentHidden(ComponentEvent e) {
|
|
updateSize();
|
|
}
|
|
});
|
|
|
|
this.mapViewer = mapViewer;
|
|
this.mapControls = mapControls;
|
|
this.greyLayer = new GrayLayer(freeColClient);
|
|
|
|
setDoubleBuffered(true);
|
|
setOpaque(true);
|
|
setLayout(null);
|
|
setFocusable(true);
|
|
setFocusTraversalKeysEnabled(false);
|
|
// Create key bindings for all actions
|
|
for (Option option : freeColClient.getActionManager().getOptions()) {
|
|
FreeColAction action = (FreeColAction)option;
|
|
getInputMap().put(action.getAccelerator(), action.getId());
|
|
getActionMap().put(action.getId(), action);
|
|
}
|
|
|
|
this.parentFrame.setVisible(true);
|
|
|
|
updateSize();
|
|
revalidate();
|
|
repaint();
|
|
|
|
/*
|
|
* TODO: Stop using hardcoded 125ms for animation. Instead, we should
|
|
* define timing for every terrain animation.
|
|
*/
|
|
animationTimer = new Timer(125, (event) -> {
|
|
paintJustTheMapImmediately();
|
|
});
|
|
this.animationTimer.start();
|
|
|
|
logger.info("Canvas created with bounds: " + windowBounds);
|
|
}
|
|
|
|
// Internals
|
|
|
|
private void updateSize() {
|
|
Dimension size = getSize();
|
|
if (oldSize.width != size.width || oldSize.height != size.height) {
|
|
logger.info("Canvas resize from " + oldSize + " to " + size);
|
|
oldSize = size;
|
|
canvasMapViewer.changeSize(size);
|
|
if (removeMapControls()) {
|
|
freeColClient.getGUI().updateMapControls();
|
|
addMapControls();
|
|
}
|
|
updateFrameSizesAndPositions(size);
|
|
|
|
revalidate();
|
|
repaint();
|
|
}
|
|
}
|
|
|
|
private void updateFrameSizesAndPositions(Dimension canvasSize) {
|
|
for (Component c : getComponents()) {
|
|
if (!(c instanceof JInternalFrame)) {
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Determines a new sensible size.
|
|
*/
|
|
final JInternalFrame f = (JInternalFrame) c;
|
|
final Boolean resizeable = (Boolean) f.getClientProperty(PROPERTY_RESIZABLE);
|
|
final Dimension newSize;
|
|
if (resizeable != null && resizeable) {
|
|
newSize = capSizeToMaximum(f, canvasSize);
|
|
f.setSize(newSize);
|
|
} else {
|
|
newSize = f.getSize();
|
|
}
|
|
|
|
/*
|
|
* Moves frames so that they are no longer out-of-bounds of the Canvas.
|
|
*/
|
|
final Point loc = f.getLocation();
|
|
final int newX = (loc.x + newSize.width > canvasSize.width) ? canvasSize.width - newSize.width : loc.x;
|
|
final int newY = (loc.y + newSize.height > canvasSize.height) ? canvasSize.height - newSize.height : loc.y;
|
|
f.setLocation(new Point(Math.max(0, newX), Math.max(0, newY)));
|
|
|
|
/*
|
|
* Maintains logical positions (like centered) after resize.
|
|
*/
|
|
final PopupPosition popupPosition = (PopupPosition) f.getClientProperty(PROPERTY_POPUP_POSITION);
|
|
if (popupPosition == null) {
|
|
continue;
|
|
}
|
|
final Point p = chooseLocation(f, f.getWidth(), f.getHeight(), popupPosition);
|
|
f.setLocation(p);
|
|
}
|
|
}
|
|
|
|
private Dimension capSizeToMaximum(JInternalFrame f, Dimension maxSize) {
|
|
final int FRAME_EMPTY_SPACE = 60;
|
|
final int width = Math.max(f.getMinimumSize().width, Math.min(f.getWidth(), maxSize.width - FRAME_EMPTY_SPACE));
|
|
final int height = Math.max(f.getMinimumSize().height, Math.min(f.getHeight(), maxSize.height - FRAME_EMPTY_SPACE));
|
|
return new Dimension(width, height);
|
|
}
|
|
|
|
// Frames and Windows
|
|
|
|
/**
|
|
* Toggle windowed flag.
|
|
*/
|
|
private void toggleWindowed() {
|
|
this.windowed = !this.windowed;
|
|
}
|
|
|
|
/**
|
|
* Create a new frame with a given menu bar and bounds.
|
|
*
|
|
* @param menuBar The new frames {@code JMenuBar}.
|
|
* @param windowBounds The new frame bounding {@code Rectangle}.
|
|
* @return The new {@code FreeColFrame}.
|
|
*/
|
|
private FreeColFrame createFrame(JMenuBar menuBar, Rectangle windowBounds) {
|
|
// FIXME: Check this:
|
|
// User might have moved window to new screen in a
|
|
// multi-screen setup, so make this.gd point to the current screen.
|
|
FreeColFrame fcf
|
|
= new FreeColFrame(this.freeColClient, this.graphicsDevice,
|
|
menuBar, isWindowed(), windowBounds);
|
|
fcf.getContentPane().add(this);
|
|
return fcf;
|
|
}
|
|
|
|
/**
|
|
* Destroy the current frame.
|
|
*/
|
|
private void destroyFrame() {
|
|
if (this.parentFrame != null) {
|
|
this.parentFrame.setVisible(false);
|
|
if (!isWindowed()) this.parentFrame.exitFullScreen();
|
|
this.parentFrame.dispose();
|
|
this.parentFrame = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine whether to use full screen or windowed mode.
|
|
*
|
|
* @param gd The {@code GraphicsDevice} to display to.
|
|
* @param desiredSize An optional window {@code Dimension}.
|
|
* @return True if windowed mode is to be used, false for full screen.
|
|
*/
|
|
private static boolean checkWindowed(GraphicsDevice gd,
|
|
Dimension desiredSize) {
|
|
boolean ret;
|
|
if (desiredSize == null) {
|
|
if (gd.isFullScreenSupported()) {
|
|
logger.info("Full screen mode used.");
|
|
ret = false;
|
|
} else {
|
|
logger.warning("Full screen mode not supported.");
|
|
System.err.println(Messages.message("client.fullScreen"));
|
|
ret = true;
|
|
}
|
|
} else {
|
|
logger.info("Windowed mode used.");
|
|
ret = true;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Adds a component on this Canvas inside a frame.
|
|
*
|
|
* @param comp The component to add to the canvas.
|
|
* @param toolBox Should be set to true if the resulting frame is
|
|
* used as a toolbox (that is: it should not be counted as a
|
|
* frame).
|
|
* @param popupPosition A preferred {@code PopupPosition}.
|
|
* @param resizable Whether this component can be resized.
|
|
* @return The {@code JInternalFrame} that was created and added.
|
|
*/
|
|
private JInternalFrame addAsFrame(JComponent comp, boolean toolBox,
|
|
PopupPosition popupPosition,
|
|
boolean resizable) {
|
|
final JInternalFrame f = (toolBox) ? new ToolBoxFrame()
|
|
: new JInternalFrame();
|
|
Container con = f.getContentPane();
|
|
if (con instanceof JComponent) {
|
|
JComponent c = (JComponent)con;
|
|
c.setOpaque(false);
|
|
c.setBorder(null);
|
|
}
|
|
|
|
if (comp.getBorder() != null) {
|
|
if (comp.getBorder() instanceof EmptyBorder) {
|
|
f.setBorder(Utility.blankBorder(10, 10, 10, 10));
|
|
} else {
|
|
f.setBorder(comp.getBorder());
|
|
comp.setBorder(Utility.blankBorder(5, 5, 5, 5));
|
|
}
|
|
} else {
|
|
f.setBorder(null);
|
|
}
|
|
|
|
final FrameMotionListener fml = new FrameMotionListener(f);
|
|
comp.addMouseMotionListener(fml);
|
|
comp.addMouseListener(fml);
|
|
if (f.getUI() instanceof BasicInternalFrameUI) {
|
|
BasicInternalFrameUI biu = (BasicInternalFrameUI) f.getUI();
|
|
biu.setNorthPane(null);
|
|
biu.setSouthPane(null);
|
|
biu.setWestPane(null);
|
|
biu.setEastPane(null);
|
|
}
|
|
|
|
f.getContentPane().add(comp);
|
|
f.setOpaque(false);
|
|
f.pack();
|
|
|
|
final Dimension size = capSizeToMaximum(f, getSize());
|
|
f.setSize(size);
|
|
|
|
final Point p = chooseLocation(comp, size.width, size.height, popupPosition);
|
|
final Point adjustedP = adjustLocationForClearSpace(p, size.width, size.height);
|
|
if (popupPosition == PopupPosition.CENTERED_FORCED || p.equals(adjustedP)) {
|
|
f.setLocation(p);
|
|
f.putClientProperty(PROPERTY_POPUP_POSITION, popupPosition);
|
|
f.putClientProperty(PROPERTY_RESIZABLE, resizable);
|
|
} else {
|
|
f.setLocation(adjustedP);
|
|
}
|
|
this.addToCanvas(f, MODAL_LAYER);
|
|
f.setName(comp.getClass().getSimpleName());
|
|
|
|
f.setFrameIcon(null);
|
|
f.setVisible(true);
|
|
f.setResizable(resizable);
|
|
try {
|
|
f.setSelected(true);
|
|
} catch (java.beans.PropertyVetoException e) {}
|
|
return f;
|
|
}
|
|
|
|
/**
|
|
* Adds a component centered on this Canvas.
|
|
*
|
|
* @param comp The {@code Component} to add to this canvas.
|
|
* @param layer The layer to add the component to (see JLayeredPane).
|
|
*/
|
|
private void addCentered(Component comp, Integer layer) {
|
|
comp.setLocation((this.oldSize.width - comp.getWidth()) / 2,
|
|
(this.oldSize.height - comp.getHeight()) / 2);
|
|
addToLayer(comp, layer);
|
|
}
|
|
|
|
/**
|
|
* Adds a component to this Canvas.
|
|
*
|
|
* @param comp The {@code Component} to add to this canvas.
|
|
* @param layer The layer to add the component to (see JLayeredPane).
|
|
*/
|
|
private void addToLayer(Component comp, Integer layer) {
|
|
addToCanvas(comp, layer);
|
|
updateMenuBar();
|
|
}
|
|
|
|
/**
|
|
* Adds a component to this Canvas updating the menus.
|
|
*
|
|
* @param comp The {@code Component} to add to this canvas.
|
|
* @param layer The layer to add the component to (see JLayeredPane).
|
|
*/
|
|
private void addToCanvas(Component comp, Integer layer) {
|
|
try {
|
|
// To avoid illegal component position exception - remove the component first
|
|
remove(comp);
|
|
add(comp, layer);
|
|
} catch (Exception e) {
|
|
logger.log(Level.WARNING, "addToCanvas("
|
|
+ comp.getClass().getSimpleName()
|
|
+ " at " + comp.getX() + "," + comp.getY()
|
|
+ " on layer " + layer + ") failed.", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the internal frame for the given component.
|
|
*
|
|
* @param c The {@code Component}.
|
|
* @return The given component if this is an internal frame or the
|
|
* first parent that is an internal frame. Returns
|
|
* {@code null} if no internal frame is found.
|
|
*/
|
|
private JInternalFrame getInternalFrame(final Component c) {
|
|
Component temp = c;
|
|
|
|
while (temp != null && !(temp instanceof JInternalFrame)) {
|
|
temp = temp.getParent();
|
|
}
|
|
return (JInternalFrame) temp;
|
|
}
|
|
|
|
/**
|
|
* Is nothing showing?
|
|
*
|
|
* @return True if no dialog or panel is being shown.
|
|
*/
|
|
private boolean nothingShowing() {
|
|
return this.dialogs.isEmpty() && getShowingPanel() == null;
|
|
}
|
|
|
|
|
|
// Location, choice and persistence
|
|
|
|
/**
|
|
* Choose a location for a component.
|
|
*
|
|
* @param comp The {@code Component} to place.
|
|
* @param width The component width to use.
|
|
* @param height The component height to use.
|
|
* @param popupPosition An optional {@code PopupPosition} hint.
|
|
* @return A suitable {@code Point} to place the component.
|
|
*/
|
|
private Point chooseLocation(Component comp, int width, int height,
|
|
PopupPosition popupPosition) {
|
|
Point p = null;
|
|
if ((comp instanceof FreeColPanel)
|
|
&& (p = getSavedPosition(comp)) != null) {
|
|
// Sanity check stuff coming out of client options.
|
|
if (p.getX() < 0
|
|
|| p.getX() >= getWidth() - width
|
|
|| p.getY() < 0
|
|
|| p.getY() >= getHeight() - height) {
|
|
p = null;
|
|
}
|
|
}
|
|
int x = 0, y = 0;
|
|
if (p != null) {
|
|
x = (int)p.getX();
|
|
y = (int)p.getY();
|
|
} else if (popupPosition != null) {
|
|
switch (popupPosition) {
|
|
case CENTERED:
|
|
case CENTERED_FORCED:
|
|
x = (getWidth() - width) / 2;
|
|
y = (getHeight() - height) / 2;
|
|
break;
|
|
case CENTERED_LEFT:
|
|
x = (getWidth() - width) / 4;
|
|
y = (getHeight() - height) / 2;
|
|
break;
|
|
case CENTERED_RIGHT:
|
|
x = ((getWidth() - width) * 3) / 4;
|
|
y = (getHeight() - height) / 2;
|
|
break;
|
|
case ORIGIN:
|
|
x = y = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new Point(x, y);
|
|
}
|
|
|
|
private Point adjustLocationForClearSpace(Point location, int width, int height) {
|
|
Point p;
|
|
int x = location.x;
|
|
int y = location.y;
|
|
if ((p = getClearSpace(x, y, width, height, MAXTRY)) != null
|
|
&& p.x >= 0 && p.x < getWidth()
|
|
&& p.y >= 0 && p.y < getHeight()) {
|
|
x = p.x;
|
|
y = p.y;
|
|
}
|
|
return new Point(x, y);
|
|
}
|
|
|
|
/**
|
|
* Try to find some free space on the canvas for a component,
|
|
* starting at x,y.
|
|
*
|
|
* @param x A starting x coordinate.
|
|
* @param y A starting y coordinate.
|
|
* @param w The component width to use.
|
|
* @param h The component height to use.
|
|
* @param tries The number of attempts to find a clear space.
|
|
* @return A {@code Point} to place the component at or null
|
|
* on failure.
|
|
*/
|
|
private Point getClearSpace(final int x, final int y,
|
|
final int w, final int h, int tries) {
|
|
final Rectangle bounds = this.getBounds();
|
|
if (!bounds.contains(x, y)) return null;
|
|
|
|
tries = 3 * tries + 1; // 3 new candidates per level
|
|
List<Point> todo = new ArrayList<>();
|
|
Point p = new Point(x, y);
|
|
todo.add(p);
|
|
|
|
List<Component> allComponents
|
|
= transform(this.getComponents(),
|
|
c -> !(c instanceof GrayLayer) && c.isValid());
|
|
allComponents.addAll(dialogs);
|
|
|
|
// Find the position with the least overlap
|
|
int bestScore = Integer.MAX_VALUE;
|
|
Point best = p;
|
|
while (!todo.isEmpty()) {
|
|
p = todo.remove(0);
|
|
Rectangle r = new Rectangle(p.x, p.y, w, h);
|
|
if (!bounds.contains(r)) {
|
|
continue;
|
|
}
|
|
|
|
// Find the most overlapping component at this position,
|
|
// as well as the globally least.
|
|
int foundScore = 0;
|
|
Component found = null;
|
|
for (Component c : allComponents) {
|
|
Rectangle rb = c.getBounds();
|
|
if (rb.intersects(r)) {
|
|
Rectangle rr = rb.intersection(r);
|
|
int score = (int)Math.round(rr.getWidth() * rr.getHeight());
|
|
if (foundScore < score) {
|
|
foundScore = score;
|
|
found = c;
|
|
}
|
|
}
|
|
}
|
|
if (found == null) { // Can not improve on no overlap, return now
|
|
return p;
|
|
}
|
|
if (bestScore > foundScore) {
|
|
bestScore = foundScore;
|
|
best = p;
|
|
}
|
|
// Guarantee eventual completion
|
|
if (--tries <= 0) break;
|
|
|
|
int n = todo.size(),
|
|
// Some alternative new positions
|
|
// 0: move right/down to avoid the collision
|
|
// 1: move as far as possible right/down
|
|
// 2: wrap back to the far left
|
|
x0 = found.getX() + found.getWidth() + 1,
|
|
y0 = found.getY() + found.getHeight() + 1,
|
|
x1 = bounds.x + bounds.width - w - 1,
|
|
y1 = bounds.y + bounds.height - h - 1,
|
|
x2 = bounds.x,
|
|
y2 = bounds.y;
|
|
boolean x0ok = bounds.contains(x0 + w, y),
|
|
y0ok = bounds.contains(x, y0 + h),
|
|
x1ok = bounds.contains(x1, y),
|
|
y1ok = bounds.contains(x, y1);
|
|
todo.add(n, new Point((x0ok) ? x0 : (x1ok) ? x1 : x2,
|
|
(y0ok) ? y0 : (y1ok) ? y1 : y2));
|
|
todo.add(n, new Point(x, (y0ok) ? y0 : (y1ok) ? y1 : y2));
|
|
todo.add(n, new Point((x0ok) ? x0 : (x1ok) ? x1 : x2, y));
|
|
}
|
|
return best;
|
|
}
|
|
|
|
/**
|
|
* Gets the saved position of a component.
|
|
*
|
|
* @param comp The {@code Component} to use.
|
|
* @return The saved position as a {@code Point}, or null if no
|
|
* saved position is found.
|
|
*/
|
|
private Point getSavedPosition(Component comp) {
|
|
final ClientOptions co = this.freeColClient.getClientOptions();
|
|
if (co == null) return null;
|
|
try {
|
|
if (!co.getBoolean(ClientOptions.REMEMBER_PANEL_POSITIONS)) {
|
|
return null;
|
|
}
|
|
} catch (Exception e) {}
|
|
return co.getPanelPosition(comp.getClass().getName());
|
|
}
|
|
|
|
/**
|
|
* Get the saved size of a component.
|
|
*
|
|
* @param comp The {@code Component} to use.
|
|
* @return A {@code Dimension} for the component or null if
|
|
* no saved size is found.
|
|
*/
|
|
private Dimension getSavedSize(Component comp) {
|
|
final ClientOptions co = this.freeColClient.getClientOptions();
|
|
if (co == null) return null;
|
|
try {
|
|
if (!co.getBoolean(ClientOptions.REMEMBER_PANEL_SIZES)) {
|
|
return null;
|
|
}
|
|
} catch (Exception e) {}
|
|
return co.getPanelSize(comp.getClass().getName());
|
|
}
|
|
|
|
/**
|
|
* Save an {@code int} value to the saved ClientOptions,
|
|
* using the name of the components class plus the given key as
|
|
* and identifier.
|
|
*
|
|
* @param className The class name for the component.
|
|
* @param key The key to save.
|
|
* @param value The value to save.
|
|
*/
|
|
private void saveInteger(String className, String key, int value) {
|
|
if (this.freeColClient == null) return;
|
|
final ClientOptions co = this.freeColClient.getClientOptions();
|
|
if (co == null) return;
|
|
final OptionGroup etc = co.getOptionGroup(ClientOptions.ETC);
|
|
if (etc == null) return;
|
|
|
|
// Insist the option is present
|
|
if (!etc.hasOption(className + key, IntegerOption.class)) {
|
|
Specification specification = (this.freeColClient.getGame() == null)
|
|
? null : this.freeColClient.getGame().getSpecification();
|
|
etc.add(new IntegerOption(className + key, specification));
|
|
}
|
|
// Set the value
|
|
etc.setInteger(className + key, value);
|
|
}
|
|
|
|
/**
|
|
* Save the position of a component.
|
|
*
|
|
* @param comp The {@code Component} to use.
|
|
* @param position The position to save.
|
|
*/
|
|
private void savePosition(Component comp, Point position) {
|
|
try {
|
|
if (!this.freeColClient.getClientOptions()
|
|
.getBoolean(ClientOptions.REMEMBER_PANEL_POSITIONS)) return;
|
|
} catch (Exception e) {}
|
|
|
|
String className = comp.getClass().getName();
|
|
saveInteger(className, ".x", position.x);
|
|
saveInteger(className, ".y", position.y);
|
|
}
|
|
|
|
/**
|
|
* Save the size of a component.
|
|
*
|
|
* @param comp The {@code Component} to use.
|
|
* @param size The {@code Dimension} to save.
|
|
*/
|
|
private void saveSize(Component comp, Dimension size) {
|
|
try {
|
|
if (!this.freeColClient.getClientOptions()
|
|
.getBoolean(ClientOptions.REMEMBER_PANEL_SIZES)) return;
|
|
} catch (Exception e) {}
|
|
|
|
String className = comp.getClass().getName();
|
|
saveInteger(className, ".w", size.width);
|
|
saveInteger(className, ".h", size.height);
|
|
}
|
|
|
|
/**
|
|
* A component is closing. Some components need position and size
|
|
* to be saved.
|
|
*
|
|
* @param c The closing {@code Component}.
|
|
* @param jif The enclosing {@code JInternalFrame}.
|
|
*/
|
|
private void notifyClose(Component c, JInternalFrame jif) {
|
|
if (c instanceof FreeColPanel) {
|
|
FreeColPanel fcp = (FreeColPanel)c;
|
|
fcp.firePropertyChange("closing", false, true);
|
|
|
|
savePosition(fcp, jif.getLocation());
|
|
saveSize(fcp, fcp.getSize());
|
|
|
|
if (nothingShowing()) updateMenuBar();
|
|
}
|
|
}
|
|
|
|
|
|
// Public routines follow
|
|
|
|
// Initialization and teardown
|
|
|
|
/**
|
|
* Removes components that is only used when in game.
|
|
*/
|
|
public void removeInGameComponents() {
|
|
// remove listeners, they will be added when launching the new game...
|
|
removeKeyAndMouseListeners();
|
|
|
|
for (Component c : getComponents()) {
|
|
if (c instanceof CanvasMapViewer) {
|
|
continue;
|
|
}
|
|
removeFromCanvas(c);
|
|
}
|
|
}
|
|
|
|
private void removeKeyAndMouseListeners() {
|
|
KeyListener[] keyListeners = getKeyListeners();
|
|
for (KeyListener keyListener : keyListeners) {
|
|
removeKeyListener(keyListener);
|
|
}
|
|
|
|
MouseListener[] mouseListeners = getMouseListeners();
|
|
for (MouseListener mouseListener : mouseListeners) {
|
|
removeMouseListener(mouseListener);
|
|
}
|
|
|
|
MouseMotionListener[] mouseMotionListeners = getMouseMotionListeners();
|
|
for (MouseMotionListener mouseMotionListener : mouseMotionListeners) {
|
|
removeMouseMotionListener(mouseMotionListener);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Map editor initialization.
|
|
*/
|
|
public void startMapEditorGUI() {
|
|
removeKeyAndMouseListeners();
|
|
|
|
freeColClient.updateActions();
|
|
this.parentFrame.setMenuBar(new MapEditorMenuBar(this.freeColClient,
|
|
new MenuMouseMotionListener(scrolling)));
|
|
CanvasMapEditorMouseListener listener
|
|
= new CanvasMapEditorMouseListener(this.freeColClient, scrolling);
|
|
addMouseListener(listener);
|
|
addMouseMotionListener(listener);
|
|
}
|
|
|
|
/**
|
|
* In game initializations.
|
|
*/
|
|
public void initializeInGame() {
|
|
removeKeyAndMouseListeners();
|
|
|
|
this.parentFrame.setMenuBar(new InGameMenuBar(this.freeColClient,
|
|
new MenuMouseMotionListener(scrolling)));
|
|
addMouseListener(new CanvasMouseListener(this.freeColClient));
|
|
addMouseMotionListener(new CanvasMouseMotionListener(this.freeColClient, scrolling));
|
|
}
|
|
|
|
/**
|
|
* Quit the canvas.
|
|
*/
|
|
public void quit() {
|
|
destroyFrame();
|
|
}
|
|
|
|
/**
|
|
* Set preferred size to saved size, or to the given
|
|
* {@code Dimension} if no saved size was found. Call this
|
|
* method in the constructor of a FreeColPanel in order to
|
|
* remember its size and position.
|
|
*
|
|
* @param comp The {@code Component} to use.
|
|
* @param d The {@code Dimension} to use as default.
|
|
*/
|
|
public void restoreSavedSize(Component comp, Dimension d) {
|
|
final Dimension pref = comp.getPreferredSize();
|
|
final Dimension sugg = (d == null) ? pref : d;
|
|
boolean save = false;
|
|
|
|
Dimension size = getSavedSize(comp);
|
|
if (size == null) {
|
|
size = new Dimension(pref);
|
|
save = true;
|
|
}
|
|
|
|
// Fix up broken/outdated saved sizes
|
|
if(size.width < sugg.width) {
|
|
size.width = sugg.width;
|
|
save = true;
|
|
}
|
|
if(size.height < sugg.height) {
|
|
size.height = sugg.height;
|
|
save = true;
|
|
}
|
|
if(size.width < pref.width) {
|
|
size.width = pref.width;
|
|
save = true;
|
|
}
|
|
if(size.height < pref.height) {
|
|
size.height = pref.height;
|
|
save = true;
|
|
}
|
|
|
|
if(save) {
|
|
saveSize(comp, size);
|
|
}
|
|
|
|
if (!pref.equals(size)) {
|
|
comp.setPreferredSize(size);
|
|
}
|
|
}
|
|
|
|
|
|
// Animation handling
|
|
|
|
/**
|
|
* Add and remove animation labels.
|
|
*
|
|
* @param label A {@code JLabel} for an animation.
|
|
* @param add If true, add the label, else remove it.
|
|
*/
|
|
public void animationLabel(JLabel label, boolean add) {
|
|
if (add) {
|
|
addToCanvas(label, JLayeredPane.DEFAULT_LAYER);
|
|
} else {
|
|
removeFromCanvas(label);
|
|
}
|
|
}
|
|
|
|
// Dialog and panel primitives, several only public for Widgets
|
|
|
|
/**
|
|
* Close a panel by class name.
|
|
*
|
|
* @param panel The panel to close.
|
|
*/
|
|
public void closePanel(String panel) {
|
|
if (panel.endsWith("Panel")) {
|
|
for (Component c1 : getComponents()) {
|
|
if (c1 instanceof JInternalFrame) {
|
|
for (Component c2 : ((JInternalFrame)c1).getContentPane()
|
|
.getComponents()) {
|
|
if (panel.equals(c2.getClass().getName())) {
|
|
notifyClose(c2, (JInternalFrame)c1);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (panel.endsWith("Dialog")) {
|
|
for (FreeColDialog<?> fcd : new ArrayList<>(dialogs)) {
|
|
if (panel.equals(fcd.getClass().getName())) {
|
|
dialogs.remove(fcd);
|
|
fcd.dispose();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a dialog to the current dialog list.
|
|
*
|
|
* @param fcd The dialog to add.
|
|
*/
|
|
public void dialogAdd(FreeColDialog<?> fcd) {
|
|
if (fcd.isModal()) {
|
|
this.mapViewer.getMapViewerState().setCursorBlinking(false);
|
|
}
|
|
dialogs.add(fcd);
|
|
}
|
|
|
|
/**
|
|
* Remove a dialog from the current dialog list.
|
|
*
|
|
* @param fcd The dialog to remove.
|
|
*/
|
|
public void dialogRemove(FreeColDialog<?> fcd) {
|
|
dialogs.remove(fcd);
|
|
if (fcd.isModal() && none(dialogs, FreeColDialog::isModal)) {
|
|
this.mapViewer.getMapViewerState().setCursorBlinking(true);
|
|
}
|
|
if (nothingShowing()) updateMenuBar();
|
|
}
|
|
|
|
/**
|
|
* Get a currentlydisplayed FreeColDialog of a given type.
|
|
*
|
|
* @param type The class of dialog to look for.
|
|
* @return The {@code FreeColDialog} found, or null if none present.
|
|
*/
|
|
public FreeColDialog<?> getExistingFreeColDialog(Class<?> type) {
|
|
for (FreeColDialog<?> d : dialogs) {
|
|
if (d.getClass() == type) return d;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets a currently displayed FreeColPanel of a given type.
|
|
*
|
|
* @param <T> The actual panel type.
|
|
* @param type The type of {@code FreeColPanel} to look for.
|
|
* @return A currently displayed {@code FreeColPanel} of the
|
|
* requested type, or null if none found.
|
|
*/
|
|
public <T extends FreeColPanel> T getExistingFreeColPanel(Class<T> type) {
|
|
for (Component c1 : getComponents()) {
|
|
if (c1 instanceof JInternalFrame) {
|
|
for (Component c2 : ((JInternalFrame)c1).getContentPane()
|
|
.getComponents()) {
|
|
try {
|
|
T ret = type.cast(c2);
|
|
if (ret != null) {
|
|
final JInternalFrame jif = (JInternalFrame)c1;
|
|
SwingUtilities.invokeLater(() -> {
|
|
jif.toFront();
|
|
jif.repaint();
|
|
});
|
|
return ret;
|
|
}
|
|
} catch (ClassCastException cce) {}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Gets any currently displayed component matching a predicate.
|
|
*
|
|
* @param pred The predicate to apply.
|
|
* @return The first match for the predicate, or null if none found.
|
|
*/
|
|
public Component getMatchingComponent(Predicate<Component> pred) {
|
|
for (Component c1 : getComponents()) {
|
|
if (c1 instanceof JInternalFrame) {
|
|
for (Component c2 : ((JInternalFrame) c1).getContentPane()
|
|
.getComponents()) {
|
|
if (pred.test(c2)) return c2;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get any panel this {@code Canvas} is displaying.
|
|
*
|
|
* @return A {@code Component} the {@code Canvas} is
|
|
* displaying, or null if none found.
|
|
*/
|
|
public Component getShowingPanel() {
|
|
for (Component c : getComponents()) {
|
|
if (c instanceof ToolBoxFrame) {
|
|
continue;
|
|
}
|
|
if (c instanceof JInternalFrame) {
|
|
return c;
|
|
} else if (c instanceof JInternalFrame.JDesktopIcon) {
|
|
return c;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Removes the given component from this canvas without
|
|
* updating the menu bar.
|
|
*
|
|
* @param comp The {@code Component} to remove.
|
|
*/
|
|
public void removeFromCanvas(Component comp) {
|
|
if (comp == null) return;
|
|
|
|
final Rectangle updateBounds = comp.getBounds();
|
|
final JInternalFrame jif = getInternalFrame(comp);
|
|
if (jif != null && jif != comp) {
|
|
jif.dispose();
|
|
} else {
|
|
// Java 1.7.0 as seen on Fedora with:
|
|
// Java version: 1.7.0_40
|
|
// Java WM version: 24.0-b56
|
|
// crashes here deep in the java libraries.
|
|
try {
|
|
super.remove(comp);
|
|
} catch (Exception e) {
|
|
logger.log(Level.WARNING, "Java crash", e);
|
|
}
|
|
}
|
|
if (jif != null) { // Notify close after removing from Canvas
|
|
notifyClose(comp, jif);
|
|
}
|
|
repaint(updateBounds.x, updateBounds.y,
|
|
updateBounds.width, updateBounds.height);
|
|
}
|
|
|
|
/**
|
|
* Displays the given dialog, optionally making sure a tile is visible.
|
|
*
|
|
* @param <T> The type to be returned from the dialog.
|
|
* @param dialog The {@code FreeColDialog} to be displayed
|
|
* @param pos A {@code PopupPosition} for the dialog.
|
|
* @return The {@link FreeColDialog#getResponse reponse} returned by
|
|
* the dialog.
|
|
*/
|
|
public <T> T showFreeColDialog(FreeColDialog<T> dialog, PopupPosition pos) {
|
|
viewFreeColDialog(dialog, pos);
|
|
T response = dialog.getResponse();
|
|
remove(dialog);
|
|
dialogRemove(dialog);
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Displays a {@code FreeColPanel} at a generalized position.
|
|
*
|
|
* @param panel {@code FreeColPanel}, panel to show
|
|
* @param popupPosition {@code PopupPosition} The generalized
|
|
* position to place the panel.
|
|
* @param resizable Should the panel be resizable?
|
|
* @return The panel.
|
|
*/
|
|
public FreeColPanel showFreeColPanel(FreeColPanel panel,
|
|
PopupPosition popupPosition,
|
|
boolean resizable) {
|
|
repaint();
|
|
addAsFrame(panel, false, popupPosition, resizable);
|
|
panel.requestFocus();
|
|
freeColClient.getActionManager().update();
|
|
return panel;
|
|
}
|
|
|
|
/**
|
|
* Displays the given dialog, optionally making sure a tile is visible.
|
|
*
|
|
* @param <T> The type to be returned from the dialog.
|
|
* @param freeColDialog The dialog to be displayed
|
|
* @param pos A {@code PopupPosition} for the dialog.
|
|
*/
|
|
public <T> void viewFreeColDialog(final FreeColDialog<T> freeColDialog,
|
|
PopupPosition pos) {
|
|
// TODO: Remove compatibility code when all non-modal dialogs
|
|
// have been converted into panels.
|
|
if (!freeColDialog.isModal()) {
|
|
int canvasWidth = getWidth();
|
|
int dialogWidth = freeColDialog.getWidth();
|
|
if (dialogWidth*2 <= canvasWidth) {
|
|
Point location = freeColDialog.getLocation();
|
|
if (pos == PopupPosition.CENTERED_LEFT) {
|
|
freeColDialog.setLocation(location.x - canvasWidth/4,
|
|
location.y);
|
|
} else if (pos == PopupPosition.CENTERED_RIGHT) {
|
|
freeColDialog.setLocation(location.x + canvasWidth/4,
|
|
location.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
dialogAdd(freeColDialog);
|
|
freeColDialog.requestFocus();
|
|
freeColDialog.setVisible(true);
|
|
}
|
|
|
|
|
|
// Frames and windowing
|
|
|
|
/**
|
|
* Are we in windowed mode?
|
|
*
|
|
* @return True if in windowed mode.
|
|
*/
|
|
public boolean isWindowed() {
|
|
return this.windowed;
|
|
}
|
|
|
|
/**
|
|
* Get the parent frame.
|
|
*
|
|
* Do not use this except inside Widgets (which is really just an
|
|
* extension of Canvas).
|
|
*
|
|
* @return The parent {@code FreeColFrame}.
|
|
*/
|
|
public FreeColFrame getParentFrame() {
|
|
return this.parentFrame;
|
|
}
|
|
|
|
/**
|
|
* Toggle the frame between windowed and non-windowed modes.
|
|
*/
|
|
public void toggleFrame() {
|
|
JMenuBar menuBar = null;
|
|
Rectangle windowBounds = null;
|
|
if (this.parentFrame != null) {
|
|
menuBar = this.parentFrame.getJMenuBar();
|
|
windowBounds = this.parentFrame.getBounds();
|
|
}
|
|
destroyFrame();
|
|
toggleWindowed();
|
|
this.parentFrame = createFrame(menuBar, windowBounds);
|
|
this.parentFrame.setVisible(true);
|
|
}
|
|
|
|
|
|
// Map controls, called out of paintComponent via checkResize
|
|
|
|
/**
|
|
* Add the map controls.
|
|
*
|
|
* @return True if any were added.
|
|
*/
|
|
public boolean addMapControls() {
|
|
if (this.mapControls == null) return false;
|
|
List<Component> components
|
|
= this.mapControls.getComponentsToAdd(this.oldSize);
|
|
for (Component c : components) {
|
|
addToCanvas(c, JLayeredPane.MODAL_LAYER);
|
|
}
|
|
return !components.isEmpty();
|
|
}
|
|
|
|
/**
|
|
* Remove the map controls.
|
|
*
|
|
* @return True if any were removed.
|
|
*/
|
|
public boolean removeMapControls() {
|
|
if (mapControls == null) {
|
|
return false;
|
|
}
|
|
final List<Component> components = mapControls.getComponentsPresent();
|
|
boolean ret = false;
|
|
for (Component c : components) {
|
|
removeFromCanvas(c);
|
|
ret = true;
|
|
}
|
|
mapControls.clear();
|
|
return ret;
|
|
}
|
|
|
|
|
|
// Menus
|
|
|
|
/**
|
|
* Closes all the menus that are currently open.
|
|
*/
|
|
public void closeMenus() {
|
|
for (JInternalFrame jif : getAllFrames()) {
|
|
for (Component c : jif.getContentPane().getComponents()) {
|
|
notifyClose(c, jif);
|
|
}
|
|
jif.dispose();
|
|
}
|
|
while (!dialogs.isEmpty()) {
|
|
FreeColDialog<?> dialog = dialogs.remove(0);
|
|
dialog.dispose();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reset the menu bar.
|
|
*/
|
|
public void resetMenuBar() {
|
|
this.freeColClient.updateActions();
|
|
this.parentFrame.resetMenuBar();
|
|
}
|
|
|
|
/**
|
|
* Update the menu bar.
|
|
*/
|
|
public void updateMenuBar() {
|
|
this.freeColClient.updateActions();
|
|
this.parentFrame.updateMenuBar();
|
|
}
|
|
|
|
|
|
// Video
|
|
|
|
/**
|
|
* Play the opening video.
|
|
*
|
|
* @param videoId An identifier for the video content.
|
|
* @param muteAudio Mute if true.
|
|
* @param runnable A {@code Runnable} to run on completion.
|
|
*/
|
|
public void playVideo(String videoId, boolean muteAudio,
|
|
final Runnable runnable) {
|
|
final Video video = ImageLibrary.getVideo(videoId);
|
|
if (video == null) {
|
|
runnable.run();
|
|
return;
|
|
}
|
|
|
|
final String originalVendor = System.getProperty("java.vendor");
|
|
if (originalVendor.indexOf(" ") == -1) {
|
|
/* Cortado crashes unless there is a space in "java.vendor". */
|
|
System.setProperty("java.vendor", originalVendor + " cortadoBugFix");
|
|
}
|
|
|
|
final VideoComponent vc = new VideoComponent(video, muteAudio, getSize());
|
|
|
|
final class AbortListener implements ActionListener, KeyListener,
|
|
MouseListener, VideoListener {
|
|
|
|
private Timer t = null;
|
|
|
|
@Override
|
|
public void keyPressed(KeyEvent e) {
|
|
}
|
|
|
|
@Override
|
|
public void keyReleased(KeyEvent e1) {
|
|
execute();
|
|
}
|
|
|
|
@Override
|
|
public void keyTyped(KeyEvent e2) {
|
|
}
|
|
|
|
@Override
|
|
public void mouseClicked(MouseEvent e3) {
|
|
execute();
|
|
}
|
|
|
|
@Override
|
|
public void mouseEntered(MouseEvent e4) {
|
|
}
|
|
|
|
@Override
|
|
public void mouseExited(MouseEvent e5) {
|
|
}
|
|
|
|
@Override
|
|
public void mousePressed(MouseEvent e6) {
|
|
}
|
|
|
|
@Override
|
|
public void mouseReleased(MouseEvent e7) {
|
|
}
|
|
|
|
@Override
|
|
public void stopped() {
|
|
execute();
|
|
}
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent ae) { // from timer
|
|
execute();
|
|
}
|
|
|
|
public void startTimer(int tim) {
|
|
this.t = new Timer(tim, this);
|
|
this.t.setRepeats(false);
|
|
this.t.start();
|
|
}
|
|
|
|
private void execute() {
|
|
removeKeyListener(this);
|
|
removeMouseListener(this);
|
|
vc.removeMouseListener(this);
|
|
vc.stop();
|
|
remove(vc);
|
|
if (t != null) {
|
|
t.stop();
|
|
}
|
|
|
|
System.setProperty("java.vendor", originalVendor);
|
|
runnable.run();
|
|
}
|
|
}
|
|
|
|
closeMenus();
|
|
// Clicks or keyboard on the canvas and the video stop the video
|
|
AbortListener l = new AbortListener();
|
|
addMouseListener(l);
|
|
addKeyListener(l);
|
|
vc.addMouseListener(l);
|
|
vc.addKeyListener(l);
|
|
// The Cortado applet is failing to quit when finished, make
|
|
// sure it eventually gets kicked.
|
|
// Change the magic number if we change the opening video length.
|
|
l.startTimer(80000);
|
|
|
|
// Add video and play
|
|
addCentered(vc, JLayeredPane.PALETTE_LAYER);
|
|
vc.play();
|
|
}
|
|
|
|
|
|
// Special dialogs and panels
|
|
|
|
/**
|
|
* Closes the {@link MainPanel}.
|
|
*/
|
|
public void closeMainPanel() {
|
|
if (this.mainPanel != null) {
|
|
remove(this.mainPanel);
|
|
this.mainPanel = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Closes all panels, changes the background and shows the main menu.
|
|
*/
|
|
public void mainTitle() {
|
|
showMainPanel();
|
|
revalidate();
|
|
repaint();
|
|
}
|
|
|
|
void mainTitleIfMainPanelIsAlreadyShowing() {
|
|
if (mainPanel != null && mainPanel.isShowing()) {
|
|
mainTitle();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows the {@code MainPanel}.
|
|
*
|
|
* @return The main panel.
|
|
*/
|
|
public FreeColPanel showMainPanel() {
|
|
prepareShowingMainMenu();
|
|
this.mainPanel = new MainPanel(this.freeColClient);
|
|
addAsFrame(mainPanel, false, PopupPosition.CENTERED_FORCED, false);
|
|
this.mainPanel.requestFocus();
|
|
return this.mainPanel;
|
|
}
|
|
|
|
public void prepareShowingMainMenu() {
|
|
removeInGameComponents();
|
|
closeMenus();
|
|
closeMainPanel();
|
|
this.parentFrame.removeMenuBar();
|
|
}
|
|
|
|
/**
|
|
* Display the map editor transform panel.
|
|
*/
|
|
public void showMapEditorTransformPanel() {
|
|
MapEditorTransformPanel panel
|
|
= new MapEditorTransformPanel(this.freeColClient);
|
|
JInternalFrame f = addAsFrame(panel, true, PopupPosition.CENTERED,
|
|
false);
|
|
f.setLocation(f.getX(), 50); // move up to near the top
|
|
repaint();
|
|
}
|
|
|
|
|
|
// Override JComponent
|
|
public void paintJustTheMapImmediately() {
|
|
if (this.freeColClient == null
|
|
|| this.freeColClient.getGame() == null
|
|
|| this.freeColClient.getGame().getMap() == null) {
|
|
return;
|
|
}
|
|
|
|
// TODO: Allow terrain animations to be turned off.
|
|
|
|
/*
|
|
* We can change to active rendering using:
|
|
*
|
|
final Graphics2D g2d = (Graphics2D) getGraphics();
|
|
final Dimension size = getSize();
|
|
g2d.setClip(0, 0, size.width, size.height);
|
|
this.mapViewer.displayMap(g2d, size);
|
|
g2d.dispose();
|
|
|
|
* ...but then we also need to call the methods in this component,
|
|
* for example paintChildren.
|
|
*/
|
|
|
|
canvasMapViewer.paintImmediately();
|
|
}
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void paintComponent(Graphics g) {
|
|
super.paintComponent(g);
|
|
|
|
/*
|
|
* TODO: We should not manipulate the component tree here.
|
|
*/
|
|
if (!this.freeColClient.isInGame()) {
|
|
return;
|
|
}
|
|
|
|
final boolean hasMap = this.freeColClient != null
|
|
&& this.freeColClient.getGame() != null
|
|
&& this.freeColClient.getGame().getMap() != null;
|
|
|
|
if (hasMap) {
|
|
final Dimension size = getSize();
|
|
|
|
// toggle grey layer if needed
|
|
if (this.freeColClient.currentPlayerIsMyPlayer()) {
|
|
if (greyLayer.getParent() != null) {
|
|
removeFromCanvas(greyLayer);
|
|
}
|
|
} else {
|
|
greyLayer.setBounds(0, 0, size.width, size.height);
|
|
greyLayer.setPlayer(this.freeColClient.getGame()
|
|
.getCurrentPlayer());
|
|
if (greyLayer.getParent() == null) {
|
|
addToCanvas(greyLayer, JLayeredPane.DRAG_LAYER);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
// Override Container
|
|
|
|
/**
|
|
* {@inheritDoc}
|
|
*/
|
|
@Override
|
|
public void remove(Component comp) {
|
|
removeFromCanvas(comp);
|
|
}
|
|
}
|