package com.bbn.openmap.dataAccess.mapTile; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.net.URL; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import javax.swing.AbstractListModel; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.ListSelectionModel; import javax.swing.SpinnerNumberModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import com.bbn.openmap.I18n; import com.bbn.openmap.Layer; import com.bbn.openmap.MapBean; import com.bbn.openmap.MapHandler; import com.bbn.openmap.gui.OMComponentPanel; import com.bbn.openmap.omGraphics.DrawingAttributes; import com.bbn.openmap.omGraphics.OMAction; import com.bbn.openmap.omGraphics.OMGraphic; import com.bbn.openmap.omGraphics.OMGraphicList; import com.bbn.openmap.omGraphics.OMRect; import com.bbn.openmap.proj.Projection; /** * A class that visually manages the settings for a ZoomLevelInfo object. Works * inside the MapTileMakerComponent. * * @author dietrick */ public class ZoomLevelMakerFace extends OMComponentPanel { protected static Logger logger = Logger.getLogger("com.bbn.openmap.dataAccess.mapTile.ZoomLevelMakerFace"); private static final long serialVersionUID = 1L; protected ZoomLevelMaker zfi; boolean active = false; boolean include = false; protected JList boundsList; protected List<BoundsObject> boundsObjectList; protected BoundsListModel boundsModel; protected OMGraphicList boundaries = new OMGraphicList(); protected List<LayerObject> layerList = new ArrayList<LayerObject>(); protected MapTileMakerComponent organizer; protected JPanel layerPanel; protected JButton createBoundaryButton; protected JButton editBoundaryButton; protected JButton deleteBoundaryButton; protected JLabel intro; protected JLabel tileDimensions; protected JButton scaleButton; JSpinner rangeLevelChoice; @SuppressWarnings("serial") public ZoomLevelMakerFace(ZoomLevelMaker zfi, MapTileMakerComponent mtmc) { this.zfi = zfi; this.organizer = mtmc; setLayout(new GridBagLayout()); JPanel introPanel = new JPanel(new GridBagLayout()); JLabel intro = new JLabel(i18n.get(ZoomLevelMakerFace.class, "zoom_level", "Make tiles for zoom levels")); JLabel tileDimLabel = new JLabel(i18n.get(ZoomLevelMakerFace.class, "tile_dim", "Tile Dimensions")); JLabel baseScaleLabel = new JLabel(i18n.get(ZoomLevelMakerFace.class, "base_scale", "Base Scale for Zoom Level")); JSpinner zoomLevelChoice = new JSpinner(new SpinnerNumberModel(0, 0, 20, 1.0)) { public void setValue(Object value) { if (value instanceof Number) { value = new Double(((Number) value).intValue()); } super.setValue(value); updateZoomLevel(((Double) getValue()).intValue()); } }; String upperZoomLevelTT = i18n.get(ZoomLevelMakerFace.class, "upper_zoom_level", I18n.TOOLTIP, "Upper zoom level"); String lowerZoomLevelTT = i18n.get(ZoomLevelMakerFace.class, "lower_zoom_level", I18n.TOOLTIP, "Lower zoom level"); zoomLevelChoice.setToolTipText(upperZoomLevelTT); tileDimensions = new JLabel(); scaleButton = new JButton(); scaleButton.setBorder(BorderFactory.createLineBorder(Color.GRAY)); scaleButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { MapHandler mHandler = (MapHandler) organizer.getBeanContext(); MapBean map = mHandler.get(MapBean.class); map.setScale(getZoomLevelMaker().getScale()); } }); rangeLevelChoice = new JSpinner(new SpinnerNumberModel(0, 0, 20, 1.0)) { public void setValue(Object value) { if (value instanceof Number) { int newVal = ((Number) value).intValue(); int limit = getZoomLevelMaker().getZoomLevel(); if (newVal > limit) { newVal = limit; } value = new Double(newVal); getZoomLevelMaker().setRange(newVal); } super.setValue(value); } }; rangeLevelChoice.setToolTipText(lowerZoomLevelTT); GridBagConstraints c = new GridBagConstraints(); c.gridx = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.WEST; c.insets = new Insets(0, 0, 0, 0); JPanel zoomHolder = new JPanel(); zoomHolder.add(zoomLevelChoice, c); zoomHolder.add(new JLabel(" to "), c); zoomHolder.add(rangeLevelChoice, c); addIntroEntry(introPanel, intro, zoomHolder, 0, new Insets(0, 0, 0, 0)); addIntroEntry(introPanel, tileDimLabel, tileDimensions, 1, new Insets(0, 0, 5, 0)); addIntroEntry(introPanel, baseScaleLabel, scaleButton, 2, new Insets(0, 0, 5, 0)); updateZoomLevel(0); add(introPanel, c); c.gridx = GridBagConstraints.REMAINDER; c.anchor = GridBagConstraints.WEST; include = true; c.fill = GridBagConstraints.BOTH; c.weightx = 1.0f; layerPanel = new JPanel(new GridBagLayout()); setLayers(layerList); JPanel outerLayerPanel = new JPanel(new GridBagLayout()); String layers_for_title = i18n.get(ZoomLevelMakerFace.class, "layers_for_title", "Layers"); outerLayerPanel.setBorder(BorderFactory.createTitledBorder(layers_for_title)); JScrollPane jsp = new JScrollPane(layerPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); c.weighty = 1.0f; c.insets = new Insets(0, 0, 0, 0); outerLayerPanel.add(jsp, c); c.weighty = .8f; c.insets = new Insets(3, 0, 3, 0); add(outerLayerPanel, c); JPanel boundsPanel = new JPanel(new BorderLayout()); String boundaries_for_title = i18n.get(ZoomLevelMakerFace.class, "boundaries_for_title", "Boundaries"); boundsPanel.setBorder(BorderFactory.createTitledBorder(boundaries_for_title)); boundsObjectList = new ArrayList<BoundsObject>(); boundsModel = new BoundsListModel(); boundsList = new JList(boundsModel); boundsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); boundsList.setLayoutOrientation(JList.VERTICAL); boundsList.setVisibleRowCount(4); boundsList.addListSelectionListener(new SelectionListener()); boundsList.addMouseListener(new ListMouseListener()); JScrollPane scrollableBoundsList = new JScrollPane(boundsList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); boundsPanel.add(scrollableBoundsList, BorderLayout.CENTER); JPanel boundsButtonPanel = new JPanel(); ImageIcon ii = createImageIcon("add_16x16.png"); createBoundaryButton = new JButton(ii); String create_a_boundary_rectangle = i18n.get(ZoomLevelMakerFace.class, "create_a_boundary_rectangle", "Create a boundary rectangle"); createBoundaryButton.setToolTipText(create_a_boundary_rectangle); createBoundaryButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { organizer.createRectangle(); } }); boundsButtonPanel.add(createBoundaryButton); ii = createImageIcon("edit_16x16.png"); editBoundaryButton = new JButton(ii); String edit_a_selected_boundary_rectangle = i18n.get(ZoomLevelMakerFace.class, "edit_a_selected_boundary_rectangle", "Edit a selected boundary rectangle"); editBoundaryButton.setToolTipText(edit_a_selected_boundary_rectangle); editBoundaryButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { if (boundsList.getSelectedIndex() != -1) { BoundsObject selected = (BoundsObject) boundsList.getSelectedValue(); organizer.edit(selected.bounds, null); } } }); boundsButtonPanel.add(editBoundaryButton); ii = createImageIcon("remov_16x16.png"); deleteBoundaryButton = new JButton(ii); String delete_a_selected_boundary_rectangle = i18n.get(ZoomLevelMakerFace.class, "delete_a_selected_boundary_rectangle", "Delete a selected boundary rectangle"); deleteBoundaryButton.setToolTipText(delete_a_selected_boundary_rectangle); deleteBoundaryButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { if (boundsList.getSelectedIndex() != -1) { BoundsObject selected = (BoundsObject) boundsList.getSelectedValue(); boundaries.remove(selected.bounds); boundsObjectList.remove(selected); boundsList.repaint(); if (organizer.drawingTool.isActivated()) { organizer.drawingTool.deactivate(OMAction.DELETE_GRAPHIC_MASK); } ((MapHandler) organizer.getBeanContext()).get(MapBean.class).repaint(); } } }); boundsButtonPanel.add(deleteBoundaryButton); boundsPanel.add(boundsButtonPanel, BorderLayout.SOUTH); c.weighty = .8f; add(boundsPanel, c); } protected void addIntroEntry(JPanel panel, JComponent left, JComponent right, int y, Insets i) { GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = y; c.weightx = 0; c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.WEST; c.insets = i; panel.add(left, c); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1f; panel.add(Box.createHorizontalGlue(), c); c.gridx = 1; c.fill = GridBagConstraints.NONE; c.weightx = 0f; c.anchor = GridBagConstraints.EAST; panel.add(right, c); } public void updateZoomLevel(int zoomLevel) { DecimalFormat df = new DecimalFormat("000,000"); zfi.setZoomLevel(zoomLevel); zfi.setScale(new OSMMapTileCoordinateTransform().getScaleForZoom(zoomLevel)); int etc = zfi.getEdgeTileCount(); tileDimensions.setText(etc + " x " + etc); String scale = "1:" + df.format(zfi.getScale()); scaleButton.setText(scale); String tooltip = i18n.get(ZoomLevelMakerFace.class, "set_map_scale_to", I18n.TOOLTIP, "Set map scale to"); scaleButton.setToolTipText(tooltip + " " + scale); Object obj = rangeLevelChoice.getValue(); if (obj instanceof Number) { int rangeVal = ((Number) obj).intValue(); if (rangeVal > zoomLevel) { rangeVal = zoomLevel; rangeLevelChoice.setValue(new Integer(zoomLevel)); zfi.setRange(rangeVal); } } } protected void enableBoundaryButtons(boolean setting) { createBoundaryButton.setEnabled(setting); boolean somethingSelected = boundsList.getSelectedIndex() != -1; editBoundaryButton.setEnabled(setting && somethingSelected); deleteBoundaryButton.setEnabled(setting && somethingSelected); } /** * Given a set of Layers, look at the internal list and make sure there are * layer objects that match. Purges LayerObjects that don't represent * layers, and adds LayerObjects as needed. Calls setLayers with * LayerObjects. * * @param layers */ protected void setLayers(Layer[] layers) { List<LayerObject> layerObjects = new ArrayList<LayerObject>(); for (Layer layer : layers) { boolean foundOne = false; for (LayerObject lo : layerList) { if (lo.getLayer().equals(layer)) { foundOne = true; layerObjects.add(lo); break; } } if (!foundOne) { layerObjects.add(new LayerObject(layer)); } } setLayers(layerObjects); } /** * Update the layer panel to have buttons for the layer objects. Doesn't do * any checking, just adds them to the layerPanel, and adds the button that * pushes this ZLIF's layer settings to all other layers. * * @param layerObjects */ protected void setLayers(List<LayerObject> layerObjects) { this.layerList = layerObjects; if (layerPanel == null) { return; } layerPanel.removeAll(); GridBagConstraints c = new GridBagConstraints(); c.gridx = GridBagConstraints.REMAINDER; c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0f; c.anchor = GridBagConstraints.WEST; for (LayerObject lo : layerObjects) { String layerMarker = lo.getLayer().getPropertyPrefix(); if (layerMarker != null) { lo.setSelected(zfi.getLayers().contains(layerMarker)); } layerPanel.add(lo, c); } c.fill = GridBagConstraints.BOTH; c.weighty = 1.0f; layerPanel.add(Box.createGlue(), c); } /** * Given a list of LayerObjects, make the visibility of the internal layers * match the list. * * @param layerObjects */ protected void matchObjects(List<LayerObject> layerObjects) { for (LayerObject lo : layerList) { for (LayerObject toMatch : layerObjects) { Layer matchedLayer = toMatch.getLayer(); if (lo.getLayer().equals(matchedLayer)) { boolean turnOn = toMatch.isSelected(); String layerMarker = matchedLayer.getPropertyPrefix(); lo.setSelected(turnOn); if (turnOn) { if (!zfi.getLayers().contains(layerMarker)) { zfi.getLayers().add(layerMarker); } } else { zfi.getLayers().remove(layerMarker); } } } setInclude(!zfi.getLayers().isEmpty()); } } public void matchBounds(List<BoundsObject> bounds) { boundsModel.clear(); boundaries.clear(); for (BoundsObject bo : bounds) { BoundsObject copy = bo.clone(); boundsModel.addElement(copy); boundaries.add(bo.bounds); } } /** * Whether this ZLIF is the active tab in the MapTileMakerComponent. * * @return true if face is active */ protected boolean isActive() { return active; } /** * Set this as the active ZLIF in MapTileMakerComponent. * * @param active */ protected void setActive(boolean active) { this.active = active; } /** * Whether this ZoomLevel should be used in the tile creation or skipped. * * @return if zoom level should be included in tile creation. */ public boolean isInclude() { return include; } /** * Set whether this ZoomLevel should be used in the tile creation. * * @param include */ public void setInclude(boolean include) { this.include = include; // includeButton.setSelected(include); } ZoomLevelMaker getZoomLevelMaker() { return zfi; } void setZoomLevelMaker(ZoomLevelMaker zfi) { this.zfi = zfi; } /** * Called from MapTileMakerComponent if this is the active ZLIF when the * drawing tool completes, assigning the boundary to this ZLIF (or whatever * action is provided). * * @param omg * @param action */ public void handleBoundary(OMGraphic omg, OMAction action) { boundaries.doAction(omg, action); boundsModel.clear(); int count = 1; for (OMGraphic omr : boundaries) { if (omr instanceof OMRect) { String bounding_rectangle = i18n.get(ZoomLevelMakerFace.class, "bounding_rectangle", "Bounding Rectangle"); boundsModel.addElement(new BoundsObject((OMRect) omr, bounding_rectangle + " " + (count++))); } } } /** * Called from the MapTileMakerComponent, so this ZLIF is ready to paint its * boundaries if it is activated. * * @param proj Projection to use to render boundaries. * @return true if boundaries can be generated for given projection */ protected boolean generate(Projection proj) { if (boundaries != null) { return boundaries.generate(proj); } return false; } /** * Called from the MapTileMakerComponent, when this is the active ZLIF so * the current boundaries are painted on top of the map. * * @param graphics */ protected void paintBoundaries(Graphics graphics) { if (boundaries != null) { boundaries.render(graphics); } } /** * Bounds list model for boundary JList. * * @author dietrick */ private final class BoundsListModel extends AbstractListModel { private static final long serialVersionUID = 1L; public int getSize() { return boundsObjectList.size(); } public BoundsObject getElementAt(int index) { return boundsObjectList.get(index); } public void editElement(int index) { fireContentsChanged(this, index, index); } public void insertElement(BoundsObject obj, int index) { boundsObjectList.add(index, obj); fireIntervalAdded(this, index, index); } public void addElement(BoundsObject obj) { int index = getSize(); boundsObjectList.add(obj); fireIntervalAdded(this, index, index); } public BoundsObject removeElementAt(int index) { BoundsObject obj = boundsObjectList.remove(index); fireIntervalRemoved(this, index, index); return obj; } public void clear() { int size = boundsObjectList.size(); boundsObjectList.clear(); fireIntervalRemoved(this, 0, size); } } /** * The list object used to represent a boundary. * * @author dietrick */ public class BoundsObject implements Cloneable { protected OMRect bounds; protected String name; public BoundsObject(OMRect rect, String displayName) { bounds = rect; name = displayName; } public String toString() { return name; } public BoundsObject clone() { OMRect copy = new OMRect(bounds.getNorthLat(), bounds.getWestLon(), bounds.getSouthLat(), bounds.getEastLon(), OMGraphic.LINETYPE_RHUMB); DrawingAttributes atts = DrawingAttributes.getDefaultClone(); atts.setFrom(bounds); atts.setTo(copy); return new BoundsObject(bounds, name); } } /** * A component used to represent a layer/layer setting in the face. * * @author dietrick */ public class LayerObject extends JCheckBox { private static final long serialVersionUID = 1L; protected Layer layer; public LayerObject(Layer layer) { super(layer.getName(), layer.isVisible()); this.layer = layer; addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { String pp = getLayer().getPropertyPrefix(); if (((JCheckBox) ae.getSource()).isSelected()) { if (!zfi.getLayers().contains(pp)) { zfi.getLayers().add(pp); } } else { zfi.getLayers().remove(pp); } if (active /* duh */&& organizer != null) { organizer.shuffleLayers(ZoomLevelMakerFace.this); setInclude(zfi.getLayers() != null && !zfi.getLayers().isEmpty()); } } }); } Layer getLayer() { return layer; } public String toString() { return layer.getName(); } } /** * A class that listens for selections on the boundary list. * * @author dietrick */ private final class SelectionListener implements ListSelectionListener { public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()) { boolean somethingSelected = boundsList.getSelectedIndex() != -1; editBoundaryButton.setEnabled(somethingSelected); deleteBoundaryButton.setEnabled(somethingSelected); } } } /** * A class that listens for double-clicks on the boundary list, launching an * editor for that rectangle. * * @author dietrick */ private class ListMouseListener extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { int index = boundsList.locationToIndex(e.getPoint()); if (index != -1) { BoundsObject selected = (BoundsObject) boundsList.getSelectedValue(); organizer.edit(selected.bounds, null); } } } } public ImageIcon createImageIcon(String path) { URL imgURL = ZoomLevelMakerFace.class.getClassLoader().getResource("com/bbn/openmap/dataAccess/mapTile/" + path); if (imgURL != null) { return new ImageIcon(imgURL); } else { System.err.println("Couldn't find file: " + path); return null; } } }