/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.swing.control;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import net.miginfocom.swing.MigLayout;
import org.geotools.swing.MapPane;
import org.geotools.swing.dialog.AbstractModalDialog;
import org.geotools.swing.dialog.DialogUtils;
import org.geotools.util.logging.Logging;
/**
* A status bar that works with a map pane.
*
* @author Michael Bedward
* @since 8.0
* @source $URL$
* @version $Id$
*/
public class JMapStatusBar extends JPanel {
private static final Logger LOGGER = Logging.getLogger("org.geotools.swing");
private static final int INSET = 0;
// Package-private constants for use by StatusBarItem classes
static final Font DEFAULT_FONT = new Font("Courier", Font.PLAIN, 12);
static final int DEFAULT_NUM_DECIMAL_DIGITS = 2;
private int numDecimalDigits = DEFAULT_NUM_DECIMAL_DIGITS;
private static class ItemInfo {
final StatusBarItem item;
final boolean configurable;
final int componentIndex;
boolean showing;
public ItemInfo(StatusBarItem item,
boolean configurable,
int componentIndex,
boolean showing) {
this.item = item;
this.configurable = configurable;
this.componentIndex = componentIndex;
this.showing = showing;
}
}
private final Map<Integer, ItemInfo> itemInfo;
private int minItemHeight;
/**
* Creates a new status bar, with the default set of items, linked to
* the given map pane. This method can be called safely from any thread.
*
* The default items are:
* <ul>
* <li>rendering activity item</li>
* <li>cursor coordinate item</li>
* <li>map extent item</li>
* </ul>
*
* @param mapPane the map pane linked to the status bar
*
* @return a new status bar
*
* @throws IllegalArgumentException if {@code mapPane} is {@code null}
*/
public static JMapStatusBar createDefaultStatusBar(final MapPane mapPane) {
final JMapStatusBar[] statusBar = new JMapStatusBar[1];
if (SwingUtilities.isEventDispatchThread()) {
statusBar[0] = doCreateDefaultStatusBar(mapPane);
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
statusBar[0] = doCreateDefaultStatusBar(mapPane);
}
});
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
return statusBar[0];
}
/**
* Helper method for {@linkplain #createDefaultStatusBar}.
*
* @param mapPane the map pane linked to the new status bar
*
* @return a new status bar
*
* @throws IllegalArgumentException if {@code mapPane} is {@code null}
*/
private static JMapStatusBar doCreateDefaultStatusBar(MapPane mapPane) {
JMapStatusBar statusBar = new JMapStatusBar();
statusBar.addItem( new CoordsStatusBarItem(mapPane) );
statusBar.addItem( new ExtentStatusBarItem(mapPane) );
statusBar.addItem( new CRSStatusBarItem(mapPane) );
return statusBar;
}
public JMapStatusBar() {
this.itemInfo = new HashMap<Integer, ItemInfo>();
setLayout(new MigLayout("insets " + INSET));
setBackground(new Color(224, 224, 224));
setFont(DEFAULT_FONT);
URL url = this.getClass().getResource("icons/configure-3.png");
ImageIcon icon = new ImageIcon(url);
PopupMenuProvider menuProvider = new PopupMenuProvider() {
@Override
public JPopupMenu getMenu() {
return createItemMenu();
}
};
StatusBarItem item = new MenuStatusBarItem("", icon, menuProvider);
addItem(item, false, true);
}
public boolean addItem(StatusBarItem item) {
return addItem(item, true, true);
}
public boolean addItem(StatusBarItem item, boolean configurable, boolean showing) {
if (!itemInfo.containsKey(item.getID())) {
ItemInfo ie = new ItemInfo(item, configurable, getComponentCount(), showing);
itemInfo.put(item.getID(), ie);
if (showing) {
add(item);
}
int h = item.getMinimumHeight();
if (h > minItemHeight) {
minItemHeight = h;
setMinimumSize(new Dimension(-1, minItemHeight));
}
return true;
} else {
LOGGER.log(Level.WARNING,
"Item label:{0} id:{1} is already in the status bar",
new Object[]{item.getName(), item.getID()});
return false;
}
}
private JPopupMenu createItemMenu() {
JPopupMenu menu = new JPopupMenu();
// Add menu items to toggle display of status bar elements
for (Entry<Integer, ItemInfo> entry : itemInfo.entrySet()) {
final ItemInfo el = entry.getValue();
if (el.configurable) {
JMenuItem menuItem = new JCheckBoxMenuItem(el.item.getName(), el.showing);
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
el.showing = !el.showing;
Rectangle r = el.item.getBounds();
if (el.showing) {
add(el.item, el.componentIndex);
} else {
remove(el.item);
}
revalidate();
repaint(r);
}
});
menu.add(menuItem);
}
}
menu.addSeparator();
JMenuItem menuItem = new JMenuItem("Set number decimal digits...");
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setNumDecimals();
}
});
menu.add(menuItem);
return menu;
}
private void setNumDecimals() {
DecimalDigitsDialog dialog = new DecimalDigitsDialog(numDecimalDigits);
DialogUtils.showCentred(dialog);
int n = dialog.getNumDigits();
if (n >= 0) {
numDecimalDigits = n;
for (ItemInfo ie : itemInfo.values()) {
ie.item.setNumDecimals(numDecimalDigits);
}
}
}
private static class DecimalDigitsDialog extends AbstractModalDialog {
private JIntegerField digitsFld;
private int numDigits;
public DecimalDigitsDialog(int initialValue) {
super("Status bar value format");
numDigits = initialValue;
initComponents();
}
@Override
public JPanel createControlPanel() {
JPanel panel = new JPanel(new MigLayout());
panel.add(new JLabel("Decimal digits:"), "gap related");
digitsFld = new JIntegerField(numDigits, false);
panel.add(digitsFld, "w 40!");
return panel;
}
public int getNumDigits() {
return numDigits;
}
@Override
public void onOK() {
numDigits = digitsFld.getValue();
closeDialog();
}
}
}