shuffle.fwk.ShuffleController.java Source code

Java tutorial

Introduction

Here is the source code for shuffle.fwk.ShuffleController.java

Source

/*  ShuffleMove - A program for identifying and simulating ideal moves in the game
 *  called Pokemon Shuffle.
 *  
 *  Copyright (C) 2015  Andrew Meyers
 *  
 *  This program 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 3 of the License, or
 *  (at your option) any later version.
 *  This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package shuffle.fwk;

import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Observable;
import java.util.Set;
import java.util.UUID;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import javax.swing.JDialog;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.FontUIResource;

import org.apache.commons.lang3.StringUtils;

import shuffle.fwk.config.ConfigFactory;
import shuffle.fwk.config.ConfigManager;
import shuffle.fwk.config.manager.BoardManager;
import shuffle.fwk.config.manager.EffectManager;
import shuffle.fwk.config.manager.EntryModeManager;
import shuffle.fwk.config.manager.GradingModeManager;
import shuffle.fwk.config.manager.ImageManager;
import shuffle.fwk.config.manager.RosterManager;
import shuffle.fwk.config.manager.SpeciesManager;
import shuffle.fwk.config.manager.TeamManager;
import shuffle.fwk.data.Board.Status;
import shuffle.fwk.data.Effect;
import shuffle.fwk.data.Species;
import shuffle.fwk.data.SpeciesPaint;
import shuffle.fwk.data.Stage;
import shuffle.fwk.data.Team;
import shuffle.fwk.data.simulation.SimulationResult;
import shuffle.fwk.data.simulation.SimulationTask;
import shuffle.fwk.data.simulation.SimulationUser;
import shuffle.fwk.gui.ShuffleFrame;
import shuffle.fwk.gui.user.ShuffleFrameUser;
import shuffle.fwk.i18n.I18nUser;
import shuffle.fwk.service.BaseServiceManager;
import shuffle.fwk.service.movepreferences.MovePreferencesService;

/**
 * @author Andrew Meyers
 *
 */
