/*
* 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.ArrayList;
import java.util.List;
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.locale.LocaleUtils;
import org.geotools.swing.MapPane;
import org.geotools.swing.dialog.AbstractSimpleDialog;
import org.geotools.swing.dialog.DialogUtils;
import org.geotools.util.logging.Logging;
/**
* A status bar that works with a map pane to display cursor coordinates and
* other data. The static {@linkplain #createDefaultStatusBar} method can be
* used for the most common configuration.
*
* @see StatusBarItem
*
* @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 String CONFIGURE_TOOL_TIP =
LocaleUtils.getValue("StatusBar", "ConfigureTooltip");
private static final String SET_DECIMALS_STRING =
LocaleUtils.getValue("StatusBar", "ConfigureSetNumDecimals");
private static final String DECIMAL_DIALOG_TITLE =
LocaleUtils.getValue("StatusBar", "ConfigureDecimalDialogTitle");
private static final String DECIMAL_DIALOG_LABEL =
LocaleUtils.getValue("StatusBar", "ConfigureDecimalDialogLabel");
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 JPopupMenu configMenu;
/*
* Stores item references and state.
*/
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 List<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>cursor coordinate item</li>
* <li>map extent item</li>
* <li>coordinate reference system item</li>
* <li>rendering activity 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 JRendererStatusBarItem(mapPane), false, true );
statusBar.addItem( new JCoordsStatusBarItem(mapPane) );
statusBar.addItem( new JExtentStatusBarItem(mapPane) );
statusBar.addItem( new JCRSStatusBarItem(mapPane) );
return statusBar;
}
/**
* Creates a new status bar. Sets a {@code MigLayout} layout manager and
* adds the default config menu status item.
*/
public JMapStatusBar() {
this.itemInfo = new ArrayList<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() {
if (configMenu == null) {
configMenu = createItemMenu();
}
return configMenu;
}
};
StatusBarItem item = new JMenuStatusBarItem("", icon,
CONFIGURE_TOOL_TIP, menuProvider);
addItem(item, false, true);
}
/**
* Adds a new item to the status bar. The item will display a border
* and appear in the status bar configuration menu. If the item is
* already present in the status bar it will not added again and the
* method will return {@code false}.
*
* @param item the item to add
* @return {@code true} if the item was added
*/
public boolean addItem(StatusBarItem item) {
return addItem(item, true, true);
}
/**
* Adds a new item to the status bar. If the item is already present in
* the status bar it will not added again and the method will return
* {@code false}.
*
* @param item the item to add
* @param configurable whether the item should appear in the status bar
* configuration menu
* @param showing whether the item should be shown initially
*
* @return {@code true} if the item was added
*/
public boolean addItem(StatusBarItem item, boolean configurable,
boolean showing) {
if (findItem(item) < 0) {
ItemInfo info = new ItemInfo(item, configurable, getComponentCount(), showing);
itemInfo.add(info);
if (showing) {
add(item);
}
int h = item.getMinimumHeight();
if (h > minItemHeight) {
minItemHeight = h;
setMinimumSize(new Dimension(-1, minItemHeight));
}
// Set the status bar config menu to null so that it will
// be re-created when next requested.
configMenu = null;
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;
}
}
/**
* Gets the number of items in this status bar including the default
* configuration menu item.
*
* @return number of status bar items
*/
public int getNumItems() {
return itemInfo.size();
}
/**
* Searches for the given item in the current set of status bar items.
* If found, the item's position index is returned; otherwise -1.
*
* @param item the item to search for
* @return position index or -1 if not found
*/
public int findItem(StatusBarItem item) {
if (item == null) {
throw new IllegalArgumentException("item must not be null");
}
for (int i = 0; i < itemInfo.size(); i++) {
if (itemInfo.get(i).item == item) {
return i;
}
}
return -1;
}
/**
* Gets the item at the specified position index. Position 0 is
* always occupied by the status bar's configuration menu item.
*
* @param index position index between 0 and {@link #getNumItems()} - 1
* @return the item
* @throws IndexOutOfBoundsException on invalid {@code index} value
*/
public StatusBarItem getItem(int index) {
if (index >= 0 && index < getNumItems()) {
return itemInfo.get(index).item;
}
throw new IndexOutOfBoundsException("Invalid item index: " + index);
}
/**
* Creates a popup menu to configure the status bar. The names of configurable
* items will appear as checkbox menu items to set whether they are shown
* or hidden. An additional item allows a custom number of decimal places
* to be set for numeric items.
*
* @return the new pop-up menu
*/
private JPopupMenu createItemMenu() {
JPopupMenu menu = new JPopupMenu();
// Add menu items to toggle display of status bar elements
for (final ItemInfo info : itemInfo) {
if (info.configurable) {
JMenuItem menuItem = new JCheckBoxMenuItem(info.item.getName(), info.showing);
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
info.showing = !info.showing;
Rectangle r = info.item.getBounds();
if (info.showing) {
add(info.item, info.componentIndex);
} else {
remove(info.item);
}
revalidate();
repaint(r);
}
});
menu.add(menuItem);
}
}
menu.addSeparator();
JMenuItem menuItem = new JMenuItem(SET_DECIMALS_STRING);
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 info : itemInfo) {
info.item.setNumDecimals(numDecimalDigits);
}
}
}
private static class DecimalDigitsDialog extends AbstractSimpleDialog {
private JIntegerField digitsFld;
private int numDigits;
public DecimalDigitsDialog(int initialValue) {
super(DECIMAL_DIALOG_TITLE);
numDigits = initialValue;
initComponents();
}
@Override
public JPanel createControlPanel() {
JPanel panel = new JPanel(new MigLayout());
panel.add(new JLabel(DECIMAL_DIALOG_LABEL), "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();
}
}
}