// 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.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Window; 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.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.image.BufferedImage; import java.awt.image.VolatileImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.ByteArrayOutputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.EventObject; import java.util.Hashtable; import java.util.List; import java.util.Locale; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButton; import javax.swing.JScrollPane; import javax.swing.JSlider; import javax.swing.JTextArea; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.ProgressMonitor; import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.Timer; import javax.swing.ToolTipManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellEditor; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.infinity.NearInfinity; import org.infinity.datatype.Flag; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionOffset; import org.infinity.gui.ButtonPopupWindow; import org.infinity.gui.Center; import org.infinity.gui.ChildFrame; import org.infinity.gui.DataMenuItem; import org.infinity.gui.RenderCanvas; import org.infinity.gui.StructViewer; import org.infinity.gui.ViewFrame; import org.infinity.gui.ViewerUtil; import org.infinity.gui.WindowBlocker; import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.gui.layeritem.LayerItemEvent; import org.infinity.gui.layeritem.LayerItemListener; import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Profile; import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; import org.infinity.resource.are.AreResource; import org.infinity.resource.are.RestSpawn; import org.infinity.resource.are.Song; import org.infinity.resource.are.viewer.ViewerConstants.LayerStackingType; import org.infinity.resource.are.viewer.ViewerConstants.LayerType; import org.infinity.resource.are.viewer.icon.ViewerIcons; import org.infinity.resource.graphics.GraphicsResource; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.key.BIFFResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.wed.Overlay; import org.infinity.resource.wed.WedResource; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; /** * The Area Viewer shows a selected map with its associated structures, such as actors, regions or * animations. */ public class AreaViewer extends ChildFrame { private static final String LabelInfoX = "Position X:"; private static final String LabelInfoY = "Position Y:"; private static final String LabelEnableSchedule = "Enable time schedules"; private static final String LabelDrawClosed = "Draw closed"; private static final String LabelDrawOverlays = "Enable overlays"; private static final String LabelAnimateOverlays = "Animate overlays"; private static final String LabelDrawGrid = "Show grid"; private final Listeners listeners; private final Map map; private final Point mapCoordinates = new Point(); private final String windowTitle; private final JCheckBox[] cbLayers = new JCheckBox[LayerManager.getLayerTypeCount()];; private final JCheckBox[] cbLayerRealAnimation = new JCheckBox[2]; private final JCheckBox[] cbMiniMaps = new JCheckBox[3]; private final JToggleButton[] tbAddLayerItem = new JToggleButton[LayerManager.getLayerTypeCount()]; private LayerManager layerManager; private TilesetRenderer rcCanvas; private JPanel pCanvas; private JScrollPane spCanvas; private Rectangle vpCenterExtent; // combines map center and viewport extent in one structure private JToolBar toolBar; private JToggleButton tbView, tbEdit; private JButton tbAre, tbWed, tbSongs, tbRest, tbSettings, tbRefresh, tbExportPNG; private JTree treeControls; private ButtonPopupWindow bpwDayTime; private DayTimePanel pDayTime; private JCheckBox cbDrawClosed, cbDrawOverlays, cbAnimateOverlays, cbDrawGrid, cbEnableSchedules; private JComboBox<String> cbZoomLevel; private JCheckBox cbLayerAmbientRange; private JLabel lPosX, lPosY; private JTextArea taInfo; private boolean bMapDragging; private Point mapDraggingPosStart, mapDraggingScrollStart, mapDraggingPos; private Timer timerOverlays; private JPopupMenu pmItems; private SwingWorker<Void, Void> workerInitGui, workerLoadMap, workerOverlays; private ProgressMonitor progress; private int pmCur, pmMax; private WindowBlocker blocker; /** * Checks whether the specified ARE resource can be displayed with the area viewer. * @param are The ARE resource to check * @return {@code true} if area is viewable, {@code false} otherwise. */ public static boolean isValid(AreResource are) { if (are != null) { ResourceRef wedRef = (ResourceRef)are.getAttribute(AreResource.ARE_WED_RESOURCE); ResourceEntry wedEntry = ResourceFactory.getResourceEntry(wedRef.getResourceName()); if (wedEntry != null) { try { WedResource wedFile = new WedResource(wedEntry); int ofs = ((SectionOffset)wedFile.getAttribute(WedResource.WED_OFFSET_OVERLAYS)).getValue(); Overlay overlay = (Overlay)wedFile.getAttribute(ofs, false); ResourceRef tisRef = (ResourceRef)overlay.getAttribute(Overlay.WED_OVERLAY_TILESET); ResourceEntry tisEntry = ResourceFactory.getResourceEntry(tisRef.getResourceName()); if (tisEntry != null) return true; } catch (Exception e) { } } } return false; } // Returns the general day time (day/twilight/night) private static int getDayTime() { return ViewerConstants.getDayTime(Settings.TimeOfDay); } // Returns the currently selected day time in hours private static int getHour() { return Settings.TimeOfDay; } public AreaViewer(AreResource are) { this(NearInfinity.getInstance(), are); } public AreaViewer(Component parent, AreResource are) { super(""); windowTitle = String.format("Area Viewer: %1$s", (are != null) ? are.getName() : "[Unknown]"); initProgressMonitor(parent, "Initializing " + are.getName(), "Loading ARE resource...", 3, 0, 0); listeners = new Listeners(); map = new Map(this, are); // loading map in dedicated thread workerInitGui = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { try { init(); } catch (Exception e) { e.printStackTrace(); } return null; } }; workerInitGui.addPropertyChangeListener(getListeners()); workerInitGui.execute(); } //--------------------- Begin Class ChildFrame --------------------- @Override public void close() { Settings.storeSettings(false); if (!map.closeWed(ViewerConstants.AREA_DAY, true)) { return; } if (!map.closeWed(ViewerConstants.AREA_NIGHT, true)) { return; } if (map != null) { map.clear(); } if (rcCanvas != null) { removeLayerItems(); rcCanvas.clear(); rcCanvas.setImage(null); } if (layerManager != null) { layerManager.close(); layerManager = null; } dispose(); System.gc(); super.close(); } //--------------------- End Class ChildFrame --------------------- /** * Returns the tileset renderer for this viewer instance. * @return The currently used TilesetRenderer instance. */ public TilesetRenderer getRenderer() { return rcCanvas; } /** Returns the instance which handles all listeners of the area viewer. */ Listeners getListeners() { return listeners; } // initialize GUI and structures private void init() { advanceProgressMonitor("Initializing GUI..."); GridBagConstraints c = new GridBagConstraints(); JPanel p; // initialize misc. features pmItems = new JPopupMenu("Select item:"); bMapDragging = false; mapDraggingPosStart = new Point(); mapDraggingPos = new Point(); mapDraggingScrollStart = new Point(); // Creating main view area pCanvas = new JPanel(new GridBagLayout()); rcCanvas = new TilesetRenderer(); rcCanvas.addComponentListener(getListeners()); rcCanvas.addMouseListener(getListeners()); rcCanvas.addMouseMotionListener(getListeners()); rcCanvas.addChangeListener(getListeners()); rcCanvas.setHorizontalAlignment(RenderCanvas.CENTER); rcCanvas.setVerticalAlignment(RenderCanvas.CENTER); rcCanvas.setLocation(0, 0); rcCanvas.setLayout(null); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pCanvas.add(rcCanvas, c); spCanvas = new JScrollPane(pCanvas); spCanvas.addComponentListener(getListeners()); spCanvas.getViewport().addChangeListener(getListeners()); spCanvas.getVerticalScrollBar().setUnitIncrement(16); spCanvas.getHorizontalScrollBar().setUnitIncrement(16); JPanel pView = new JPanel(new BorderLayout()); pView.add(spCanvas, BorderLayout.CENTER); // Creating right side bar JPanel pTree = new JPanel(new GridBagLayout()); pTree.setBorder(BorderFactory.createTitledBorder("Area Viewer Controls: ")); DefaultMutableTreeNode t, t2, t3; DefaultMutableTreeNode top = new DefaultMutableTreeNode(""); // Adding Visual State elements // Note: the string is required for setting the correct size of the button bpwDayTime = new ButtonPopupWindow(String.format(" %1$s ", DayTimePanel.getButtonText(21)), Icons.getIcon(Icons.ICON_ARROW_DOWN_15)); Dimension d = bpwDayTime.getPreferredSize(); bpwDayTime.setIconTextGap(8); pDayTime = new DayTimePanel(bpwDayTime, getHour()); pDayTime.addChangeListener(getListeners()); bpwDayTime.setContent(pDayTime); bpwDayTime.setPreferredSize(d); bpwDayTime.setMargin(new Insets(2, bpwDayTime.getMargin().left, 2, bpwDayTime.getMargin().right)); cbEnableSchedules = new JCheckBox(LabelEnableSchedule); cbEnableSchedules.setToolTipText("Enable activity schedules on layer structures that support them (e.g. actors, ambient sounds or background animations."); cbEnableSchedules.addActionListener(getListeners()); cbDrawClosed = new JCheckBox(LabelDrawClosed); cbDrawClosed.setToolTipText("Draw opened or closed states of doors"); cbDrawClosed.addActionListener(getListeners()); cbDrawGrid = new JCheckBox(LabelDrawGrid); cbDrawGrid.addActionListener(getListeners()); cbDrawOverlays = new JCheckBox(LabelDrawOverlays); cbDrawOverlays.addActionListener(getListeners()); cbAnimateOverlays = new JCheckBox(LabelAnimateOverlays); cbAnimateOverlays.addActionListener(getListeners()); JLabel lZoomLevel = new JLabel("Zoom map:"); cbZoomLevel = new JComboBox<>(Settings.LabelZoomFactor); cbZoomLevel.setSelectedIndex(Settings.ZoomLevel); cbZoomLevel.addActionListener(getListeners()); JPanel pZoom = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); pZoom.add(lZoomLevel, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); pZoom.add(cbZoomLevel, c); JLabel l = new JLabel("Visual State"); l.setFont(new Font(l.getFont().getFontName(), Font.BOLD, l.getFont().getSize()+1)); t = new DefaultMutableTreeNode(l); top.add(t); t.add(new DefaultMutableTreeNode(bpwDayTime)); t.add(new DefaultMutableTreeNode(cbEnableSchedules)); t.add(new DefaultMutableTreeNode(cbDrawClosed)); t.add(new DefaultMutableTreeNode(cbDrawGrid)); t2 = new DefaultMutableTreeNode(cbDrawOverlays); t2.add(new DefaultMutableTreeNode(cbAnimateOverlays)); t.add(t2); t.add(new DefaultMutableTreeNode(pZoom)); // Adding Layer elements l = new JLabel("Layers"); l.setFont(new Font(l.getFont().getFontName(), Font.BOLD, l.getFont().getSize()+1)); t = new DefaultMutableTreeNode(l); top.add(t); for (int i = 0, ltCount = LayerManager.getLayerTypeCount(); i < ltCount; i++) { LayerType layer = LayerManager.getLayerType(i); cbLayers[i] = new JCheckBox(LayerManager.getLayerTypeLabel(layer)); cbLayers[i].addActionListener(getListeners()); t2 = new DefaultMutableTreeNode(cbLayers[i]); t.add(t2); if (i == LayerManager.getLayerTypeIndex(LayerType.AMBIENT)) { // Initializing ambient sound range checkbox cbLayerAmbientRange = new JCheckBox("Show local sound ranges"); cbLayerAmbientRange.addActionListener(getListeners()); t3 = new DefaultMutableTreeNode(cbLayerAmbientRange); t2.add(t3); } else if (i == LayerManager.getLayerTypeIndex(LayerType.ANIMATION)) { // Initializing real animation checkboxes cbLayerRealAnimation[0] = new JCheckBox("Show actual animations"); cbLayerRealAnimation[0].addActionListener(getListeners()); t3 = new DefaultMutableTreeNode(cbLayerRealAnimation[0]); t2.add(t3); cbLayerRealAnimation[1] = new JCheckBox("Animate actual animations"); cbLayerRealAnimation[1].addActionListener(getListeners()); t3 = new DefaultMutableTreeNode(cbLayerRealAnimation[1]); t2.add(t3); } } // Adding mini map entries cbMiniMaps[ViewerConstants.MAP_SEARCH] = new JCheckBox("Display search map"); cbMiniMaps[ViewerConstants.MAP_SEARCH].addActionListener(getListeners()); cbMiniMaps[ViewerConstants.MAP_LIGHT] = new JCheckBox("Display light map"); cbMiniMaps[ViewerConstants.MAP_LIGHT].addActionListener(getListeners()); cbMiniMaps[ViewerConstants.MAP_HEIGHT] = new JCheckBox("Display height map"); cbMiniMaps[ViewerConstants.MAP_HEIGHT].addActionListener(getListeners()); l = new JLabel("Mini maps"); l.setFont(new Font(l.getFont().getFontName(), Font.BOLD, l.getFont().getSize()+1)); t = new DefaultMutableTreeNode(l); top.add(t); t.add(new DefaultMutableTreeNode(cbMiniMaps[0])); t.add(new DefaultMutableTreeNode(cbMiniMaps[1])); t.add(new DefaultMutableTreeNode(cbMiniMaps[2])); treeControls = new JTree(new DefaultTreeModel(top)); treeControls.addTreeExpansionListener(getListeners()); treeControls.setBackground(getBackground()); treeControls.setRootVisible(false); treeControls.setShowsRootHandles(true); treeControls.setRowHeight(bpwDayTime.getPreferredSize().height); treeControls.setEditable(true); ComponentTreeCellRenderer renderer = new ComponentTreeCellRenderer(); treeControls.setCellRenderer(renderer); treeControls.setCellEditor(new ComponentTreeCellEditor(treeControls, renderer)); ToolTipManager.sharedInstance().registerComponent(treeControls); for (int i = 0; i < treeControls.getRowCount(); i++) { treeControls.expandRow(i); } treeControls.setMinimumSize(treeControls.getPreferredSize()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 4, 4, 4), 0, 0); pTree.add(treeControls, c); // Creating Info Box area JLabel lPosXLabel = new JLabel(LabelInfoX); JLabel lPosYLabel = new JLabel(LabelInfoY); lPosX = new JLabel("0"); lPosY = new JLabel("0"); taInfo = new JTextArea(4, 15); taInfo.setEditable(false); taInfo.setFont(lPosX.getFont()); taInfo.setBackground(lPosX.getBackground()); taInfo.setSelectionColor(lPosX.getBackground()); taInfo.setSelectedTextColor(lPosX.getBackground()); taInfo.setWrapStyleWord(true); taInfo.setLineWrap(true); p = new JPanel(new GridBagLayout()); 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); p.add(lPosXLabel, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 8, 0, 0), 0, 0); p.add(lPosX, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); p.add(lPosYLabel, c); c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 8, 0, 0), 0, 0); p.add(lPosY, c); c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(4, 0, 0, 0), 0, 0); p.add(taInfo, c); JPanel pInfoBox = new JPanel(new GridBagLayout()); pInfoBox.setBorder(BorderFactory.createTitledBorder("Information: ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 4, 0, 4), 0, 0); pInfoBox.add(p, c); // Assembling right side bar JPanel pSideBar = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 4), 0, 0); pSideBar.add(pTree, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 4), 0, 0); pSideBar.add(pInfoBox, c); p = new JPanel(); p.setPreferredSize(new Dimension(pTree.getPreferredSize().width, p.getMinimumSize().height)); c = ViewerUtil.setGBC(c, 0, 2, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, new Insets(4, 0, 0, 0), 0, 0); pSideBar.add(p, c); // Creating toolbar Dimension dimSeparator = new Dimension(24, 40); toolBar = new JToolBar("Area Viewer Controls", SwingConstants.HORIZONTAL); toolBar.setRollover(true); toolBar.setFloatable(false); tbView = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_VIEW_MODE), true); tbView.setToolTipText("Enter view mode"); tbView.addActionListener(getListeners()); tbView.setEnabled(false); // toolBar.add(tbView); tbEdit = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_EDIT_MODE), false); tbEdit.setToolTipText("Enter edit mode"); tbEdit.addActionListener(getListeners()); tbEdit.setEnabled(false); // toolBar.add(tbEdit); // toolBar.addSeparator(dimSeparator); JToggleButton tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_ACTOR), false); tb.setToolTipText("Add a new actor to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.ACTOR)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_REGION), false); tb.setToolTipText("Add a new region to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.REGION)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_ENTRANCE), false); tb.setToolTipText("Add a new entrance to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.ENTRANCE)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_CONTAINER), false); tb.setToolTipText("Add a new container to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.CONTAINER)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_AMBIENT), false); tb.setToolTipText("Add a new global ambient sound to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.AMBIENT)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_DOOR), false); tb.setToolTipText("Add a new door to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.DOOR)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_ANIM), false); tb.setToolTipText("Add a new background animation to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.ANIMATION)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_AUTOMAP), false); tb.setToolTipText("Add a new automap note to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.AUTOMAP)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_SPAWN_POINT), false); tb.setToolTipText("Add a new spawn point to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.SPAWN_POINT)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_PRO_TRAP), false); tb.setToolTipText("Add a new projectile trap to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.PRO_TRAP)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_DOOR_POLY), false); tb.setToolTipText("Add a new door polygon to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.DOOR_POLY)] = tb; tb = new JToggleButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_ADD_WALL_POLY), false); tb.setToolTipText("Add a new wall polygon to the map"); tb.addActionListener(getListeners()); tb.setEnabled(false); // toolBar.add(tb); tbAddLayerItem[LayerManager.getLayerTypeIndex(LayerType.WALL_POLY)] = tb; // toolBar.addSeparator(dimSeparator); tbAre = new JButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_MAP_ARE)); tbAre.setToolTipText(String.format("Edit ARE structure (%1$s)", map.getAre().getName())); tbAre.addActionListener(getListeners()); toolBar.add(tbAre); tbWed = new JButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_MAP_WED)); tbWed.addActionListener(getListeners()); toolBar.add(tbWed); tbSongs = new JButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_SONGS)); tbSongs.setToolTipText("Edit song entries"); tbSongs.addActionListener(getListeners()); toolBar.add(tbSongs); tbRest = new JButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_REST)); tbRest.setToolTipText("Edit rest encounters"); tbRest.addActionListener(getListeners()); toolBar.add(tbRest); toolBar.addSeparator(dimSeparator); tbSettings = new JButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_SETTINGS)); tbSettings.setToolTipText("Area viewer settings"); tbSettings.addActionListener(getListeners()); toolBar.add(tbSettings); tbRefresh = new JButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_REFRESH)); tbRefresh.setToolTipText("Update map"); tbRefresh.addActionListener(getListeners()); toolBar.add(tbRefresh); toolBar.addSeparator(dimSeparator); tbExportPNG = new JButton(Icons.getIcon(ViewerIcons.class, ViewerIcons.ICON_BTN_EXPORT)); tbExportPNG.setToolTipText("Export current map state as PNG"); tbExportPNG.addActionListener(getListeners()); toolBar.add(tbExportPNG); pView.add(toolBar, BorderLayout.NORTH); updateToolBarButtons(); // Putting all together JPanel pMain = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pMain.add(pView, c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, new Insets(0, 0, 0, 0), 0, 0); pMain.add(pSideBar, c); // setting frame rate for overlay animations to 5 fps (in-game frame rate: 7.5 fps) timerOverlays = new Timer(1000/5, getListeners()); advanceProgressMonitor("Initializing map..."); Container pane = getContentPane(); pane.setLayout(new BorderLayout()); pane.add(pMain, BorderLayout.CENTER); pack(); // setting window size and state setSize(NearInfinity.getInstance().getSize()); Center.center(this, NearInfinity.getInstance().getBounds()); setExtendedState(NearInfinity.getInstance().getExtendedState()); try { initGuiSettings(); } catch (OutOfMemoryError e) { e.printStackTrace(); JOptionPane.showMessageDialog(this, "Not enough memory to load area!", "Error", JOptionPane.ERROR_MESSAGE); throw e; } rcCanvas.requestFocusInWindow(); // put focus on a safe component advanceProgressMonitor("Ready!"); // adding context menu key support for calling the popup menu on map items rcCanvas.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_CONTEXT_MENU, 0), rcCanvas); rcCanvas.getActionMap().put(rcCanvas, new AbstractAction() { @Override public void actionPerformed(ActionEvent event) { Point pt = rcCanvas.getMousePosition(); if (pt != null) { Point pts = rcCanvas.getLocationOnScreen(); pts.x += pt.x; pts.y += pt.y; showItemPopup(new MouseEvent(rcCanvas, MouseEvent.MOUSE_PRESSED, 0, 0, pt.x, pt.y, pts.x, pts.y, 1, true, MouseEvent.BUTTON2)); } } }); updateWindowTitle(); setVisible(true); } // Sets the state of all GUI components and their associated actions private void initGuiSettings() { Settings.loadSettings(false); // expanding main sections in sidebar based on current settings DefaultTreeModel model = (DefaultTreeModel)treeControls.getModel(); DefaultMutableTreeNode root = (DefaultMutableTreeNode)model.getRoot(); for (int i = 0, cCount = root.getChildCount(); i < cCount; i++) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)root.getChildAt(i); int bit = 1 << i; if ((Settings.SidebarControls & bit) != 0) { treeControls.expandPath(new TreePath(node.getPath())); } else { treeControls.collapsePath(new TreePath(node.getPath())); } } // initializing minimap state (needs to be set before the first call to setHour) cbMiniMaps[ViewerConstants.MAP_SEARCH].setSelected(Settings.MiniMap == ViewerConstants.MAP_SEARCH); cbMiniMaps[ViewerConstants.MAP_LIGHT].setSelected(Settings.MiniMap == ViewerConstants.MAP_LIGHT); cbMiniMaps[ViewerConstants.MAP_HEIGHT].setSelected(Settings.MiniMap == ViewerConstants.MAP_HEIGHT); // initializing visual state of the map setHour(Settings.TimeOfDay); // initializing time schedules for layer items cbEnableSchedules.setSelected(Settings.EnableSchedules); // initializing closed state of doors cbDrawClosed.setSelected(Settings.DrawClosed); cbDrawClosed.setEnabled(rcCanvas.hasDoors()); if (rcCanvas.hasDoors()) { setDoorState(Settings.DrawClosed); } // initializing grid cbDrawGrid.setSelected(Settings.DrawGrid); setTileGridEnabled(Settings.DrawGrid); // initializing overlays cbDrawOverlays.setSelected(Settings.DrawOverlays); cbDrawOverlays.setEnabled(rcCanvas.hasOverlays()); cbAnimateOverlays.setEnabled(rcCanvas.hasOverlays()); if (rcCanvas.hasOverlays()) { setOverlaysEnabled(Settings.DrawOverlays); setOverlaysAnimated(cbAnimateOverlays.isSelected()); } // initializing zoom level cbZoomLevel.setSelectedIndex(Settings.ZoomLevel); // initializing layers layerManager = new LayerManager(getCurrentAre(), getCurrentWed(), this); layerManager.setDoorState(Settings.DrawClosed ? ViewerConstants.DOOR_CLOSED : ViewerConstants.DOOR_OPEN); layerManager.setScheduleEnabled(Settings.EnableSchedules); layerManager.setSchedule(LayerManager.toSchedule(getHour())); addLayerItems(); updateScheduledItems(); for (int i = 0, ltCount = LayerManager.getLayerTypeCount(); i < ltCount; i++) { LayerType layer = LayerManager.getLayerType(i); int bit = 1 << i; boolean isChecked = (Settings.LayerFlags & bit) != 0; int count = layerManager.getLayerObjectCount(layer); if (count > 0) { cbLayers[i].setToolTipText(layerManager.getLayerAvailability(layer)); } cbLayers[i].setEnabled(count > 0); cbLayers[i].setSelected(isChecked); updateLayerItems(Settings.layerToStacking(layer)); showLayer(LayerManager.getLayerType(i), cbLayers[i].isSelected()); } // Setting up ambient sound ranges LayerAmbient layerAmbient = (LayerAmbient)layerManager.getLayer(ViewerConstants.LayerType.AMBIENT); if (layerAmbient.getLayerObjectCount(ViewerConstants.AMBIENT_TYPE_LOCAL) > 0) { cbLayerAmbientRange.setToolTipText(layerAmbient.getAvailability(ViewerConstants.AMBIENT_TYPE_LOCAL)); } cbLayerAmbientRange.setSelected(Settings.ShowAmbientRanges); updateAmbientRange(); // initializing background animation display // Disabling animated frames for performance and safety reasons if (Settings.ShowRealAnimations == ViewerConstants.ANIM_SHOW_ANIMATED) { Settings.ShowRealAnimations = ViewerConstants.ANIM_SHOW_STILL; } ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationFrameState(Settings.ShowFrame); cbLayerRealAnimation[0].setSelected(Settings.ShowRealAnimations == ViewerConstants.ANIM_SHOW_STILL); cbLayerRealAnimation[1].setSelected(false); updateRealAnimation(); updateRealAnimationsLighting(getDayTime()); updateWindowTitle(); applySettings(); } // Updates the window title private void updateWindowTitle() { int zoom = (int)(getZoomFactor()*100.0); String dayNight; switch (getVisualState()) { case ViewerConstants.LIGHTING_TWILIGHT: dayNight = "twilight"; break; case ViewerConstants.LIGHTING_NIGHT: dayNight = "night"; break; default: dayNight = "day"; } String scheduleState = Settings.EnableSchedules ? "enabled" : "disabled"; String doorState = isDoorStateClosed() ? "closed" : "open"; String overlayState; if (isOverlaysEnabled() && !isOverlaysAnimated()) { overlayState = "enabled"; } else if (isOverlaysEnabled() && isOverlaysAnimated()) { overlayState = "animated"; } else { overlayState = "disabled"; } String gridState = isTileGridEnabled() ? "enabled" : "disabled"; setTitle(String.format("%1$s (Time: %2$02d:00 (%3$s), Schedules: %4$s, Doors: %5$s, Overlays: %6$s, Grid: %7$s, Zoom: %8$d%%)", windowTitle, getHour(), dayNight, scheduleState, doorState, overlayState, gridState, zoom)); } // Sets day time to a specific hour (0..23). private void setHour(int hour) { while (hour < 0) { hour += 24; } hour %= 24; Settings.TimeOfDay = hour; setVisualState(getHour()); if (layerManager != null) { layerManager.setSchedule(LayerManager.toSchedule(getHour())); } if (pDayTime != null) { pDayTime.setHour(Settings.TimeOfDay); } updateScheduledItems(); } // Returns the currently selected ARE resource private AreResource getCurrentAre() { if (map != null) { return map.getAre(); } else { return null; } } // Returns the currently selected WED resource (day/night) private WedResource getCurrentWed() { if (map != null) { return map.getWed(getCurrentWedIndex()); } return null; } // Returns the currently selected WED resource (day/night) private int getCurrentWedIndex() { if (map != null) { return getDayTime() == ViewerConstants.LIGHTING_NIGHT ? ViewerConstants.AREA_NIGHT : ViewerConstants.AREA_DAY; } else { return ViewerConstants.AREA_DAY; } } // Returns the currently selected visual state (day/twilight/night) private int getVisualState() { return getDayTime(); } // Set the lighting condition of the current map (day/twilight/night) and real background animations private synchronized void setVisualState(int hour) { while (hour < 0) { hour += 24; } hour %= 24; int index = ViewerConstants.getDayTime(hour); if (!map.hasDayNight()) { index = ViewerConstants.LIGHTING_DAY; } switch (index) { case ViewerConstants.LIGHTING_DAY: if (!isProgressMonitorActive() && map.getWed(ViewerConstants.AREA_DAY) != rcCanvas.getWed()) { initProgressMonitor(this, "Loading tileset...", null, 1, 0, 0); } if (!rcCanvas.isMapLoaded() || rcCanvas.getWed() != map.getWed(ViewerConstants.AREA_DAY)) { rcCanvas.loadMap(map.getWed(ViewerConstants.AREA_DAY)); reloadWedLayers(true); } rcCanvas.setLighting(index); break; case ViewerConstants.LIGHTING_TWILIGHT: if (!isProgressMonitorActive() && map.getWed(ViewerConstants.AREA_DAY) != rcCanvas.getWed()) { initProgressMonitor(this, "Loading tileset...", null, 1, 0, 0); } if (!rcCanvas.isMapLoaded() || rcCanvas.getWed() != map.getWed(ViewerConstants.AREA_DAY)) { rcCanvas.loadMap(map.getWed(ViewerConstants.AREA_DAY)); reloadWedLayers(true); } rcCanvas.setLighting(index); break; case ViewerConstants.LIGHTING_NIGHT: if (!isProgressMonitorActive() && map.getWed(ViewerConstants.AREA_NIGHT) != rcCanvas.getWed()) { initProgressMonitor(this, "Loading tileset...", null, 1, 0, 0); } if (!rcCanvas.isMapLoaded() || map.hasExtendedNight()) { if (rcCanvas.getWed() != map.getWed(ViewerConstants.AREA_NIGHT)) { rcCanvas.loadMap(map.getWed(ViewerConstants.AREA_NIGHT)); reloadWedLayers(true); } } if (!map.hasExtendedNight()) { rcCanvas.setLighting(index); } break; } // updating current visual state if (hour != getHour()) { Settings.TimeOfDay = hour; } updateMiniMap(); updateToolBarButtons(); updateRealAnimationsLighting(getDayTime()); updateScheduledItems(); updateWindowTitle(); } // Returns whether map dragging is enabled; updates current and previous mouse positions private boolean isMapDragging(Point mousePos) { if (bMapDragging && mousePos != null && !mapDraggingPos.equals(mousePos)) { mapDraggingPos.x = mousePos.x; mapDraggingPos.y = mousePos.y; } return bMapDragging; } // Enables/disables map dragging mode (set mouse cursor, global state and current mouse position) private void setMapDraggingEnabled(boolean enable, Point mousePos) { if (bMapDragging != enable) { bMapDragging = enable; setCursor(Cursor.getPredefinedCursor(bMapDragging ? Cursor.MOVE_CURSOR : Cursor.DEFAULT_CURSOR)); if (bMapDragging && mousePos != null) { mapDraggingPosStart.x = mapDraggingPos.x = mousePos.x; mapDraggingPosStart.y = mapDraggingPos.y = mousePos.y; mapDraggingScrollStart.x = spCanvas.getHorizontalScrollBar().getModel().getValue(); mapDraggingScrollStart.y = spCanvas.getVerticalScrollBar().getModel().getValue(); } } } // Returns the current or previous mouse position private Point getMapDraggingDistance() { Point pDelta = new Point(); if (bMapDragging) { pDelta.x = mapDraggingPosStart.x - mapDraggingPos.x; pDelta.y = mapDraggingPosStart.y - mapDraggingPos.y; } return pDelta; } // Updates the map portion displayed in the viewport private void moveMapViewport() { if (!mapDraggingPosStart.equals(mapDraggingPos)) { Point distance = getMapDraggingDistance(); JViewport vp = spCanvas.getViewport(); Point curPos = vp.getViewPosition(); Dimension curDim = vp.getExtentSize(); Dimension maxDim = new Dimension(spCanvas.getHorizontalScrollBar().getMaximum(), spCanvas.getVerticalScrollBar().getMaximum()); if (curDim.width < maxDim.width) { curPos.x = mapDraggingScrollStart.x + distance.x; if (curPos.x < 0) curPos.x = 0; if (curPos.x + curDim.width > maxDim.width) curPos.x = maxDim.width - curDim.width; } if (curDim.height < maxDim.height) { curPos.y = mapDraggingScrollStart.y + distance.y; if (curPos.y < 0) curPos.y = 0; if (curPos.y + curDim.height > maxDim.height) curPos.y = maxDim.height - curDim.height; } vp.setViewPosition(curPos); } } // Returns whether closed door state is active private boolean isDoorStateClosed() { return Settings.DrawClosed; } // Draw opened/closed state of doors (affects map tiles, door layer and door poly layer) private void setDoorState(boolean closed) { Settings.DrawClosed = closed; setDoorStateMap(closed); setDoorStateLayers(closed); updateWindowTitle(); } // Called by setDoorState(): sets door state map tiles private void setDoorStateMap(boolean closed) { if (rcCanvas != null) { rcCanvas.setDoorsClosed(Settings.DrawClosed); } } // Called by setDoorState(): sets door state in door layer and door poly layer private void setDoorStateLayers(boolean closed) { if (layerManager != null) { layerManager.setDoorState(Settings.DrawClosed ? ViewerConstants.DOOR_CLOSED : ViewerConstants.DOOR_OPEN); } } // Returns whether tile grid on map has been enabled private boolean isTileGridEnabled() { return Settings.DrawGrid; } // Enable/disable tile grid on map private void setTileGridEnabled(boolean enable) { Settings.DrawGrid = enable; if (rcCanvas != null) { rcCanvas.setGridEnabled(Settings.DrawGrid); } updateWindowTitle(); } // Returns whether overlays are enabled (considers both internal overlay flag and whether the map contains overlays) private boolean isOverlaysEnabled() { return Settings.DrawOverlays; } // Enable/disable overlays private void setOverlaysEnabled(boolean enable) { Settings.DrawOverlays = enable; if (rcCanvas != null) { rcCanvas.setOverlaysEnabled(Settings.DrawOverlays); } updateWindowTitle(); } // Returns whether overlays are animated private boolean isOverlaysAnimated() { if (timerOverlays != null) { return (isOverlaysEnabled() && timerOverlays.isRunning()); } else { return false; } } // Activate/deactivate overlay animations private void setOverlaysAnimated(boolean animate) { if (timerOverlays != null) { if (animate && !timerOverlays.isRunning()) { timerOverlays.start(); } else if (!animate && timerOverlays.isRunning()) { timerOverlays.stop(); } updateWindowTitle(); } } // Advances animated overlays by one frame private synchronized void advanceOverlayAnimation() { if (rcCanvas != null) { rcCanvas.advanceTileFrame(); } } // Returns the currently used zoom factor of the canvas map private double getZoomFactor() { if (rcCanvas != null) { return rcCanvas.getZoomFactor(); } else { return Settings.ItemZoomFactor[Settings.ZoomLevel]; } } // Sets a new zoom level to the map and associated structures private void setZoomLevel(int zoomIndex) { zoomIndex = Math.min(Math.max(zoomIndex, 0), Settings.ItemZoomFactor.length - 1); updateViewpointCenter(); double zoom = 1.0; if (zoomIndex == Settings.ZoomFactorIndexAuto) { // removing scrollbars (not needed in this mode) boolean needValidate = false; if (spCanvas.getHorizontalScrollBarPolicy() != ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) { spCanvas.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); needValidate = true; } if (spCanvas.getVerticalScrollBarPolicy() != ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER) { spCanvas.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); needValidate = true; } if (needValidate) { spCanvas.validate(); // required for determining the correct viewport size } // determining zoom factor by preserving correct aspect ratio Dimension viewDim = new Dimension(spCanvas.getViewport().getExtentSize()); Dimension mapDim = new Dimension(rcCanvas.getMapWidth(false), rcCanvas.getMapHeight(false)); double zoomX = (double)viewDim.width / (double)mapDim.width; double zoomY = (double)viewDim.height / (double)mapDim.height; zoom = zoomX; if ((int)(zoomX*mapDim.height) > viewDim.height) { zoom = zoomY; } } else { // (re-)activating scrollbars if (spCanvas.getHorizontalScrollBarPolicy() != ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED) { spCanvas.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); } if (spCanvas.getVerticalScrollBarPolicy() != ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED) { spCanvas.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); } zoom = Settings.ItemZoomFactor[zoomIndex]; } if (rcCanvas != null) { rcCanvas.setZoomFactor(zoom); } Settings.ZoomLevel = zoomIndex; updateWindowTitle(); } // Returns whether auto-fit has been selected private boolean isAutoZoom() { return (Settings.ZoomLevel == Settings.ZoomFactorIndexAuto); } // Updates the map coordinate at the center of the current viewport private void updateViewpointCenter() { if (vpCenterExtent == null) { vpCenterExtent = new Rectangle(); } Dimension mapDim = new Dimension(rcCanvas.getMapWidth(true), rcCanvas.getMapHeight(true)); JViewport vp = spCanvas.getViewport(); Rectangle view = vp.getViewRect(); vpCenterExtent.x = view.x + (view.width / 2); vpCenterExtent.y = view.y + (view.height / 2); if (view.width > mapDim.width) { vpCenterExtent.x = mapDim.width / 2; } if (view.height > mapDim.height) { vpCenterExtent.y = mapDim.height / 2; } // canvas coordinate -> map coordinate vpCenterExtent.x = (int)((double)vpCenterExtent.x / getZoomFactor()); vpCenterExtent.y = (int)((double)vpCenterExtent.y / getZoomFactor()); vpCenterExtent.width = vp.getViewSize().width; vpCenterExtent.height = vp.getViewSize().height; } // Attempts to re-center the last known center coordinate in the current viewport private void setViewpointCenter() { if (vpCenterExtent != null) { if (!vpCenterExtent.getSize().equals(spCanvas.getViewport().getViewSize())) { Dimension mapDim = new Dimension(rcCanvas.getMapWidth(true), rcCanvas.getMapHeight(true)); // map coordinate -> canvas coordinate vpCenterExtent.x = (int)((double)vpCenterExtent.x * getZoomFactor()); vpCenterExtent.y = (int)((double)vpCenterExtent.y * getZoomFactor()); JViewport vp = spCanvas.getViewport(); Rectangle view = vp.getViewRect(); Point newViewPos = new Point(vpCenterExtent.x - (view.width / 2), vpCenterExtent.y - (view.height / 2)); if (newViewPos.x < 0) { newViewPos.x = 0; } else if (newViewPos.x + view.width > mapDim.width) { newViewPos.x = mapDim.width - view.width; } if (newViewPos.y < 0) { newViewPos.y = 0; } else if (newViewPos.y + view.height > mapDim.height) { newViewPos.y = mapDim.height - view.height; } vpCenterExtent = null; vp.setViewPosition(newViewPos); } } } // Converts canvas coordinates into actual map coordinates private Point canvasToMapCoordinates(Point coords) { if (coords != null) { coords.x = (int)((double)coords.x / getZoomFactor()); coords.y = (int)((double)coords.y / getZoomFactor()); } return coords; } // Updates the map coordinates pointed to by the current cursor position private void showMapCoordinates(Point coords) { if (coords != null) { // Converting canvas coordinates -> map coordinates coords = canvasToMapCoordinates(coords); if (coords.x != mapCoordinates.x) { mapCoordinates.x = coords.x; lPosX.setText(Integer.toString(mapCoordinates.x)); } if (coords.y != mapCoordinates.y) { mapCoordinates.y = coords.y; lPosY.setText(Integer.toString(mapCoordinates.y)); } } } // Shows a description in the info box private void setInfoText(String text) { if (taInfo != null) { if (text != null) { taInfo.setText(text); } else { taInfo.setText(""); } } } // Creates and displays a popup menu containing the items located at the specified location private boolean updateItemPopup(Point canvasCoords) { final int MaxLen = 32; // max. length of a menuitem text if (layerManager != null) { // preparing menu items List<JMenuItem> menuItems = new ArrayList<JMenuItem>(); Point itemLocation = new Point(); pmItems.removeAll(); // for each active layer... for (int i = 0, lloSize = Settings.ListLayerOrder.size(); i < lloSize; i++) { LayerStackingType stacking = Settings.ListLayerOrder.get(i); LayerType layer = Settings.stackingToLayer(stacking); if (isLayerEnabled(stacking)) { List<? extends LayerObject> itemList = layerManager.getLayerObjects(layer); if (itemList != null && !itemList.isEmpty()) { // for each layer object... for (int j = 0, ilSize = itemList.size(); j < ilSize; j++) { AbstractLayerItem[] items = itemList.get(j).getLayerItems(); // for each layer item... for (int k = 0; k < items.length; k++) { // special case: Ambient/Ambient range (avoiding duplicates) if (stacking == LayerStackingType.AMBIENT && cbLayerAmbientRange.isSelected() && ((LayerObjectAmbient)itemList.get(j)).isLocal()) { // skipped: will be handled in AmbientRange layer break; } if (stacking == LayerStackingType.AMBIENT_RANGE) { if (((LayerObjectAmbient)itemList.get(j)).isLocal() && k == ViewerConstants.AMBIENT_ITEM_ICON) { // considering ranged item only continue; } else if (!((LayerObjectAmbient)itemList.get(j)).isLocal()) { // global sounds don't have ambient ranges break; } } itemLocation.x = canvasCoords.x - items[k].getX(); itemLocation.y = canvasCoords.y - items[k].getY(); if (items[k].isVisible() && items[k].contains(itemLocation)) { // creating a new menu item StringBuilder sb = new StringBuilder(); if (items[k].getName() != null && !items[k].getName().isEmpty()) { sb.append(items[k].getName()); } else { sb.append("Item"); } sb.append(": "); int lenPrefix = sb.length(); int lenMsg = items[k].getMessage().length(); if (lenPrefix + lenMsg > MaxLen) { sb.append(items[k].getMessage().substring(0, MaxLen - lenPrefix)); sb.append("..."); } else { sb.append(items[k].getMessage()); } DataMenuItem dmi = new DataMenuItem(sb.toString(), null, items[k]); if (lenPrefix + lenMsg > MaxLen) { dmi.setToolTipText(items[k].getMessage()); } dmi.addActionListener(getListeners()); menuItems.add(dmi); } } } } } } // updating context menu with the prepared item list if (!menuItems.isEmpty()) { for (int i = 0, miSize = menuItems.size(); i < miSize; i++) { pmItems.add(menuItems.get(i)); } } return !menuItems.isEmpty(); } return false; } // Shows a popup menu containing layer items located at the current position if available private void showItemPopup(MouseEvent event) { if (event != null && event.isPopupTrigger()) { Component parent = null; Point location = null; if (event.getSource() instanceof AbstractLayerItem) { parent = (AbstractLayerItem)event.getSource(); location = parent.getLocation(); location.translate(event.getX(), event.getY()); } else if (event.getSource() == rcCanvas) { parent = rcCanvas; location = event.getPoint(); } if (parent != null && location != null) { if (updateItemPopup(location)) { pmItems.show(parent, event.getX(), event.getY()); } } } } // Updates all available layer items private void reloadLayers() { rcCanvas.reload(true); reloadAreLayers(false); reloadWedLayers(false); applySettings(); } // Updates ARE-related layer items private void reloadAreLayers(boolean order) { if (layerManager != null) { for (int i = 0, ltCount = LayerManager.getLayerTypeCount(); i < ltCount; i++) { LayerType layer = LayerManager.getLayerType(i); LayerStackingType layer2 = Settings.layerToStacking(layer); if (layer != LayerType.DOOR_POLY && layer != LayerType.WALL_POLY) { layerManager.reload(layer); updateLayerItems(layer2); addLayerItems(layer2); if (layer == LayerType.AMBIENT) { updateLayerItems(LayerStackingType.AMBIENT_RANGE); addLayerItems(LayerStackingType.AMBIENT_RANGE); } showLayer(layer, cbLayers[i].isSelected()); } } } updateAmbientRange(); updateRealAnimation(); updateRealAnimationsLighting(getVisualState()); if (order) { orderLayerItems(); } } // Updates WED-related layer items private void reloadWedLayers(boolean order) { if (layerManager != null) { layerManager.close(LayerType.DOOR_POLY); layerManager.close(LayerType.WALL_POLY); layerManager.setWedResource(getCurrentWed()); layerManager.reload(LayerType.DOOR_POLY); layerManager.reload(LayerType.WALL_POLY); updateLayerItems(LayerStackingType.DOOR_POLY); updateLayerItems(LayerStackingType.WALL_POLY); addLayerItems(LayerStackingType.DOOR_POLY); addLayerItems(LayerStackingType.WALL_POLY); showLayer(LayerType.DOOR_POLY, cbLayers[LayerManager.getLayerTypeIndex(LayerType.DOOR_POLY)].isSelected()); showLayer(LayerType.WALL_POLY, cbLayers[LayerManager.getLayerTypeIndex(LayerType.WALL_POLY)].isSelected()); } if (order) { orderLayerItems(); } } // Returns the identifier of the specified layer checkbox, or null on error private LayerType getLayerType(JCheckBox cb) { if (cb != null) { for (int i = 0; i < cbLayers.length; i++) { if (cb == cbLayers[i]) { return LayerManager.getLayerType(i); } } } return null; } // Returns whether the specified layer is visible (by layer) private boolean isLayerEnabled(LayerType layer) { if (layer != null) { return ((Settings.LayerFlags & (1 << LayerManager.getLayerTypeIndex(layer))) != 0); } else { return false; } } // Returns whether the specified layer is visible (by stacked layer) private boolean isLayerEnabled(LayerStackingType layer) { return isLayerEnabled(Settings.stackingToLayer(layer)); } // Opens a viewable instance associated with the specified layer item private void showTable(AbstractLayerItem item) { if (item != null) { if (item.getViewable() instanceof AbstractStruct) { Window wnd = getViewerWindow((AbstractStruct)item.getViewable()); ((AbstractStruct)item.getViewable()).selectEditTab(); wnd.setVisible(true); wnd.toFront(); } else { item.showViewable(); } } } // Attempts to find the Window instance containing the viewer of the specified AbstractStruct object // If it cannot find one, it creates and returns a new one. // If all fails, it returns the NearInfinity instance. private Window getViewerWindow(AbstractStruct as) { if (as != null) { if (as.getViewer() != null && as.getViewer().getParent() != null) { // Determining whether the structure is associated with any open NearInfinity window StructViewer sv = as.getViewer(); Component[] list = sv.getParent().getComponents(); if (list != null) { for (int i = 0; i < list.length; i++) { if (list[i] == sv) { Component c = sv.getParent(); while (c != null) { if (c instanceof Window) { // Window found, returning return (Window)c; } c = c.getParent(); } } } } } // Window not found, creating and returning a new one return new ViewFrame(NearInfinity.getInstance(), as); } // Last resort: returning NearInfinity instance return NearInfinity.getInstance(); } // Updates the visibility state of minimaps (search/height/light maps) private void updateMiniMap() { if (map != null) { int type; if (cbMiniMaps[ViewerConstants.MAP_SEARCH].isSelected()) { type = ViewerConstants.MAP_SEARCH; } else if (cbMiniMaps[ViewerConstants.MAP_LIGHT].isSelected()) { type = ViewerConstants.MAP_LIGHT; } else if (cbMiniMaps[ViewerConstants.MAP_HEIGHT].isSelected()) { type = ViewerConstants.MAP_HEIGHT; } else { type = ViewerConstants.MAP_NONE; } updateTreeNode(cbMiniMaps[ViewerConstants.MAP_SEARCH]); updateTreeNode(cbMiniMaps[ViewerConstants.MAP_LIGHT]); updateTreeNode(cbMiniMaps[ViewerConstants.MAP_HEIGHT]); Settings.MiniMap = type; rcCanvas.setMiniMap(Settings.MiniMap, map.getMiniMap(Settings.MiniMap, getDayTime() == ViewerConstants.LIGHTING_NIGHT)); } } // Sets visibility state of scheduled layer items depending on current day time private void updateScheduledItems() { if (layerManager != null) { for (int i = 0, ltCount = LayerManager.getLayerTypeCount(); i < ltCount; i++) { LayerType layer = LayerManager.getLayerType(i); layerManager.setLayerVisible(layer, isLayerEnabled(layer)); } } } // Applying time schedule settings to layer items private void updateTimeSchedules() { layerManager.setScheduleEnabled(Settings.EnableSchedules); updateWindowTitle(); } // Updates the state of the ambient sound range checkbox and associated functionality private void updateAmbientRange() { if (layerManager != null) { LayerAmbient layer = (LayerAmbient)layerManager.getLayer(LayerType.AMBIENT); if (layer != null) { JCheckBox cb = cbLayers[LayerManager.getLayerTypeIndex(LayerType.AMBIENT)]; cbLayerAmbientRange.setEnabled(cb.isSelected() && layer.getLayerObjectCount(ViewerConstants.AMBIENT_TYPE_LOCAL) > 0); boolean state = cbLayerAmbientRange.isEnabled() && cbLayerAmbientRange.isSelected(); layer.setItemTypeEnabled(ViewerConstants.AMBIENT_ITEM_RANGE, state); } else { cbLayerAmbientRange.setEnabled(false); } updateTreeNode(cbLayerAmbientRange); // Storing settings Settings.ShowAmbientRanges = cbLayerAmbientRange.isSelected(); } } // Applies the specified lighting condition to real animation items private void updateRealAnimationsLighting(int visualState) { if (layerManager != null) { List<? extends LayerObject> list = layerManager.getLayerObjects(LayerType.ANIMATION); if (list != null) { for (int i = 0, size = list.size(); i < size; i++) { LayerObjectAnimation obj = (LayerObjectAnimation)list.get(i); obj.setLighting(visualState); } } } } // Updates the state of real animation checkboxes and their associated functionality private void updateRealAnimation() { if (layerManager != null) { LayerAnimation layer = (LayerAnimation)layerManager.getLayer(LayerType.ANIMATION); if (layer != null) { JCheckBox cb = cbLayers[LayerManager.getLayerTypeIndex(LayerType.ANIMATION)]; boolean enabled = cb.isEnabled() && cb.isSelected(); cbLayerRealAnimation[0].setEnabled(enabled); cbLayerRealAnimation[1].setEnabled(enabled); boolean animEnabled = false; boolean animPlaying = false; if (enabled) { if (cbLayerRealAnimation[0].isSelected()) { animEnabled = true; } else if (cbLayerRealAnimation[1].isSelected()) { animEnabled = true; animPlaying = true; } } layer.setRealAnimationEnabled(animEnabled); layer.setRealAnimationPlaying(animPlaying); } else { cbLayerRealAnimation[0].setEnabled(false); cbLayerRealAnimation[1].setEnabled(false); } updateTreeNode(cbLayerRealAnimation[0]); updateTreeNode(cbLayerRealAnimation[1]); // Storing settings if (!cbLayerRealAnimation[0].isSelected() && !cbLayerRealAnimation[1].isSelected()) { Settings.ShowRealAnimations = ViewerConstants.ANIM_SHOW_NONE; } else if (cbLayerRealAnimation[0].isSelected() && !cbLayerRealAnimation[1].isSelected()) { Settings.ShowRealAnimations = ViewerConstants.ANIM_SHOW_STILL; } else if (!cbLayerRealAnimation[0].isSelected() && cbLayerRealAnimation[1].isSelected()) { Settings.ShowRealAnimations = ViewerConstants.ANIM_SHOW_ANIMATED; } } } // Show/hide items of the specified layer private void showLayer(LayerType layer, boolean visible) { if (layer != null && layerManager != null) { layerManager.setLayerVisible(layer, visible); // updating layer states int bit = 1 << LayerManager.getLayerTypeIndex(layer); if (visible) { Settings.LayerFlags |= bit; } else { Settings.LayerFlags &= ~bit; } } } // Adds items of all available layers to the map canvas. private void addLayerItems() { for (int i = 0, lloSize = Settings.ListLayerOrder.size(); i < lloSize; i++) { addLayerItems(Settings.ListLayerOrder.get(i)); } } // Adds items of the specified layer to the map canvas. private void addLayerItems(LayerStackingType layer) { if (layer != null && layerManager != null) { List<? extends LayerObject> list = layerManager.getLayerObjects(Settings.stackingToLayer(layer)); if (list != null) { for (int i = 0, size = list.size(); i < size; i++) { addLayerItem(layer, list.get(i)); } } } } // Adds items of a single layer object to the map canvas. private void addLayerItem(LayerStackingType layer, LayerObject object) { if (object != null) { // Dealing with ambient icons and ambient ranges separately if (layer == LayerStackingType.AMBIENT) { AbstractLayerItem item = object.getLayerItem(ViewerConstants.AMBIENT_ITEM_ICON); if (item != null) { rcCanvas.add(item); } } else if (layer == LayerStackingType.AMBIENT_RANGE) { AbstractLayerItem item = object.getLayerItem(ViewerConstants.AMBIENT_ITEM_RANGE); if (item != null) { rcCanvas.add(item); } } else { AbstractLayerItem[] items = object.getLayerItems(); if (items != null) { for (int i = 0; i < items.length; i++) { rcCanvas.add(items[i]); } } } } } // Removes all items of all available layers. private void removeLayerItems() { for (int i = 0, lloSize = Settings.ListLayerOrder.size(); i < lloSize; i++) { removeLayerItems(Settings.ListLayerOrder.get(i)); } } // Removes all items of the specified layer. private void removeLayerItems(LayerStackingType layer) { if (layer != null && layerManager != null) { List<? extends LayerObject> list = layerManager.getLayerObjects(Settings.stackingToLayer(layer)); if (list != null) { for (int i = 0, size = list.size(); i < size; i++) { removeLayerItem(layer, list.get(i)); } } } } // Removes items of a single layer object from the map canvas. private void removeLayerItem(LayerStackingType layer, LayerObject object) { if (object != null) { if (layer == LayerStackingType.AMBIENT) { AbstractLayerItem item = object.getLayerItem(ViewerConstants.AMBIENT_ITEM_ICON); rcCanvas.remove(item); } else if (layer == LayerStackingType.AMBIENT_RANGE) { AbstractLayerItem item = object.getLayerItem(ViewerConstants.AMBIENT_ITEM_RANGE); if (item != null) { rcCanvas.remove(item); } } else { AbstractLayerItem[] items = object.getLayerItems(); if (items != null) { for (int i = 0; i < items.length; i++) { rcCanvas.remove(items[i]); } } } } } // Re-orders layer items on the map using listLayer for determining priorities. private void orderLayerItems() { if (layerManager != null) { int index = 0; for (int i = 0, lloSize = Settings.ListLayerOrder.size(); i < lloSize; i++) { List<? extends LayerObject> list = layerManager.getLayerObjects(Settings.stackingToLayer(Settings.ListLayerOrder.get(i))); if (list != null) { for (int j = 0, size = list.size(); j < size; j++) { if (Settings.ListLayerOrder.get(i) == LayerStackingType.AMBIENT_RANGE) { // Special: process ambient ranges only LayerObjectAmbient obj = (LayerObjectAmbient)list.get(j); AbstractLayerItem item = obj.getLayerItem(ViewerConstants.AMBIENT_ITEM_RANGE); if (item != null) { rcCanvas.setComponentZOrder(item, index); index++; } } else if (Settings.ListLayerOrder.get(i) == LayerStackingType.AMBIENT) { // Special: process ambient icons only LayerObjectAmbient obj = (LayerObjectAmbient)list.get(j); AbstractLayerItem item = obj.getLayerItem(ViewerConstants.AMBIENT_ITEM_ICON); rcCanvas.setComponentZOrder(item, index); index++; } else { AbstractLayerItem[] items = list.get(j).getLayerItems(); if (items != null) { for (int k = 0; k < items.length; k++) { if (items[k].getParent() != null) { rcCanvas.setComponentZOrder(items[k], index); index++; } } } } } } } } } // Updates all items of all available layers. private void updateLayerItems() { for (int i = 0, lloSize = Settings.ListLayerOrder.size(); i < lloSize; i++) { updateLayerItems(Settings.ListLayerOrder.get(i)); } } // Updates the map locations of the items in the specified layer. private void updateLayerItems(LayerStackingType layer) { if (layer != null && layerManager != null) { List<? extends LayerObject> list = layerManager.getLayerObjects(Settings.stackingToLayer(layer)); if (list != null) { for (int i = 0, size = list.size(); i < size; i++) { updateLayerItem(list.get(i)); } } } } // Updates the map locations of the items in the specified layer object. private void updateLayerItem(LayerObject object) { if (object != null) { object.update(getZoomFactor()); } } // Update toolbar-related stuff private void updateToolBarButtons() { tbWed.setToolTipText(String.format("Edit WED structure (%1$s)", getCurrentWed().getName())); } // Initializes a new progress monitor instance private void initProgressMonitor(Component parent, String msg, String note, int maxProgress, int msDecide, int msWait) { if (parent == null) parent = NearInfinity.getInstance(); if (maxProgress <= 0) maxProgress = 1; releaseProgressMonitor(); pmMax = maxProgress; pmCur = 0; progress = new ProgressMonitor(parent, msg, note, 0, pmMax); progress.setMillisToDecideToPopup(msDecide); progress.setMillisToPopup(msWait); progress.setProgress(pmCur); } // Closes the current progress monitor private void releaseProgressMonitor() { if (progress != null) { progress.close(); progress = null; } } // Advances the current progress monitor by one and adds the specified note private void advanceProgressMonitor(String note) { if (progress != null) { if (pmCur < pmMax) { pmCur++; if (note != null) { progress.setNote(note); } progress.setProgress(pmCur); } } } // Returns whether a progress monitor is currently active private boolean isProgressMonitorActive() { return progress != null; } // Updates the tree node containing the specified component private void updateTreeNode(Component c) { if (treeControls != null) { DefaultTreeModel model = (DefaultTreeModel)treeControls.getModel(); if (model.getRoot() instanceof TreeNode) { TreeNode node = getTreeNodeOf((TreeNode)model.getRoot(), c); if (node != null) { model.nodeChanged(node); } } } } // Recursive function to find the node containing c private TreeNode getTreeNodeOf(TreeNode node, Component c) { if (node != null && node instanceof DefaultMutableTreeNode && c != null) { if (((DefaultMutableTreeNode)node).getUserObject() == c) { return node; } for (int i = 0, cCount = node.getChildCount(); i < cCount; i++) { TreeNode retVal = getTreeNodeOf(node.getChildAt(i), c); if (retVal != null) { return retVal; } } } return null; } // Shows settings dialog and updates respective controls if needed private void viewSettings() { SettingsDialog vs = new SettingsDialog(this); if (vs.settingsChanged()) { applySettings(); } vs = null; } // Applies current global area viewer settings private void applySettings() { // applying layer stacking order orderLayerItems(); // applying interpolation settings to map switch (Settings.InterpolationMap) { case ViewerConstants.FILTERING_AUTO: rcCanvas.setForcedInterpolation(false); break; case ViewerConstants.FILTERING_NEARESTNEIGHBOR: rcCanvas.setInterpolationType(ViewerConstants.TYPE_NEAREST_NEIGHBOR); rcCanvas.setForcedInterpolation(true); break; case ViewerConstants.FILTERING_BILINEAR: rcCanvas.setInterpolationType(ViewerConstants.TYPE_BILINEAR); rcCanvas.setForcedInterpolation(true); break; } // applying minimap alpha rcCanvas.setMiniMapTransparency((int)(Settings.MiniMapAlpha*255.0)); if (layerManager != null) { // applying animation frame settings ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationFrameState(Settings.ShowFrame); // applying animation active override settings ((LayerAnimation)layerManager.getLayer(LayerType.ANIMATION)).setRealAnimationActiveIgnored(Settings.OverrideAnimVisibility); // applying interpolation settings to animations switch (Settings.InterpolationAnim) { case ViewerConstants.FILTERING_AUTO: layerManager.setRealAnimationForcedInterpolation(false); break; case ViewerConstants.FILTERING_NEARESTNEIGHBOR: layerManager.setRealAnimationInterpolation(ViewerConstants.TYPE_NEAREST_NEIGHBOR); layerManager.setRealAnimationForcedInterpolation(true); break; case ViewerConstants.FILTERING_BILINEAR: layerManager.setRealAnimationInterpolation(ViewerConstants.TYPE_BILINEAR); layerManager.setRealAnimationForcedInterpolation(true); break; } // applying frame rate to animated overlays int interval = (int)(1000.0 / Settings.FrameRateOverlays); if (interval != timerOverlays.getDelay()) { timerOverlays.setDelay(interval); } // applying frame rate to background animations layerManager.setRealAnimationFrameRate(Settings.FrameRateAnimations); } } // Exports the current map state to PNG private void exportMap() { WindowBlocker.blockWindow(this, true); initProgressMonitor(this, "Exporting to PNG...", null, 1, 0, 0); // prevent blocking the event queue SwingUtilities.invokeLater(new Runnable() { @Override public void run() { ByteArrayOutputStream os = new ByteArrayOutputStream(); boolean bRet = false; try { try { VolatileImage srcImage = (VolatileImage)rcCanvas.getImage(); BufferedImage dstImage = ColorConvert.createCompatibleImage(srcImage.getWidth(), srcImage.getHeight(), srcImage.getTransparency()); Graphics2D g = dstImage.createGraphics(); g.drawImage(srcImage, 0, 0, null); g.dispose(); srcImage = null; bRet = ImageIO.write(dstImage, "png", os); dstImage.flush(); dstImage = null; } catch (Exception e) { e.printStackTrace(); } } finally { releaseProgressMonitor(); WindowBlocker.blockWindow(AreaViewer.this, false); } if (bRet) { String fileName = getCurrentAre().getResourceEntry().getResourceName() .toUpperCase(Locale.US).replace(".ARE", ".PNG"); ResourceFactory.exportResource(getCurrentAre().getResourceEntry(), StreamUtils.getByteBuffer(os.toByteArray()), fileName, AreaViewer.this); } else { JOptionPane.showMessageDialog(AreaViewer.this, "Error while exporting map as graphics.", "Error", JOptionPane.ERROR_MESSAGE); } } }); } //----------------------------- INNER CLASSES ----------------------------- // Handles all events of the viewer private class Listeners implements ActionListener, MouseListener, MouseMotionListener, ChangeListener, TilesetChangeListener, PropertyChangeListener, LayerItemListener, ComponentListener, TreeExpansionListener { public Listeners() { } //--------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getSource() instanceof JCheckBox) { JCheckBox cb = (JCheckBox)event.getSource(); LayerType layer = getLayerType(cb); if (layer != null) { showLayer(layer, cb.isSelected()); if (layer == LayerType.AMBIENT) { // Taking care of local ambient ranges updateAmbientRange(); } else if (layer == LayerType.ANIMATION) { // Taking care of real animation display updateRealAnimation(); } updateScheduledItems(); } else if (cb == cbLayerAmbientRange) { updateAmbientRange(); } else if (cb == cbLayerRealAnimation[0]) { if (cbLayerRealAnimation[0].isSelected()) { cbLayerRealAnimation[1].setSelected(false); } updateRealAnimation(); } else if (cb == cbLayerRealAnimation[1]) { if (cbLayerRealAnimation[1].isSelected()) { cbLayerRealAnimation[0].setSelected(false); } updateRealAnimation(); } else if (cb == cbEnableSchedules) { WindowBlocker.blockWindow(AreaViewer.this, true); try { Settings.EnableSchedules = cbEnableSchedules.isSelected(); updateTimeSchedules(); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (cb == cbDrawClosed) { WindowBlocker.blockWindow(AreaViewer.this, true); try { setDoorState(cb.isSelected()); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (cb == cbDrawGrid) { WindowBlocker.blockWindow(AreaViewer.this, true); try { setTileGridEnabled(cb.isSelected()); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (cb == cbDrawOverlays) { WindowBlocker.blockWindow(AreaViewer.this, true); try { setOverlaysEnabled(cb.isSelected()); cbAnimateOverlays.setEnabled(cb.isSelected()); if (!cb.isSelected() && cbAnimateOverlays.isSelected()) { cbAnimateOverlays.setSelected(false); setOverlaysAnimated(false); } updateTreeNode(cbAnimateOverlays); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (cb == cbAnimateOverlays) { WindowBlocker.blockWindow(AreaViewer.this, true); try { setOverlaysAnimated(cb.isSelected()); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (cb == cbMiniMaps[ViewerConstants.MAP_SEARCH]) { if (cb.isSelected()) { cbMiniMaps[ViewerConstants.MAP_LIGHT].setSelected(false); cbMiniMaps[ViewerConstants.MAP_HEIGHT].setSelected(false); } WindowBlocker.blockWindow(AreaViewer.this, true); try { updateMiniMap(); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (cb == cbMiniMaps[ViewerConstants.MAP_LIGHT]) { if (cb.isSelected()) { cbMiniMaps[ViewerConstants.MAP_SEARCH].setSelected(false); cbMiniMaps[ViewerConstants.MAP_HEIGHT].setSelected(false); } WindowBlocker.blockWindow(AreaViewer.this, true); try { updateMiniMap(); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (cb == cbMiniMaps[ViewerConstants.MAP_HEIGHT]) { if (cb.isSelected()) { cbMiniMaps[ViewerConstants.MAP_SEARCH].setSelected(false); cbMiniMaps[ViewerConstants.MAP_LIGHT].setSelected(false); } WindowBlocker.blockWindow(AreaViewer.this, true); try { updateMiniMap(); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } } else if (event.getSource() == cbZoomLevel) { WindowBlocker.blockWindow(AreaViewer.this, true); try { int previousZoomLevel = Settings.ZoomLevel; try { setZoomLevel(cbZoomLevel.getSelectedIndex()); } catch (OutOfMemoryError e) { e.printStackTrace(); cbZoomLevel.hidePopup(); WindowBlocker.blockWindow(AreaViewer.this, false); String msg = "Not enough memory to set selected zoom level.\n" + "(Note: It is highly recommended to close and reopen the area viewer.)"; JOptionPane.showMessageDialog(AreaViewer.this, msg, "Error", JOptionPane.ERROR_MESSAGE); cbZoomLevel.setSelectedIndex(previousZoomLevel); setZoomLevel(previousZoomLevel); } } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } } else if (event.getSource() == timerOverlays) { // Important: making sure that only ONE instance is running at a time to avoid GUI freezes if (workerOverlays == null) { workerOverlays = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { advanceOverlayAnimation(); return null; } }; workerOverlays.addPropertyChangeListener(this); workerOverlays.execute(); } } else if (event.getSource() instanceof AbstractLayerItem) { AbstractLayerItem item = (AbstractLayerItem)event.getSource(); showTable(item); } else if (event.getSource() instanceof DataMenuItem) { DataMenuItem lmi = (DataMenuItem)event.getSource(); AbstractLayerItem item = (AbstractLayerItem)lmi.getData(); showTable(item); } else if (event.getSource() == tbAre) { showTable(map.getAreItem()); } else if (event.getSource() == tbWed) { showTable(map.getWedItem(getCurrentWedIndex())); } else if (event.getSource() == tbSongs) { showTable(map.getSongItem()); } else if (event.getSource() == tbRest) { showTable(map.getRestItem()); } else if (event.getSource() == tbSettings) { viewSettings(); } else if (event.getSource() == tbRefresh) { WindowBlocker.blockWindow(AreaViewer.this, true); try { reloadLayers(); } finally { WindowBlocker.blockWindow(AreaViewer.this, false); } // } else if (ArrayUtil.indexOf(tbAddLayerItem, event.getSource()) >= 0) { // // TODO: include "Add layer item" functionality // int index = ArrayUtil.indexOf(tbAddLayerItem, event.getSource()); // switch (LayerManager.getLayerType(index)) { // case Actor: // case Ambient: // case Animation: // case Automap: // case Container: // case Door: // case DoorPoly: // case Entrance: // case ProTrap: // case Region: // case SpawnPoint: // case Transition: // case WallPoly: // break; // } } else if (event.getSource() == tbExportPNG) { exportMap(); } } //--------------------- End Interface ActionListener --------------------- //--------------------- Begin Interface MouseMotionListener --------------------- @Override public void mouseDragged(MouseEvent event) { if (event.getSource() == rcCanvas && isMapDragging(event.getLocationOnScreen())) { moveMapViewport(); } } @Override public void mouseMoved(MouseEvent event) { if (event.getSource() == rcCanvas) { showMapCoordinates(event.getPoint()); } else if (event.getSource() instanceof AbstractLayerItem) { AbstractLayerItem item = (AbstractLayerItem)event.getSource(); MouseEvent newEvent = new MouseEvent(rcCanvas, event.getID(), event.getWhen(), event.getModifiers(), event.getX() + item.getX(), event.getY() + item.getY(), event.getXOnScreen(), event.getYOnScreen(), event.getClickCount(), event.isPopupTrigger(), event.getButton()); rcCanvas.dispatchEvent(newEvent); } } //--------------------- End Interface MouseMotionListener --------------------- //--------------------- Begin Interface MouseListener --------------------- @Override public void mouseClicked(MouseEvent event) { } @Override public void mousePressed(MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1 && event.getSource() == rcCanvas) { setMapDraggingEnabled(true, event.getLocationOnScreen()); } else { showItemPopup(event); } } @Override public void mouseReleased(MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1 && event.getSource() == rcCanvas) { setMapDraggingEnabled(false, event.getLocationOnScreen()); } else { showItemPopup(event); } } @Override public void mouseEntered(MouseEvent event) { } @Override public void mouseExited(MouseEvent event) { } //--------------------- End Interface MouseListener --------------------- //--------------------- Begin Interface ChangeListener --------------------- @Override public void stateChanged(ChangeEvent event) { if (event.getSource() == pDayTime) { if (workerLoadMap == null) { // loading map in a separate thread if (workerLoadMap == null) { blocker = new WindowBlocker(AreaViewer.this); blocker.setBlocked(true); workerLoadMap = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { setHour(pDayTime.getHour()); return null; } }; workerLoadMap.addPropertyChangeListener(this); workerLoadMap.execute(); } } } else if (event.getSource() == spCanvas.getViewport()) { setViewpointCenter(); } } //--------------------- End Interface ChangeListener --------------------- //--------------------- Begin Interface TilesetChangeListener --------------------- @Override public void tilesetChanged(TilesetChangeEvent event) { if (event.getSource() == rcCanvas) { if (event.hasChangedMap()) { updateLayerItems(); } } } //--------------------- End Interface TilesetChangeListener --------------------- //--------------------- Begin Interface LayerItemListener --------------------- @Override public void layerItemChanged(LayerItemEvent event) { if (event.getSource() instanceof AbstractLayerItem) { AbstractLayerItem item = (AbstractLayerItem)event.getSource(); if (event.isHighlighted()) { setInfoText(item.getMessage()); } else { setInfoText(null); } } } //--------------------- End Interface LayerItemListener --------------------- //--------------------- Begin Interface PropertyChangeListener --------------------- @Override public void propertyChange(PropertyChangeEvent event) { if (event.getSource() == workerInitGui) { if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { releaseProgressMonitor(); workerInitGui = null; } } else if (event.getSource() == workerLoadMap) { if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { if (blocker != null) { blocker.setBlocked(false); blocker = null; } releaseProgressMonitor(); workerLoadMap = null; } } else if (event.getSource() == workerOverlays) { if ("state".equals(event.getPropertyName()) && SwingWorker.StateValue.DONE == event.getNewValue()) { // Important: making sure that only ONE instance is running at a time to avoid GUI freezes workerOverlays = null; } } } //--------------------- End Interface PropertyChangeListener --------------------- //--------------------- Begin Interface ComponentListener --------------------- @Override public void componentResized(ComponentEvent event) { if (event.getSource() == rcCanvas) { // changing panel size whenever the tileset size changes pCanvas.setPreferredSize(rcCanvas.getSize()); pCanvas.setSize(rcCanvas.getSize()); } if (event.getSource() == spCanvas) { if (isAutoZoom()) { setZoomLevel(Settings.ZoomFactorIndexAuto); } // centering the tileset if it fits into the viewport Dimension pDim = rcCanvas.getPreferredSize(); Dimension spDim = pCanvas.getSize(); if (pDim.width < spDim.width || pDim.height < spDim.height) { Point pLocation = rcCanvas.getLocation(); Point pDistance = new Point(); if (pDim.width < spDim.width) { pDistance.x = pLocation.x - (spDim.width - pDim.width) / 2; } if (pDim.height < spDim.height) { pDistance.y = pLocation.y - (spDim.height - pDim.height) / 2; } rcCanvas.setLocation(pLocation.x - pDistance.x, pLocation.y - pDistance.y); } else { rcCanvas.setLocation(0, 0); } } } @Override public void componentMoved(ComponentEvent event) { } @Override public void componentShown(ComponentEvent event) { } @Override public void componentHidden(ComponentEvent event) { } //--------------------- End Interface ComponentListener --------------------- //--------------------- Begin Interface TreeExpansionListener --------------------- @Override public void treeExpanded(TreeExpansionEvent event) { if (event.getPath().getLastPathComponent() instanceof DefaultMutableTreeNode) { // Storing the expanded state of the node if it marks a sidebar section DefaultMutableTreeNode node = (DefaultMutableTreeNode)event.getPath().getLastPathComponent(); if (node.getLevel() == 1) { DefaultMutableTreeNode root = (DefaultMutableTreeNode)node.getParent(); for (int i = 0, cCount = root.getChildCount(); i < cCount; i++) { if (root.getChildAt(i) == node) { switch (1 << i) { case ViewerConstants.SIDEBAR_VISUALSTATE: Settings.SidebarControls |= ViewerConstants.SIDEBAR_VISUALSTATE; break; case ViewerConstants.SIDEBAR_LAYERS: Settings.SidebarControls |= ViewerConstants.SIDEBAR_LAYERS; break; case ViewerConstants.SIDEBAR_MINIMAPS: Settings.SidebarControls |= ViewerConstants.SIDEBAR_MINIMAPS; break; } } } } } } @Override public void treeCollapsed(TreeExpansionEvent event) { if (event.getPath().getLastPathComponent() instanceof DefaultMutableTreeNode) { // Storing the collapsed state of the node if it marks a sidebar section DefaultMutableTreeNode node = (DefaultMutableTreeNode)event.getPath().getLastPathComponent(); if (node.getLevel() == 1) { DefaultMutableTreeNode root = (DefaultMutableTreeNode)node.getParent(); for (int i = 0, cCount = root.getChildCount(); i < cCount; i++) { if (root.getChildAt(i) == node) { switch (1 << i) { case ViewerConstants.SIDEBAR_VISUALSTATE: Settings.SidebarControls &= ~ViewerConstants.SIDEBAR_VISUALSTATE; break; case ViewerConstants.SIDEBAR_LAYERS: Settings.SidebarControls &= ~ViewerConstants.SIDEBAR_LAYERS; break; case ViewerConstants.SIDEBAR_MINIMAPS: Settings.SidebarControls &= ~ViewerConstants.SIDEBAR_MINIMAPS; break; } } } } } } //--------------------- End Interface TreeExpansionListener --------------------- } // Handles map-specific properties private static class Map { private final Window parent; private final WedResource[] wed = new WedResource[2]; private final AbstractLayerItem[] wedItem = new IconLayerItem[]{null, null}; private final GraphicsResource[] mapLight = new GraphicsResource[]{null, null}; private AreResource are; private boolean hasDayNight, hasExtendedNight; private AbstractLayerItem areItem, songItem, restItem; private GraphicsResource mapSearch, mapHeight; public Map(Window parent, AreResource are) { this.parent = parent; this.are = are; init(); } /** * Removes resources from memory. */ public void clear() { songItem = null; restItem = null; are = null; areItem = null; closeWed(ViewerConstants.AREA_DAY, false); wed[ViewerConstants.AREA_DAY] = null; closeWed(ViewerConstants.AREA_NIGHT, false); wed[ViewerConstants.AREA_NIGHT] = null; wedItem[ViewerConstants.AREA_DAY] = null; wedItem[ViewerConstants.AREA_NIGHT] = null; } /** * Returns the current AreResource instance. * @return The current AreResource instance. */ public AreResource getAre() { return are; } /** * Returns the WedResource instance of day or night map. * @param dayNight Either one of AREA_DAY or AREA_NIGHT. * @return The desired WedResource instance. */ public WedResource getWed(int dayNight) { switch (dayNight) { case ViewerConstants.AREA_DAY: return wed[ViewerConstants.AREA_DAY]; case ViewerConstants.AREA_NIGHT: return wed[ViewerConstants.AREA_NIGHT]; default: return null; } } /** * Returns the specifie minimap. * @param mapType One of MAP_SEARCH, MAP_HEIGHT or MAP_LIGHT. * @param isNight Specify {@code true} to return the night-specific light map, * or {@code false} to return the day-specific light map. * @return The specified BmpResource instance. */ public GraphicsResource getMiniMap(int mapType, boolean isNight) { switch (mapType) { case ViewerConstants.MAP_SEARCH: return mapSearch; case ViewerConstants.MAP_HEIGHT: return mapHeight; case ViewerConstants.MAP_LIGHT: return isNight ? mapLight[1] : mapLight[0]; default: return null; } } /** * Attempts to close the specified WED. If changes have been done, a dialog asks for saving. * @param dayNight Either AREA_DAY or AREA_NIGHT. * @param allowCancel Indicates whether to allow cancelling the saving process. * @return {@code true} if the resource has been closed, {@code false} otherwise (e.g. * if the user chooses to cancel saving changes.) */ public boolean closeWed(int dayNight, boolean allowCancel) { boolean bRet = false; dayNight = (dayNight == ViewerConstants.AREA_NIGHT) ? ViewerConstants.AREA_NIGHT : ViewerConstants.AREA_DAY; if (wed[dayNight] != null) { if (wed[dayNight].hasStructChanged()) { Path output; if (wed[dayNight].getResourceEntry() instanceof BIFFResourceEntry) { output = FileManager.query(Profile.getRootFolders(), Profile.getOverrideFolderName(), wed[dayNight].getResourceEntry().toString()); } else { output = wed[dayNight].getResourceEntry().getActualPath(); } int optionIndex = allowCancel ? 1 : 0; int optionType = allowCancel ? JOptionPane.YES_NO_CANCEL_OPTION : JOptionPane.YES_NO_OPTION; String options[][] = { {"Save changes", "Discard changes"}, {"Save changes", "Discard changes", "Cancel"} }; int result = JOptionPane.showOptionDialog(parent, "Save changes to " + output + '?', "Resource changed", optionType, JOptionPane.WARNING_MESSAGE, null, options[optionIndex], options[optionIndex][0]); if (result == 0) { ResourceFactory.saveResource((Resource)wed[dayNight], parent); } if (result != 2) { wed[dayNight].setStructChanged(false); } bRet = (result != 2); } else { bRet = true; } if (bRet && wed[dayNight].getViewer() != null) { wed[dayNight].getViewer().close(); } } return bRet; } /** * Reloads the specified WED resource. * @param dayNight The WED resource to load. */ public void reloadWed(int dayNight) { if (are != null) { dayNight = (dayNight == ViewerConstants.AREA_NIGHT) ? ViewerConstants.AREA_NIGHT : ViewerConstants.AREA_DAY; String wedName = ""; ResourceRef wedRef = (ResourceRef)are.getAttribute(AreResource.ARE_WED_RESOURCE); if (wedRef != null) { wedName = wedRef.getResourceName(); if ("None".equalsIgnoreCase(wedName)) { wedName = ""; } if (dayNight == ViewerConstants.AREA_DAY) { if (!wedName.isEmpty()) { try { wed[ViewerConstants.AREA_DAY] = new WedResource(ResourceFactory.getResourceEntry(wedName)); } catch (Exception e) { wed[ViewerConstants.AREA_DAY] = null; } } else { wed[ViewerConstants.AREA_DAY] = null; } if (wed[ViewerConstants.AREA_DAY] != null) { wedItem[ViewerConstants.AREA_DAY] = new IconLayerItem(new Point(), wed[ViewerConstants.AREA_DAY], wed[ViewerConstants.AREA_DAY].getName()); wedItem[ViewerConstants.AREA_DAY].setVisible(false); } } else { // getting extended night map if (hasExtendedNight && !wedName.isEmpty()) { int pos = wedName.lastIndexOf('.'); if (pos > 0) { String wedNameNight = wedName.substring(0, pos) + "N" + wedName.substring(pos); try { wed[ViewerConstants.AREA_NIGHT] = new WedResource(ResourceFactory.getResourceEntry(wedNameNight)); } catch (Exception e) { wed[ViewerConstants.AREA_NIGHT] = wed[ViewerConstants.AREA_DAY]; } } else { wed[ViewerConstants.AREA_NIGHT] = wed[ViewerConstants.AREA_DAY]; } } else { wed[ViewerConstants.AREA_NIGHT] = wed[ViewerConstants.AREA_DAY]; } if (wed[ViewerConstants.AREA_NIGHT] != null) { wedItem[ViewerConstants.AREA_NIGHT] = new IconLayerItem(new Point(), wed[ViewerConstants.AREA_NIGHT], wed[ViewerConstants.AREA_NIGHT].getName()); wedItem[ViewerConstants.AREA_NIGHT].setVisible(false); } } } } } /** * Reloads the search/light/height maps associated with the area. */ public void reloadMiniMaps() { if (are != null) { String mapName = are.getName().toUpperCase(Locale.ENGLISH); if (mapName.lastIndexOf('.') >= 0) { mapName = mapName.substring(0, mapName.lastIndexOf('.')); } // loading search map String name = mapName + "SR.BMP"; try { mapSearch = new GraphicsResource(ResourceFactory.getResourceEntry(name)); } catch (Exception e) { mapSearch = null; } // loading height map name = mapName + "HT.BMP"; try { mapHeight = new GraphicsResource(ResourceFactory.getResourceEntry(name)); } catch (Exception e) { mapHeight = null; } // loading light map(s) name = mapName + "LM.BMP"; try { mapLight[0] = new GraphicsResource(ResourceFactory.getResourceEntry(name)); } catch (Exception e) { mapLight[0] = null; } if (hasExtendedNight()) { name = mapName + "LN.BMP"; try { mapLight[1] = new GraphicsResource(ResourceFactory.getResourceEntry(name)); } catch (Exception e) { mapLight[1] = mapLight[0]; } } else { mapLight[1] = mapLight[0]; } } } // Returns the pseudo layer item for the AreResource structure public AbstractLayerItem getAreItem() { return areItem; } // Returns the pseudo layer item for the WedResource structure of the selected day time public AbstractLayerItem getWedItem(int dayNight) { if (dayNight == ViewerConstants.AREA_NIGHT) { return wedItem[ViewerConstants.AREA_NIGHT]; } else { return wedItem[ViewerConstants.AREA_DAY]; } } // Returns the pseudo layer item for the ARE's song structure public AbstractLayerItem getSongItem() { return songItem; } // Returns the pseudo layer item for the ARE's rest encounter structure public AbstractLayerItem getRestItem() { return restItem; } // Returns whether the current map supports day/twilight/night settings public boolean hasDayNight() { return hasDayNight; } // Returns true if the current map has separate WEDs for day/night public boolean hasExtendedNight() { return hasExtendedNight; } private void init() { if (are != null) { // fetching important flags Flag flags = (Flag)are.getAttribute(AreResource.ARE_LOCATION); if (flags != null) { if (Profile.getEngine() == Profile.Engine.PST) { hasDayNight = flags.isFlagSet(10); hasExtendedNight = false; } else { hasDayNight = flags.isFlagSet(1); hasExtendedNight = flags.isFlagSet(6); } } // initializing pseudo layer items areItem = new IconLayerItem(new Point(), are, are.getName()); areItem.setVisible(false); Song song = (Song)are.getAttribute(Song.ARE_SONGS); if (song != null) { songItem = new IconLayerItem(new Point(), song, ""); songItem.setVisible(false); } RestSpawn rest = (RestSpawn)are.getAttribute(RestSpawn.ARE_RESTSPAWN); if (rest != null) { restItem = new IconLayerItem(new Point(), rest, ""); } // getting associated WED resources reloadWed(ViewerConstants.AREA_DAY); reloadWed(ViewerConstants.AREA_NIGHT); reloadMiniMaps(); } } } // Defines a panel providing controls for setting day times (either by hour or by general day time) private static final class DayTimePanel extends JPanel implements ActionListener, ChangeListener { private final List<ChangeListener> listeners = new ArrayList<ChangeListener>(); private final JRadioButton[] rbDayTime = new JRadioButton[3]; private final ButtonPopupWindow bpwDayTime; private JSlider sHours; // Creates and returns a string describing the time for display on the parent button public static String getButtonText(int hour) { final String[] dayTime = new String[]{"Day", "Twilight", "Night"}; String desc = dayTime[ViewerConstants.getDayTime(hour)]; return String.format("Time (%1$02d:00 - %2$s)", hour, desc); } public DayTimePanel(ButtonPopupWindow bpw, int hour) { super(new BorderLayout()); bpwDayTime = bpw; init(hour); } public int getHour() { return sHours.getValue(); } public void setHour(int hour) { while (hour < 0) { hour += 24; } hour %= 24; if (hour != sHours.getValue()) { sHours.setValue(hour); rbDayTime[ViewerConstants.getDayTime(hour)].setSelected(true); fireStateChanged(); } } /** * Adds a ChangeListener to the slider. * @param l the ChangeListener to add */ public void addChangeListener(ChangeListener l) { if (l != null) { if (!listeners.contains(l)) { listeners.add(l); } } } /** * Removes a ChangeListener from the slider. * @param l the ChangeListener to remove */ @SuppressWarnings("unused") public void removeChangeListener(ChangeListener l) { if (l != null) { int index = listeners.indexOf(l); if (index >= 0) { listeners.remove(index); } } } /** * Returns an array of all the ChangeListeners added to this JSlider with addChangeListener(). * @return All of the ChangeListeners added or an empty array if no listeners have been added. */ @SuppressWarnings("unused") public ChangeListener[] getChangeListeners() { ChangeListener[] retVal = new ChangeListener[listeners.size()]; for (int i = 0, size = listeners.size(); i < size; i++) { retVal[i] = listeners.get(i); } return retVal; } // --------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { for (int i = 0; i < rbDayTime.length; i++) { if (event.getSource() == rbDayTime[i]) { int hour = ViewerConstants.getHourOf(i); if (hour != sHours.getValue()) { sHours.setValue(hour); } break; } } } // --------------------- End Interface ActionListener --------------------- // --------------------- Begin Interface ChangeListener --------------------- @Override public void stateChanged(ChangeEvent event) { if (event.getSource() == sHours) { if (!sHours.getValueIsAdjusting()) { int dt = ViewerConstants.getDayTime(sHours.getValue()); if (!rbDayTime[dt].isSelected()) { rbDayTime[dt].setSelected(true); } updateButton(); fireStateChanged(); } } } // --------------------- End Interface ChangeListener --------------------- // Fires a stateChanged event for all registered listeners private void fireStateChanged() { ChangeEvent event = new ChangeEvent(this); for (int i = 0, size = listeners.size(); i < size; i++) { listeners.get(i).stateChanged(event); } } // Updates the text of the parent button private void updateButton() { if (bpwDayTime != null) { bpwDayTime.setText(getButtonText(sHours.getValue())); } } private void init(int hour) { while (hour < 0) { hour += 24; } hour %= 24; int dayTime = ViewerConstants.getDayTime(hour); ButtonGroup bg = new ButtonGroup(); String s = String.format("Day (%1$02d:00)", ViewerConstants.getHourOf(ViewerConstants.LIGHTING_DAY)); rbDayTime[ViewerConstants.LIGHTING_DAY] = new JRadioButton(s, (dayTime == ViewerConstants.LIGHTING_DAY)); rbDayTime[ViewerConstants.LIGHTING_DAY].addActionListener(this); bg.add(rbDayTime[ViewerConstants.LIGHTING_DAY]); s = String.format("Twilight (%1$02d:00)", ViewerConstants.getHourOf(ViewerConstants.LIGHTING_TWILIGHT)); rbDayTime[ViewerConstants.LIGHTING_TWILIGHT] = new JRadioButton(s, (dayTime == ViewerConstants.LIGHTING_TWILIGHT)); rbDayTime[ViewerConstants.LIGHTING_TWILIGHT].addActionListener(this); bg.add(rbDayTime[ViewerConstants.LIGHTING_TWILIGHT]); s = String.format("Night (%1$02d:00)", ViewerConstants.getHourOf(ViewerConstants.LIGHTING_NIGHT)); rbDayTime[ViewerConstants.LIGHTING_NIGHT] = new JRadioButton(s, (dayTime == ViewerConstants.LIGHTING_NIGHT)); rbDayTime[ViewerConstants.LIGHTING_NIGHT].addActionListener(this); bg.add(rbDayTime[ViewerConstants.LIGHTING_NIGHT]); Hashtable<Integer, JLabel> table = new Hashtable<Integer, JLabel>(); for (int i = 0; i < 24; i += 4) { table.put(Integer.valueOf(i), new JLabel(String.format("%1$02d:00", i))); } sHours = new JSlider(0, 23, hour); sHours.addChangeListener(this); sHours.setSnapToTicks(true); sHours.setLabelTable(table); sHours.setPaintLabels(true); sHours.setMinorTickSpacing(1); sHours.setMajorTickSpacing(4); sHours.setPaintTicks(true); sHours.setPaintTrack(true); Dimension dim = sHours.getPreferredSize(); sHours.setPreferredSize(new Dimension((dim.width*3)/2, dim.height)); GridBagConstraints c = new GridBagConstraints(); JPanel pHours = new JPanel(new GridBagLayout()); pHours.setBorder(BorderFactory.createTitledBorder("By hour: ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0); pHours.add(sHours, c); JPanel pTime = new JPanel(new GridBagLayout()); pTime.setBorder(BorderFactory.createTitledBorder("By lighting condition: ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 4, 0), 0, 0); pTime.add(rbDayTime[ViewerConstants.LIGHTING_DAY], c); c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 4, 0), 0, 0); pTime.add(rbDayTime[ViewerConstants.LIGHTING_TWILIGHT], c); c = ViewerUtil.setGBC(c, 2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(4, 8, 4, 8), 0, 0); pTime.add(rbDayTime[ViewerConstants.LIGHTING_NIGHT], c); JPanel pMain = new JPanel(new GridBagLayout()); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 0, 4), 0, 0); pMain.add(pHours, c); c = ViewerUtil.setGBC(c, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0); pMain.add(pTime, c); add(pMain, BorderLayout.CENTER); updateButton(); } } // Adds support for visual components in JTree instances private static class ComponentTreeCellRenderer extends DefaultTreeCellRenderer { public ComponentTreeCellRenderer() { super(); } // --------------------- Begin Interface TreeCellRenderer --------------------- @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value instanceof DefaultMutableTreeNode) { value = ((DefaultMutableTreeNode)value).getUserObject(); } Component c = null; if (value instanceof Component) { c = (Component)value; } else { c = new JLabel((value != null) ? value.toString() : ""); } return c; } // --------------------- End Interface TreeCellRenderer --------------------- } // Adds support for editable visual components in JTree instances private static class ComponentTreeCellEditor extends DefaultTreeCellEditor { public ComponentTreeCellEditor(JTree tree, ComponentTreeCellRenderer renderer) { super(tree, renderer); } // --------------------- Begin Interface TreeCellEditor --------------------- @Override public boolean isCellEditable(EventObject event) { return true; } @Override public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, boolean expanded, boolean leaf, int row) { return renderer.getTreeCellRendererComponent(tree, value, isSelected, expanded, leaf, row, true); } // --------------------- End Interface TreeCellEditor --------------------- } }