public class ShuffleController extends Observable
        implements ShuffleViewUser, ShuffleModelUser, ShuffleFrameUser, SimulationUser, I18nUser {
    /** The log properties file path */
    private static final String LOG_CONFIG_FILE = "config/logger.properties";

    /** The logger for this controller. */
    private static final Logger LOG = Logger.getLogger(ShuffleController.class.getName());

    static { // Sets look and feel to a nicer version than the default
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                | UnsupportedLookAndFeelException e) {
            LOG.warning("Cannot load NimbusLookAndFeel because: " + e.getMessage());
        }
    }

    // i18n keys
    private static final String KEY_LOAD_ALL = "log.all.load";
    private static final String KEY_SAVE_ALL = "log.all.save";
    private static final String KEY_SAVE_ALL_SUCCESS = "log.all.save.success";
    private static final String KEY_LOAD_ROSTER = "log.roster.load";
    private static final String KEY_SAVE_ROSTER = "log.roster.save";
    private static final String KEY_SAVE_ROSTER_SUCCESS = "log.roster.save.success";
    private static final String KEY_LOAD_TEAM = "log.team.load";
    private static final String KEY_SAVE_TEAM = "log.team.save";
    private static final String KEY_SAVE_TEAM_SUCCESS = "log.team.save.success";
    private static final String KEY_CLEAR_GRID = "log.grid.cleared";
    private static final String KEY_LOAD_GRID = "log.grid.load";
    private static final String KEY_LOAD_DEFAULT_GRID = "log.grid.load.default";
    private static final String KEY_SAVE_GRID = "log.grid.save";
    private static final String KEY_SAVE_GRID_SUCCESS = "log.grid.save.success";
    private static final String KEY_DO_MOVE = "log.move.do";
    private static final String KEY_REDO_MOVE = "log.move.redo";
    private static final String KEY_UNDO_MOVE = "log.move.undo";
    private static final String KEY_COMPUTE_NOW = "log.compute.now";
    private static final String KEY_COMPUTE_AUTO_TRUE = "log.compute.auto.on";
    private static final String KEY_COMPUTE_AUTO_FALSE = "log.compute.auto.off";
    private static final String KEY_ROSTER_CHANGED = "log.roster.changed";
    private static final String KEY_TEAM_CHANGED = "log.team.changed";
    private static final String KEY_IMAGES_CHANGED = "log.images.changed";
    private static final String KEY_SPECIES_CHANGED = "log.species.changed";
    private static final String KEY_GRADING_CHANGED = "log.grading.changed";

    // Scaling config keys
    private static final String KEY_FONT_SIZE_SCALING = "FONT_SIZE_SCALING";
    private static final String KEY_BORDER_SCALING = "BORDER_SCALING";

    /** The model for this controller. */
    private ShuffleModel model;
    /** The view for this controller. */
    private ShuffleView view;

    /** The ShuffleFrame which is the primary Window for the program. */
    private ShuffleFrame frame = null;
    /** The ConfigFactory used for manager creation */
    private ConfigFactory factory = null;

    /**
     * The main which starts the program.
     * 
     * @param args
     *           unused.
     */
    public static void main(String... args) {
        String userHomeArg = null;
        String levelToSetArg = null;

        if (args != null && args.length > 0) {
            userHomeArg = args[0];
            if (args.length > 1) {
                levelToSetArg = args[1];
            }
        }

        if (userHomeArg == null) {
            userHomeArg = System.getProperty("user.home") + File.separator + "Shuffle-Move";
        }
        setUserHome(userHomeArg);
        try {
            FileHandler handler = new FileHandler();
            Logger toRoot = LOG;
            while (toRoot.getParent() != null) {
                toRoot = toRoot.getParent();
            }
            toRoot.addHandler(handler);
        } catch (SecurityException e1) {
            e1.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }

        if (levelToSetArg != null) {
            try {
                Level levelToSet = Level.parse(args[1]);
                Logger.getLogger(SimulationTask.class.getName()).setLevel(levelToSet);
                SimulationTask.setLogFiner(levelToSet.intValue() <= Level.FINER.intValue());
                Logger.getLogger(ShuffleModel.class.getName()).setLevel(levelToSet);
            } catch (Exception e) {
                LOG.fine("Cannot set simulation logging to that level: " + StringUtils.join(args));
            }
        }

        ShuffleController ctrl = new ShuffleController();
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                ctrl.getFrame().launch();
            }
        });
    }

    /**
     * Sets the user home to the given path.
     * 
     * @param userHome
     *           The absolute path to the new user home.
     */
    public static void setUserHome(String userHome) {
        try {
            File absoluteFile = new File(userHome).getCanonicalFile();
            absoluteFile.mkdir();
            System.setProperty("user.dir", absoluteFile.getCanonicalPath());
            System.setProperty("user.home", absoluteFile.getCanonicalPath());
            try (InputStream is = ClassLoader.getSystemResourceAsStream(LOG_CONFIG_FILE)) {
                File logDir = new File("log").getAbsoluteFile();
                if (!logDir.exists() && !logDir.mkdirs()) {
                    throw new IOException("Cannot create log directory.");
                }
                LogManager.getLogManager().readConfiguration(is);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }

    /**
     * Creates a ShuffleController with the given configuration paths for the primary configuration
     * (which tells other managers where to get their configurations). If there are none passed, then
     * "config/main.txt" is assumed.
     * 
     * @param configPaths
     *           The paths as Strings
     */
    public ShuffleController(String... configPaths) {
        if (configPaths.length > 0 && configPaths[0] != null) {
            factory = new ConfigFactory(configPaths[0]);
        } else {
            factory = new ConfigFactory();
        }
        Integer menuFontOverride = getPreferencesManager().getIntegerValue(KEY_FONT_SIZE_SCALING);
        if (menuFontOverride != null && menuFontOverride != 100 && menuFontOverride >= 1
                && menuFontOverride <= 10000) {
            float scale = menuFontOverride.floatValue() / 100.0f;
            try {
                // This is the cleanest and most bug-free way to do this hack.
                Set<Object> allKeys = new HashSet<Object>();
                allKeys.add("JMenu.font");
                // Yes we're not supposed to use this, but it is the only one that works with Nimbus LAF
                allKeys.addAll(UIManager.getLookAndFeelDefaults().keySet());
                Object value = UIManager.get("defaultFont");
                if (value != null && value instanceof FontUIResource) {
                    FontUIResource fromFont = (javax.swing.plaf.FontUIResource) value;
                    FontUIResource toFont = new FontUIResource(fromFont.deriveFont(fromFont.getSize() * scale));
                    // This one is necessary
                    UIManager.getLookAndFeel().getDefaults().put("defaultFont", toFont);
                    // And this one allows other LAF to be used in the future
                    UIManager.getDefaults().put("defaultFont", toFont);
                }

                // Needed for Nimbus's JTable row height adjustment
                Object tableFontValue = UIManager.getLookAndFeel().getDefaults().get("Table.font");
                Number bestRowHeight = null;
                if (tableFontValue != null && tableFontValue instanceof FontUIResource) {
                    FontUIResource fromFont = (FontUIResource) tableFontValue;
                    bestRowHeight = fromFont.getSize();
                }
                Object rowHeightValue = UIManager.getLookAndFeel().getDefaults().get("Table.rowHeight");
                if (rowHeightValue != null && rowHeightValue instanceof Number) {
                    Number rowHeight = (Number) rowHeightValue;
                    rowHeight = rowHeight.doubleValue() * scale;
                    if (bestRowHeight == null || bestRowHeight.intValue() < rowHeight.intValue()) {
                        bestRowHeight = rowHeight;
                    }
                }
                if (bestRowHeight != null) {
                    bestRowHeight = bestRowHeight.doubleValue() * (4.0 / 3.0);
                }
                if (bestRowHeight != null && bestRowHeight.intValue() > 0) {
                    UIManager.getLookAndFeel().getDefaults().put("Table.rowHeight", bestRowHeight.intValue());
                }
            } catch (Exception e) {
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                e.printStackTrace(pw);
                LOG.log(Level.SEVERE, "Cannot override menu font sizes!", e);
            }
        }
        try {
            setModel(new ShuffleModel(this));
            setView(new ShuffleView(this));
            getModel().checkLocaleConfig();
            loadFrame();
        } catch (Exception e) {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);
            LOG.log(Level.SEVERE, "Failure on start:", e);
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.ModeIndicatorUser#scaleFont(java.awt.Font)
     */
    @Override
    public Font scaleFont(Font givenFont) {
        Font retFont = givenFont;
        Integer menuFontOverride = getPreferencesManager().getIntegerValue(KEY_FONT_SIZE_SCALING);
        if (menuFontOverride != null && menuFontOverride != 100 && menuFontOverride > 0
                && menuFontOverride < 10000) {
            float scale = menuFontOverride.floatValue() / 100.0f;
            float adjustedSize = retFont.getSize2D() * scale;
            retFont = retFont.deriveFont(adjustedSize);
        }
        return retFont;
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.config.provider.ImageManagerProvider#scaleBorderThickness(int)
     */
    @Override
    public Integer scaleBorderThickness(int given) {
        Integer borderScale = getPreferencesManager().getIntegerValue(KEY_BORDER_SCALING);
        Integer ret = given;
        if (borderScale != null && borderScale != 100 && borderScale >= 1 && borderScale <= 10000) {
            float scale = borderScale.floatValue() * ret.floatValue() / 100.0f;
            ret = Math.round(scale);
        }
        return ret;
    }

    /**
     * Gets the ConfigFactory associated with this ShuffleController.
     * 
     * @return
     */
    @Override
    public ConfigFactory getConfigFactory() {
        return factory;
    }

    /**
     * Gets the ShuffleModel for this ShuffleController.
     * 
     * @return The ShuffleModel
     */
    public ShuffleModel getModel() {
        return model;
    }

    /**
     * Sets the ShuffleModel for this ShuffleController
     * 
     * @param model
     *           The ShuffleModel
     */
    public void setModel(ShuffleModel model) {
        this.model = model;
    }

    /**
     * Gets the ShuffleView for this ShuffleController.
     * 
     * @return The ShuffleView
     */
    public ShuffleView getView() {
        return view;
    }

    /**
     * Sets the ShuffleView for this ShuffleController.
     * 
     * @param view
     *           The ShuffleView.
     */
    public void setView(ShuffleView view) {
        this.view = view;
    }

    // Load/Save menu options
    /**
     * Loads all data from configurations.
     */
    @Override
    public void loadAll() {
        LOG.info(getString(KEY_LOAD_ALL));
        if (getModel().loadAllData()) {
            getModel().setDataChanged();
            repaint();
        }
    }

    @Override
    public void saveAll() {
        LOG.info(getString(KEY_SAVE_ALL));
        getModel().saveAllData();
        LOG.info(getString(KEY_SAVE_ALL_SUCCESS));
    }

    /**
     * Loads all Roster data from configuration.
     */
    @Override
    public void loadRoster() {
        RosterManager manager = getModel().getRosterManager();
        if (manager.loadFromConfig()) {
            LOG.info(getString(KEY_LOAD_ROSTER));
            getModel().setDataChanged();
            repaint();
        }
    }

    /**
     * Saves all Roster data to configuration.
     */
    @Override
    public void saveRoster() {
        RosterManager manager = getModel().getRosterManager();
        LOG.info(getString(KEY_SAVE_ROSTER));
        manager.saveDataToConfig();
        LOG.info(getString(KEY_SAVE_ROSTER_SUCCESS));
    }

    /**
     * Loads all Teams data from configuration.
     */
    @Override
    public void loadTeams() {
        TeamManager manager = getModel().getTeamManager();
        if (manager.loadFromConfig()) {
            LOG.info(getString(KEY_LOAD_TEAM));
            getModel().setDataChanged();
            repaint();
        }
    }

    /**
     * Saves all Teams data to configuration.
     */
    @Override
    public void saveTeams() {
        TeamManager manager = getModel().getTeamManager();
        LOG.info(getString(KEY_SAVE_TEAM));
        manager.saveDataToConfig();
        LOG.info(getString(KEY_SAVE_TEAM_SUCCESS));
    }

    /**
     * Clears the current board.
     */
    @Override
    public void clearGrid() {
        if (getModel().clearBoard()) {
            LOG.info(getString(KEY_CLEAR_GRID));
            getModel().setDataChanged();
            repaint();
        }
    }

    /**
     * Loads the board from configuration.
     */
    @Override
    public void loadGrid() {
        if (getModel().loadBoard()) {
            LOG.info(getString(KEY_LOAD_GRID));
            getModel().setDataChanged();
            repaint();
        }
    }

    /**
     * Loads the default board for the current stage, from configuration.
     */
    @Override
    public void loadDefaultGrid() {
        if (getModel().loadDefaultBoard()) {
            LOG.info(getString(KEY_LOAD_DEFAULT_GRID));
            getModel().setDataChanged();
            repaint();
        }
    }

    /**
     * Saves the board to configuration.
     */
    @Override
    public void saveGrid() {
        LOG.info(getString(KEY_SAVE_GRID));
        getModel().saveBoard();
        LOG.info(getString(KEY_SAVE_GRID_SUCCESS));
    }

    @Override
    public void doSelectedMove() {
        if (getModel().doSelectedMove()) {
            LOG.info(getString(KEY_DO_MOVE));
            if (getModel().isSwapToPaint()) {
                getModel().setCurrentEntryMode(EntryMode.PAINT);
            }
            getModel().setCursorTo(1, 1);
            repaint();
            getFrame().toFront();
        }
    }

    @Override
    public void undoMove() {
        if (getModel().undoMove()) {
            LOG.info(getString(KEY_UNDO_MOVE));
            repaint();
        }
    }

    @Override
    public void redoMove() {
        if (getModel().redoMove()) {
            LOG.info(getString(KEY_REDO_MOVE));
            repaint();
        }
    }

    /**
     * Returns the ShuffleFrame for this view.
     * 
     * @return The ShuffleFrame
     */
    public ShuffleFrame getFrame() {
        if (frame == null) {
            frame = new ShuffleFrame(this);
        }
        return frame;
    }

    public void loadFrame() {
        if (frame == null) {
            frame = new ShuffleFrame(this);
        }
    }

    @Override
    public void changeMode() {
        EntryMode next = getModel().getCurrentEntryMode().getNextMode();
        if (getModel().setCurrentEntryMode(next)) {
            repaint();
        }
    }

    @Override
    public ImageManager getImageManager() {
        return getConfigFactory().getImageManager();
    }

    @Override
    public void toggleFrozen() {
        if (getModel().toggleFrozenPaints()) {
            repaint();
        }
    }

    @Override
    public void setSelectedSpecies(Species toPaint) {
        if (getModel().setSelectedSpecies(toPaint)) {
            repaint();
        }
    }

    @Override
    public void setCursorTo(int row, int col) {
        if (getModel().setCursorTo(row, col)) {
            repaint();
        }
    }

    @Override
    public void paintAt(SpeciesPaint paint, int row, int col) {
        if (getModel().paintAt(row, col, paint)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    @Override
    public void toggleFrozenAt(Integer row, Integer column) {
        boolean prevFrozen = getModel().getBoard().isFrozenAt(row, column);
        if (getModel().paintAt(row, column, null, !prevFrozen)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    @Override
    public void advanceCursorBy(int i) {
        if (getModel().advanceCursorBy(i)) {
            repaint();
        }
    }

    @Override
    public void setCurrentEntryMode(EntryMode mode) {
        if (getModel().setCurrentEntryMode(mode)) {
            repaint();
        }
    }

    @Override
    public void setCurrentStage(Stage newStage) {
        if (getModel().setCurrentStage(newStage)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.StageIndicatorUser#setEscalationLevel(java.lang.Integer)
     */
    @Override
    public void setEscalationLevel(Integer level) {
        if (getModel().setEscalationLevel(level)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.StageIndicatorUser#canLevelEscalation()
     */
    @Override
    public boolean canLevelEscalation() {
        return true;
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.StageIndicatorUser#getEscalationLevel()
     */
    @Override
    public Integer getEscalationLevel() {
        return getModel().getEscalationLevel();
    }

    @Override
    public String getTitle() {
        return ShuffleVersion.VERSION_FULL;
    }

    @Override
    public String getTextFor(Object value) {
        return getView().getTextFor(value);
    }

    @Override
    public Stage getCurrentStage() {
        return getModel().getCurrentStage();
    }

    @Override
    public SpeciesPaint getSelectedSpeciesPaint() {
        return getModel().getCurrentSpeciesPaint();
    }

    @Override
    public List<SpeciesPaint> getCurrentPaints() {
        return getModel().getCurrentPaints();
    }

    // Simulation User methods

    @Override
    public int getMegaProgress() {
        return getModel().getMegaProgress();
    }

    @Override
    public void setMegaProgress(int progress) {
        if (getModel().setMegaProgress(progress)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    @Override
    public boolean isMegaAllowed() {
        return getModel().isMegaAllowed();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.data.simulation.util.SimulationAcceptor#acceptResults(java.util.Collection)
     */
    @Override
    public void acceptResults(Collection<SimulationResult> results) {
        if (getModel().setBestResults(results)) {
            repaint();
        }
    }

    @Override
    public UUID getAcceptedId() {
        return getModel().getAcceptedId();
    }

    @Override
    public int getPreferredNumFeeders() {
        return getModel().getNumFeeders();
    }

    @Override
    public int getPreferredFeederHeight() {
        return getModel().getFeederHeight();
    }

    @Override
    public void computeNow() {
        LOG.info(getString(KEY_COMPUTE_NOW));
        getModel().computeNow();
        repaint();
    }

    @Override
    public boolean isAutoCompute() {
        return getModel().getAutoCompute();
    }

    @Override
    public void setAutoCompute(boolean autoCompute) {
        if (getModel().setAutoCompute(autoCompute)) {
            if (autoCompute) {
                LOG.info(getString(KEY_COMPUTE_AUTO_TRUE));
            } else {
                LOG.info(getString(KEY_COMPUTE_AUTO_FALSE));
            }
            getModel().setDataChanged();
            repaint();
        }
    }

    @Override
    public void reportBug(final String givenMessage) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                StringBuilder sb = new StringBuilder();
                sb.append("MESSAGE READS:\r\n");
                sb.append(givenMessage);
                sb.append("\r\nEND OF MESSAGE");
                sb.append("\r\n\r\n");
                sb.append("Selected result:");
                SimulationResult curResult = getModel().getCurrentResult();
                if (curResult == null) {
                    sb.append(" No result selected.");
                } else {
                    sb.append("\r\n");
                    sb.append(curResult.toString());
                }
                sb.append("\r\n\r\n");
                sb.append("Current results:");
                Collection<SimulationResult> results = getModel().getResults();
                if (results == null) {
                    sb.append("No results.");
                } else {
                    for (SimulationResult result : results) {
                        if (result != null) {
                            sb.append("\r\n");
                            sb.append(result.toString());
                        }
                    }
                }
                sb.append("\r\n\r\n");
                sb.append("Current running windows:");
                List<String> info = new ArrayList<String>();
                info.add(getFrame().toString());
                Collection<JDialog> serviceDialogs = BaseServiceManager.getAllDialogs();
                for (JDialog d : serviceDialogs) {
                    info.add(d.toString());
                }
                for (String s : info) {
                    sb.append("\r\n\r\n");
                    sb.append(s);
                }
                getModel().saveAllData();
                getModel().reportBug(sb.toString());
            }
        });
    }

    @Override
    public void repaint() {
        if (SwingUtilities.isEventDispatchThread()) {
            setChanged();
            notifyObservers();
            getFrame().repaint();
        } else {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    setChanged();
                    notifyObservers();
                    getFrame().repaint();
                }
            });
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.update.UpdateServiceUser#getCurrentVersion()
     */
    @Override
    public String getCurrentVersion() {
        return ShuffleVersion.VERSION_FULL;
    }

    public void setMegaActive(boolean active) {
        if (getModel().setMegaActive(active)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    /**
     * 
     */
    @Override
    public void toggleActiveMega() {
        Team team = getCurrentTeam();
        String megaSlotName = team.getMegaSlotName();
        if (megaSlotName != null) {
            boolean wasActive = getModel().isMegaSlotActive();
            setMegaActive(!wasActive);
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.data.simulation.SimulationUser#getSpeciesManager()
     */
    @Override
    public SpeciesManager getSpeciesManager() {
        return getModel().getSpeciesManager();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.StageIndicatorUser#getAllStages()
     */
    @Override
    public Collection<Stage> getAllStages() {
        return getModel().getStageManager().getAllStages();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.config.provider.RosterManagerProvider#getRosterManager()
     */
    @Override
    public RosterManager getRosterManager() {
        return getModel().getRosterManager();
    }

    /*
     * (non-Javadoc)
     * @see
     * shuffle.fwk.service.roster.EditRosterServiceUser#loadFromRosterManager(shuffle.fwk.config.
     * manager.RosterManager)
     */
    @Override
    public void loadFromRosterManager(RosterManager manager) {
        RosterManager curManager = getModel().getRosterManager();
        if (curManager.copyFromManager(manager)) {
            LOG.info(getString(KEY_ROSTER_CHANGED));
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.config.provider.TeamManagerProvider#getTeamManager()
     */
    @Override
    public TeamManager getTeamManager() {
        return getModel().getTeamManager();
    }

    @Override
    public EffectManager getEffectManager() {
        return getModel().getEffectManager();
    }

    @Override
    public GradingModeManager getGradingModeManager() {
        return getModel().getGradingModeManager();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.config.provider.BoardManagerProvider#getBoardManager()
     */
    @Override
    public BoardManager getBoardManager() {
        return getModel().getBoardManager();
    }

    @Override
    public EntryModeManager getEntryModeManager() {
        return getModel().getEntryModeManager();
    }

    @Override
    public EntryMode getCurrentEntryMode() {
        return getEntryModeManager().getCurrentEntryMode();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.EntryModeUser#getCurrentCursor()
     */
    @Override
    public List<Integer> getCurrentCursor() {
        return getModel().getCurrentCursor();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.EntryModeUser#getCurrentSpeciesPaint()
     */
    @Override
    public SpeciesPaint getCurrentSpeciesPaint() {
        return getModel().getCurrentSpeciesPaint();
    }

    @Override
    public Collection<Species> getCurrentSpecies() {
        return getModel().getCurrentSpecies();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.EntryModeUser#getPreviousCursor()
     */
    @Override
    public List<Integer> getPreviousCursor() {
        return getModel().getPreviousCursor();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.data.simulation.util.SimulationResultProvider#getResult()
     */
    @Override
    public SimulationResult getSelectedResult() {
        return getModel().getCurrentResult();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.data.simulation.util.SimulationResultProvider#getResults()
     */
    @Override
    public Collection<SimulationResult> getResults() {
        return getModel().getResults();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.GridPanelUser#getPaintAt(java.lang.Integer, java.lang.Integer)
     */
    @Override
    public SpeciesPaint getPaintAt(Integer row, Integer col) {
        return getModel().getPaintAt(row, col);
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.textdisplay.TextDisplayServiceUser#getPathManager()
     */
    @Override
    public ConfigManager getPathManager() {
        return getConfigFactory().getPathManager();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.textdisplay.TextDisplayServiceUser#getPreferencesManager()
     */
    @Override
    public ConfigManager getPreferencesManager() {
        return getConfigFactory().getPreferencesManager();
    }

    /*
     * (non-Javadoc)
     * @see
     * shuffle.fwk.service.teams.EditTeamServiceUser#loadFromTeamManager(shuffle.fwk.config.manager
     * .TeamManager)
     */
    @Override
    public void loadFromTeamManager(TeamManager manager) {
        TeamManager curManager = getModel().getTeamManager();
        if (curManager.copyFromManager(manager)) {
            LOG.info(getString(KEY_TEAM_CHANGED));
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * shuffle.fwk.service.editspecies.EditSpeciesServiceUser#loadSpeciesManagerFrom(shuffle.fwk.
     * config.manager.SpeciesManager)
     */
    @Override
    public void loadSpeciesManagerFrom(SpeciesManager manager) {
        SpeciesManager curManager = getModel().getSpeciesManager();
        if (curManager.copyFromManager(manager)) {
            LOG.info(getString(KEY_SPECIES_CHANGED));
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * shuffle.fwk.service.editspecies.EditSpeciesServiceUser#loadImageManagerFrom(shuffle.fwk.config
     * .manager.ImageManager)
     */
    @Override
    public void loadImageManagerFrom(ImageManager manager) {
        ImageManager curManager = getView().getImageManager();
        if (curManager.copyFromManager(manager)) {
            LOG.info(getString(KEY_IMAGES_CHANGED));
            curManager.reloadIcons();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.GradingModeUser#getCurrentGradingMode()
     */
    @Override
    public GradingMode getCurrentGradingMode() {
        return getModel().getCurrentGradingMode();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.GradingModeUser#setGradingMode(shuffle.fwk.GradingMode)
     */
    @Override
    public void setGradingMode(GradingMode mode) {
        if (getModel().setGradingMode(mode)) {
            LOG.info(getString(KEY_GRADING_CHANGED));
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see
     * shuffle.fwk.service.movechooser.MoveChooserServiceUser#setSelectedResult(shuffle.fwk.data.
     * simulation.SimulationResult)
     */
    @Override
    public void setSelectedResult(SimulationResult result) {
        if (getModel().setSelectedResult(result)) {
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.ShuffleMenuUser#setLocaleTo(java.util.Locale)
     */
    @Override
    public void setLocaleTo(Locale loc) {
        if (getModel().setLocaleTo(loc)) {
            repaint();
            getFrame().updateMinimumSize();
            getFrame().pack();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#setTeamForStage(shuffle.fwk.data.Team,
     * shuffle.fwk.data.Stage)
     */
    @Override
    public void setTeamForStage(Team team, Stage stage) {
        if (getModel().getTeamManager().setTeamForStage(team, stage)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#setFrozenState(boolean)
     */
    @Override
    public void setPaintsFrozen(boolean selected) {
        boolean prev = getModel().arePaintsFrozen();
        if (prev != selected && getModel().toggleFrozenPaints()) {
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#getFrozenState()
     */
    @Override
    public boolean getFrozenState() {
        return getModel().arePaintsFrozen();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.data.simulation.SimulationUser#getRemainingMoves()
     */
    @Override
    public int getRemainingMoves() {
        return getModel().getRemainingMoves();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.data.simulation.SimulationUser#getRemainingHealth()
     */
    @Override
    public int getRemainingHealth() {
        return getModel().getRemainingHealth();
    }

    @Override
    public void setCurrentScore(int score) {
        if (getModel().setCurrentScore(score)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    @Override
    public int getCurrentScore() {
        return getModel().getCurrentScore();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#setRemainingMoves(int)
     */
    @Override
    public void setRemainingMoves(int moves) {
        if (getModel().setRemainingMoves(moves)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.movepreferences.MovePreferencesServiceUser#getDisabledEffects()
     */
    @Override
    public Collection<Effect> getDisabledEffects() {
        return getModel().getDisabledEffects();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.movepreferences.MovePreferencesServiceUser#getAttackPowerUp()
     */
    @Override
    public boolean getAttackPowerUp() {
        return getModel().getAttackPowerUp();
    }

    @Override
    public int getEffectThreshold() {
        return getModel().getEffectThreshold();
    }

    /*
     * (non-Javadoc)
     * @see
     * shuffle.fwk.service.movepreferences.MovePreferencesServiceUser#applyMovePreferences(shuffle
     * .fwk.service.movepreferences.MovePreferencesService)
     */
    @Override
    public void applyMovePreferences(MovePreferencesService service) {
        int numFeeders = service.getNumFeeders();
        int feederHeight = service.getFeederHeight();
        boolean autoCompute = service.isAutoCompute();
        boolean swapToPaint = service.isSwapToPaint();
        Collection<Effect> disabledEffects = service.getDisabledEffects();
        int threshold = service.getThreshold();
        boolean mobileMode = service.isMobileMode();
        boolean expressMetal = service.isExpressMetalAdvanceEnabled();
        boolean extendedMetal = service.isExtendedMetalEnabled();

        boolean changed = false;
        // These DO affect simulation results.
        changed |= getModel().setFeederPreferences(numFeeders, feederHeight, autoCompute);
        changed |= getModel().setDisabledEffects(disabledEffects);
        changed |= getModel().setEffectThreshold(threshold);
        changed |= getModel().setMobileMode(mobileMode);
        boolean teamChanged = getModel().setMetalExtended(extendedMetal);

        if (changed) {
            getModel().setDataChanged();
        }
        if (changed || teamChanged) {
            repaint();
        }
        // This doesn't affect simulation results.
        getModel().setSwapToPaint(swapToPaint);
        getModel().setExpressMetalAdvanceEnabled(expressMetal);
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#setAttackPowerUp(boolean)
     */
    @Override
    public void setAttackPowerUp(boolean enabled) {
        if (getModel().setAttackPowerUp(enabled)) {
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.ShuffleMenuUser#fillGrid()
     */
    @Override
    public void fillGrid() {
        if (getModel().fillGrid()) {
            getModel().setDataChanged();
            repaint();
        }
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.saveprompt.SavePromptServiceUser#shouldPromptSave()
     */
    @Override
    public boolean shouldPromptSave() {
        return factory.isDataChanged();
    }

    @Override
    public boolean isSwapToPaint() {
        return getModel().isSwapToPaint();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.movepreferences.MovePreferencesServiceUser#isMobileMode()
     */
    @Override
    public boolean isMobileMode() {
        return factory.isMobileMode();
    }

    /*
     * (non-Javadoc)
     * @see
     * shuffle.fwk.service.movepreferences.MovePreferencesServiceUser#isExpressMetalAdvanceEnabled()
     */
    @Override
    public boolean isExpressMetalAdvanceEnabled() {
        return getModel().isExpressMetalAdvanceEnabled();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.teams.EditTeamServiceUser#isMetalExtended()
     */
    @Override
    public boolean isExtendedMetalEnabled() {
        return getModel().isExtendedMetalEnabled();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.teams.EditTeamServiceUser#isSurvival()
     */
    @Override
    public boolean isSurvival() {
        return getModel().isSurvivalMode();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.service.teams.EditTeamServiceUser#setSurvival(boolean)
     */
    @Override
    public boolean setSurvival(boolean enabled) {
        boolean changed = getModel().setSurvivalMode(enabled);
        if (changed) {
            getModel().setDataChanged();
            repaint();
        }
        return changed;
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.ShuffleViewUser#getCurrentTeam()
     */
    @Override
    public Team getCurrentTeam() {
        return getModel().getCurrentTeam();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#getStatus()
     */
    @Override
    public Status getStatus() {
        return getModel().getStatus();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#getStatusDuration()
     */
    @Override
    public int getStatusDuration() {
        return getModel().getStatusDuration();
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#setStatus(shuffle.fwk.data.Board.Status)
     */
    @Override
    public boolean setStatus(Status status) {
        boolean changed = getModel().setStatus(status);
        if (changed) {
            getModel().setDataChanged();
            repaint();
        }
        return changed;
    }

    /*
     * (non-Javadoc)
     * @see shuffle.fwk.gui.user.PaintsIndicatorUser#setStatusDuration(int)
     */
    @Override
    public boolean setStatusDuration(int duration) {
        boolean changed = getModel().setStatusDuration(duration);
        if (changed) {
            getModel().setDataChanged();
            repaint();
        }
        return changed;
    }

}