// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.resource.are.viewer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Hashtable;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SpinnerNumberModel;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.infinity.gui.ViewerUtil;
import org.infinity.icon.Icons;
import org.infinity.resource.are.viewer.Settings;
import org.infinity.util.SimpleListModel;
/**
* A settings dialog for the area viewer.
*/
public class SettingsDialog extends JDialog
implements ActionListener, ListSelectionListener
{
private static final String[] AnimationFrames = {"Never", "On mouse-over only", "Always"};
private static final String[] QualityItems = {"Choose optimal filtering automatically",
"Always use nearest neighbor filtering",
"Always use bilinear filtering"};
private static final String[] LayerDesc = {"Actors", "Regions", "Entrances", "Containers",
"Ambient Sounds", "Ambient Sound Ranges",
"Doors", "Background Animations", "Automap Notes",
"Spawn Points", "Map Transitions", "Projectile Traps",
"Door Polygons", "Wall Polygons" };
private SimpleListModel<LayerEntry> modelLayers;
private JList<LayerEntry> listLayers;
private JButton bUp, bDown, bDefaultOrder;
private JComboBox<String> cbFrames, cbQualityMap, cbQualityAnim;
private JCheckBox cbOverrideAnimVisibility, cbStoreSettings;
private JButton bDefaultSettings, bCancel, bOK;
private JSpinner sOverlaysFps, sAnimationsFps;
private JSlider sMiniMapAlpha;
private boolean settingsChanged;
public SettingsDialog(Window owner)
{
super(owner, "Area viewer settings", Dialog.ModalityType.DOCUMENT_MODAL);
init();
}
public boolean settingsChanged()
{
return settingsChanged;
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bUp) {
layerUp();
} else if (event.getSource() == bDown) {
layerDown();
} else if (event.getSource() == bDefaultOrder) {
resetLayerOrder();
} else if (event.getSource() == bDefaultSettings) {
resetDialogSettings();
} else if (event.getSource() == bCancel) {
setVisible(false);
} else if (event.getSource() == bOK) {
updateSettings();
setVisible(false);
}
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface ListSelectionListener ---------------------
@Override
public void valueChanged(ListSelectionEvent event)
{
if (event.getSource() == listLayers) {
updateLayerButtons();
}
}
// --------------------- End Interface ListSelectionListener ---------------------
// Applies dialog settings to the global Settings entries
private void updateSettings()
{
for (int i = 0; i < modelLayers.size(); i++) {
LayerEntry lt = modelLayers.get(i);
Settings.ListLayerOrder.set(i, lt.layer);
}
Settings.ShowFrame = cbFrames.getSelectedIndex();
Settings.OverrideAnimVisibility = cbOverrideAnimVisibility.isSelected();
Settings.InterpolationMap = cbQualityMap.getSelectedIndex();
Settings.InterpolationAnim = cbQualityAnim.getSelectedIndex();
Settings.FrameRateOverlays = (Double)sOverlaysFps.getValue();
Settings.FrameRateAnimations = (Double)sAnimationsFps.getValue();
Settings.MiniMapAlpha = (double)sMiniMapAlpha.getValue() / 100.0;
Settings.StoreVisualSettings = cbStoreSettings.isSelected();
settingsChanged = true;
}
// Resets layer order
private void resetLayerOrder()
{
// filling list with layer entries
int curIndex = listLayers.getSelectedIndex();
modelLayers.clear();
List<ViewerConstants.LayerStackingType> list = Settings.getDefaultLayerOrder();
for (int i = 0; i < list.size(); i++) {
ViewerConstants.LayerStackingType layer = list.get(i);
String desc = LayerDesc[Settings.getLayerStackingTypeIndex(layer)];
modelLayers.addElement(new LayerEntry(layer, desc));
}
list.clear();
list = null;
listLayers.setSelectedIndex(curIndex);
updateLayerButtons();
}
// Re-initializes dialog controls with global settings
private void resetDialogSettings()
{
resetLayerOrder();
cbFrames.setSelectedIndex(Settings.getDefaultShowFrame());
cbOverrideAnimVisibility.setSelected(Settings.getDefaultOverrideAnimVisibility());
cbQualityMap.setSelectedIndex(Settings.getDefaultInterpolationMap());
cbQualityAnim.setSelectedIndex(Settings.getDefaultInterpolationAnim());
sOverlaysFps.setValue(Double.valueOf(Settings.getDefaultFrameRateOverlays()));
sAnimationsFps.setValue(Double.valueOf(Settings.getDefaultFrameRateAnimations()));
sMiniMapAlpha.setValue((int)(Settings.getDefaultMiniMapAlpha()*100.0));
cbStoreSettings.setSelected(Settings.getDefaultStoreVisualSettings());
}
private void layerUp()
{
int idx = listLayers.getSelectedIndex();
if (idx > 0) {
LayerEntry lt = modelLayers.get(idx);
modelLayers.set(idx, modelLayers.get(idx-1));
modelLayers.set(idx-1, lt);
listLayers.setSelectedIndex(idx - 1);
updateLayerButtons();
}
}
private void layerDown()
{
int idx = listLayers.getSelectedIndex();
if (idx < modelLayers.size() - 1) {
LayerEntry lt = modelLayers.get(idx);
modelLayers.set(idx, modelLayers.get(idx+1));
modelLayers.set(idx+1, lt);
listLayers.setSelectedIndex(idx+1);
updateLayerButtons();
}
}
private void updateLayerButtons()
{
bUp.setEnabled(listLayers.getSelectedIndex() > 0);
bDown.setEnabled(listLayers.getSelectedIndex() < modelLayers.size() - 1);
}
private void init()
{
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
GridBagConstraints c = new GridBagConstraints();
settingsChanged = false;
// Initializing layer items order
modelLayers = new SimpleListModel<LayerEntry>();
listLayers = new JList<>(modelLayers);
listLayers.setCellRenderer(new IndexedCellRenderer(1));
listLayers.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
listLayers.setBorder(BorderFactory.createLineBorder(getForeground()));
// filling list with layer entries
for (int i = 0; i < Settings.ListLayerOrder.size(); i++) {
ViewerConstants.LayerStackingType layer = Settings.ListLayerOrder.get(i);
String desc = LayerDesc[Settings.getLayerStackingTypeIndex(layer)];
modelLayers.addElement(new LayerEntry(layer, desc));
}
Dimension d = listLayers.getPreferredSize();
listLayers.setPreferredSize(new Dimension(d.width + 40, d.height + 4));
listLayers.setSelectedIndex(0);
listLayers.addListSelectionListener(this);
bDefaultOrder = new JButton("Default stacking order");
bDefaultOrder.addActionListener(this);
JTextArea taOrderNote = new JTextArea();
taOrderNote.setEditable(false);
taOrderNote.setWrapStyleWord(true);
taOrderNote.setLineWrap(true);
Font fnt = UIManager.getFont("Label.font");
Color bg = UIManager.getColor("Label.background");
taOrderNote.setFont(fnt);
taOrderNote.setBackground(bg);
taOrderNote.setSelectionColor(bg);
taOrderNote.setSelectedTextColor(bg);
taOrderNote.setText("Note: Layers of higher priority are drawn on top of layers of lower priority.");
JPanel pLayersArrows = new JPanel(new GridBagLayout());
bUp = new JButton(Icons.getIcon(Icons.ICON_UP_16));
bUp.setMargin(new Insets(16, 2, 16, 2));
bUp.addActionListener(this);
bUp.setEnabled(listLayers.getSelectedIndex() > 0);
bDown = new JButton(Icons.getIcon(Icons.ICON_DOWN_16));
bDown.setMargin(new Insets(16, 2, 16, 2));
bDown.addActionListener(this);
updateLayerButtons();
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);
pLayersArrows.add(bUp, c);
c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.NONE, new Insets(8, 0, 0, 0), 0, 0);
pLayersArrows.add(bDown, c);
JPanel pLayers = new JPanel(new GridBagLayout());
pLayers.setBorder(BorderFactory.createTitledBorder("Stacking order of layered items: "));
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(4, 4, 0, 0), 0, 0);
pLayers.add(listLayers, c);
c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.VERTICAL, new Insets(4, 4, 0, 4), 0, 0);
pLayers.add(pLayersArrows, c);
c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 0), 0, 0);
pLayers.add(bDefaultOrder, c);
c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.NONE, new Insets(4, 4, 0, 0), 0, 0);
pLayers.add(new JPanel(), c);
c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.BOTH, new Insets(4, 4, 4, 0), 0, 0);
pLayers.add(taOrderNote, c);
c = ViewerUtil.setGBC(c, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.NONE, new Insets(4, 4, 4, 0), 0, 0);
pLayers.add(new JPanel(), c);
// Initializing options
// Background animation frame
JPanel pShowFrame = new JPanel(new GridBagLayout());
pShowFrame.setBorder(BorderFactory.createTitledBorder("Background animations: "));
JLabel lFrames = new JLabel("Show frame:");
cbFrames = new JComboBox<>(AnimationFrames);
cbFrames.setSelectedIndex(Settings.ShowFrame);
cbOverrideAnimVisibility = new JCheckBox("Show animations regardless of their active state", Settings.OverrideAnimVisibility);
cbOverrideAnimVisibility.setToolTipText("Requires a restart of the area viewer or a map update via toolbar button.");
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(4, 4, 4, 0), 0, 0);
pShowFrame.add(lFrames, c);
c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 4), 0, 0);
pShowFrame.add(cbFrames, c);
c = ViewerUtil.setGBC(c, 0, 1, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(4, 4, 4, 4), 0, 0);
pShowFrame.add(cbOverrideAnimVisibility, c);
// Interpolation type
JPanel pQuality = new JPanel(new GridBagLayout());
pQuality.setBorder(BorderFactory.createTitledBorder("Visual quality: "));
JLabel lQualityMap = new JLabel("Map tilesets:");
cbQualityMap = new JComboBox<>(QualityItems);
cbQualityMap.setSelectedIndex(Settings.InterpolationMap);
JLabel lQualityAnim = new JLabel("Animations:");
cbQualityAnim = new JComboBox<>(QualityItems);
cbQualityAnim.setSelectedIndex(Settings.InterpolationAnim);
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(4, 4, 0, 0), 0, 0);
pQuality.add(lQualityMap, c);
c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 4), 0, 0);
pQuality.add(cbQualityMap, c);
c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(4, 4, 4, 0), 0, 0);
pQuality.add(lQualityAnim, c);
c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0);
pQuality.add(cbQualityAnim, c);
// Frame rates
JPanel pFrameRates = new JPanel(new GridBagLayout());
pFrameRates.setBorder(BorderFactory.createTitledBorder("Frame rates: "));
JTextArea taFrameRatesNote = new JTextArea();
taFrameRatesNote.setEditable(false);
taFrameRatesNote.setWrapStyleWord(true);
taFrameRatesNote.setLineWrap(true);
taFrameRatesNote.setFont(fnt);
taFrameRatesNote.setBackground(bg);
taFrameRatesNote.setSelectionColor(bg);
taFrameRatesNote.setSelectedTextColor(bg);
taFrameRatesNote.setText("Caution: The area viewer may become less responsive on higher frame rates.");
sOverlaysFps = new JSpinner(new SpinnerNumberModel(Settings.FrameRateOverlays, 1.0, 30.0, 0.5));
d = sOverlaysFps.getPreferredSize();
sOverlaysFps.setPreferredSize(new Dimension(d.width + 16, d.height));
sAnimationsFps = new JSpinner(new SpinnerNumberModel(Settings.FrameRateAnimations, 1.0, 30.0, 0.5));
d = sAnimationsFps.getPreferredSize();
sAnimationsFps.setPreferredSize(new Dimension(d.width + 16, d.height));
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 4, 4, 0), 0, 0);
pFrameRates.add(new JLabel("Overlays:"), c);
c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 8, 4, 0), 0, 0);
pFrameRates.add(sOverlaysFps, c);
c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 4, 4, 0), 0, 0);
pFrameRates.add(new JLabel("fps"), c);
c = ViewerUtil.setGBC(c, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 16, 4, 0), 0, 0);
pFrameRates.add(new JLabel("Animations:"), c);
c = ViewerUtil.setGBC(c, 4, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 8, 4, 0), 0, 0);
pFrameRates.add(sAnimationsFps, c);
c = ViewerUtil.setGBC(c, 5, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 4, 4, 4), 0, 0);
pFrameRates.add(new JLabel("fps"), c);
c = ViewerUtil.setGBC(c, 0, 1, 6, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0);
pFrameRates.add(taFrameRatesNote, c);
// Minimap transparency
JPanel pMiniMap = new JPanel(new GridBagLayout());
pMiniMap.setBorder(BorderFactory.createTitledBorder("Mini map opacity: "));
Hashtable<Integer, JLabel> table = new Hashtable<Integer, JLabel>();
for (int i = 0; i <= 100; i+=25) {
table.put(Integer.valueOf(i), new JLabel(String.format("%1$d%%", i)));
}
sMiniMapAlpha = new JSlider(0, 100, (int)(Settings.MiniMapAlpha*100.0));
sMiniMapAlpha.setSnapToTicks(false);
sMiniMapAlpha.setLabelTable(table);
sMiniMapAlpha.setPaintLabels(true);
sMiniMapAlpha.setMinorTickSpacing(5);
sMiniMapAlpha.setMajorTickSpacing(25);
sMiniMapAlpha.setPaintTicks(true);
sMiniMapAlpha.setPaintTrack(true);
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 4, 4, 4), 0, 0);
pMiniMap.add(sMiniMapAlpha, c);
// Misc. settings
JPanel pMisc = new JPanel(new GridBagLayout());
pMisc.setBorder(BorderFactory.createTitledBorder("Misc. settings: "));
cbStoreSettings = new JCheckBox("Remember all visual settings");
cbStoreSettings.setSelected(Settings.StoreVisualSettings);
c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(4, 4, 4, 4), 0, 0);
pMisc.add(cbStoreSettings, c);
// initializing dialog buttons
JPanel pButtons = new JPanel(new GridBagLayout());
bDefaultSettings = new JButton("Set defaults");
bDefaultSettings.addActionListener(this);
bCancel = new JButton("Cancel");
bCancel.addActionListener(this);
bOK = new JButton("OK");
bOK.setPreferredSize(bCancel.getPreferredSize());
bOK.addActionListener(this);
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0);
pButtons.add(bDefaultSettings, c);
c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0);
pButtons.add(bOK, c);
c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.NONE, new Insets(0, 4, 0, 0), 0, 0);
pButtons.add(bCancel, c);
// putting options together
JPanel pOptions = new JPanel(new GridBagLayout());
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
pOptions.add(pShowFrame, c);
c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0);
pOptions.add(pQuality, c);
c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0);
pOptions.add(pFrameRates, c);
c = ViewerUtil.setGBC(c, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0);
pOptions.add(pMiniMap, c);
c = ViewerUtil.setGBC(c, 0, 4, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(4, 0, 0, 0), 0, 0);
pOptions.add(pMisc, c);
c = ViewerUtil.setGBC(c, 0, 5, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START,
GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0);
pOptions.add(new JPanel(), c);
c = ViewerUtil.setGBC(c, 0, 6, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
pOptions.add(pButtons, c);
// putting all together
JPanel pMain = new JPanel(new GridBagLayout());
c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START,
GridBagConstraints.BOTH, new Insets(8, 8, 8, 0), 0, 0);
pMain.add(pLayers, c);
c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START,
GridBagConstraints.BOTH, new Insets(8, 8, 8, 8), 0, 0);
pMain.add(pOptions, c);
getContentPane().add(pMain, BorderLayout.CENTER);
pack();
setMinimumSize(getPreferredSize());
setLocationRelativeTo(getOwner());
// Registering close on ESC key
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), getRootPane());
getRootPane().getActionMap().put(getRootPane(), new AbstractAction() {
@Override
public void actionPerformed(ActionEvent event)
{
setVisible(false);
}
});
getRootPane().setDefaultButton(bOK);
setVisible(true);
}
//----------------------------- INNER CLASSES -----------------------------
private static class IndexedCellRenderer extends DefaultListCellRenderer
{
private int startIndex;
public IndexedCellRenderer(int startIndex)
{
super();
this.startIndex = startIndex;
}
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean isSelected, boolean cellHasFocus)
{
String template = "%1$0" +
String.format("%1$d", Integer.toString(list.getModel().getSize()).length()) +
"d - %2$s";
return super.getListCellRendererComponent(list, String.format(template, index + startIndex, value),
index, isSelected, cellHasFocus);
}
}
// A simple wrapper for the list control to link layers with their respective description
private static class LayerEntry
{
public ViewerConstants.LayerStackingType layer;
public String desc;
public LayerEntry(ViewerConstants.LayerStackingType layer, String description)
{
this.layer = layer;
this.desc = description;
}
@Override
public String toString()
{
return desc;
}
}
}