// License: GPL. See LICENSE file for details.
package org.openstreetmap.josm.gui;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JToolBar;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.mapmode.DeleteAction;
import org.openstreetmap.josm.actions.mapmode.DrawAction;
import org.openstreetmap.josm.actions.mapmode.ExtrudeAction;
import org.openstreetmap.josm.actions.mapmode.MapMode;
import org.openstreetmap.josm.actions.mapmode.SelectAction;
import org.openstreetmap.josm.actions.mapmode.ZoomAction;
import org.openstreetmap.josm.gui.dialogs.ChangesetDialog;
import org.openstreetmap.josm.gui.dialogs.CommandStackDialog;
import org.openstreetmap.josm.gui.dialogs.ConflictDialog;
import org.openstreetmap.josm.gui.dialogs.DialogsPanel;
import org.openstreetmap.josm.gui.dialogs.FilterDialog;
import org.openstreetmap.josm.gui.dialogs.HistoryDialog;
import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
import org.openstreetmap.josm.gui.dialogs.RelationListDialog;
import org.openstreetmap.josm.gui.dialogs.SelectionListDialog;
import org.openstreetmap.josm.gui.dialogs.ToggleDialog;
import org.openstreetmap.josm.gui.dialogs.UserListDialog;
import org.openstreetmap.josm.gui.dialogs.properties.PropertiesDialog;
import org.openstreetmap.josm.tools.Destroyable;
/**
* One Map frame with one dataset behind. This is the container gui class whose
* display can be set to the different views.
*
* @author imi
*/
public class MapFrame extends JPanel implements Destroyable {
/**
* The current mode, this frame operates.
*/
public MapMode mapMode;
/**
* The view control displayed.
*/
public MapView mapView;
/**
* The toolbar with the action icons. To add new toggle dialog actions, use addToggleDialog
* instead of adding directly to this list. To add a new mode use addMapMode.
*/
private JToolBar toolBarActions = new JToolBar(JToolBar.VERTICAL);
private JToolBar toolBarToggle = new JToolBar(JToolBar.VERTICAL);
/**
* The status line below the map
*/
public MapStatus statusLine;
public ConflictDialog conflictDialog;
public FilterDialog filterDialog;
/**
* The dialog that shows all relations and lets the user edit them.
*/
public RelationListDialog relationListDialog;
/**
* The panel list of all toggle dialog icons. To add new toggle dialog actions, use addToggleDialog
* instead of adding directly to this list.
*/
private List<ToggleDialog> allDialogs = new ArrayList<ToggleDialog>();
private final DialogsPanel dialogsPanel;
public final ButtonGroup toolGroup = new ButtonGroup();
/**
* Default width of the toggle dialog area.
*/
public final int DEF_TOGGLE_DLG_WIDTH = 330;
public MapFrame() {
setSize(400,400);
setLayout(new BorderLayout());
mapView = new MapView();
new FileDrop(mapView);
// show menu entry
Main.main.menu.viewMenu.setVisible(true);
// toolbar
toolBarActions.setFloatable(false);
addMapMode(new IconToggleButton(new SelectAction(this)));
addMapMode(new IconToggleButton(new DrawAction(this)));
addMapMode(new IconToggleButton(new ExtrudeAction(this)));
addMapMode(new IconToggleButton(new ZoomAction(this)));
addMapMode(new IconToggleButton(new DeleteAction(this)));
toolGroup.setSelected(((AbstractButton)toolBarActions.getComponent(0)).getModel(), true);
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
dialogsPanel = new DialogsPanel(splitPane);
splitPane.setLeftComponent(mapView);
splitPane.setRightComponent(dialogsPanel);
/**
* All additional space goes to the mapView
*/
splitPane.setResizeWeight(1.0);
/**
* Some beautifications.
*/
splitPane.setDividerSize(5);
splitPane.setBorder(null);
splitPane.setUI(new BasicSplitPaneUI() {
@Override
public BasicSplitPaneDivider createDefaultDivider() {
return new BasicSplitPaneDivider(this) {
@Override
public void setBorder(Border b) {
}
};
}
});
add(splitPane, BorderLayout.CENTER);
dialogsPanel.setLayout(new BoxLayout(dialogsPanel, BoxLayout.Y_AXIS));
dialogsPanel.setPreferredSize(new Dimension(Main.pref.getInteger("toggleDialogs.width",DEF_TOGGLE_DLG_WIDTH), 0));
dialogsPanel.setMinimumSize(new Dimension(24, 0));
mapView.setMinimumSize(new Dimension(10,0));
toolBarToggle.setFloatable(false);
LayerListDialog.createInstance(this);
addToggleDialog(LayerListDialog.getInstance());
addToggleDialog(new PropertiesDialog(this));
addToggleDialog(new SelectionListDialog());
addToggleDialog(relationListDialog = new RelationListDialog());
addToggleDialog(new CommandStackDialog(this));
addToggleDialog(new UserListDialog());
addToggleDialog(new HistoryDialog());
addToggleDialog(conflictDialog = new ConflictDialog());
if(Main.pref.getBoolean("displayfilter", true)) {
addToggleDialog(filterDialog = new FilterDialog());
}
addToggleDialog(new ChangesetDialog(this));
// status line below the map
statusLine = new MapStatus(this);
}
public void selectSelectTool(boolean onlyIfModeless) {
if(onlyIfModeless && !Main.pref.getBoolean("modeless", false))
return;
selectMapMode((MapMode)getDefaultButtonAction());
}
public void selectDrawTool(boolean onlyIfModeless) {
if(onlyIfModeless && !Main.pref.getBoolean("modeless", false))
return;
Action drawAction = ((AbstractButton)toolBarActions.getComponent(1)).getAction();
selectMapMode((MapMode)drawAction);
}
/**
* Called as some kind of destructor when the last layer has been removed.
* Delegates the call to all Destroyables within this component (e.g. MapModes)
*/
public void destroy() {
dialogsPanel.destroy();
for (int i = 0; i < toolBarActions.getComponentCount(); ++i)
if (toolBarActions.getComponent(i) instanceof Destroyable) {
((Destroyable)toolBarActions.getComponent(i)).destroy();
}
for (int i = 0; i < toolBarToggle.getComponentCount(); ++i)
if (toolBarToggle.getComponent(i) instanceof Destroyable) {
((Destroyable)toolBarToggle.getComponent(i)).destroy();
}
// remove menu entries
Main.main.menu.viewMenu.setVisible(false);
// MapFrame gets destroyed when the last layer is removed, but the status line background
// thread that collects the information doesn't get destroyed automatically.
if(statusLine.thread != null) {
try {
statusLine.thread.interrupt();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public Action getDefaultButtonAction() {
return ((AbstractButton)toolBarActions.getComponent(0)).getAction();
}
/**
* Open all ToggleDialogs that have their preferences property set. Close all others.
*/
public void initializeDialogsPane() {
dialogsPanel.initialize(allDialogs);
}
/**
* Call this to add new toggle dialogs to the left button-list
* @param dlg The toggle dialog. It must not be in the list already.
*/
public IconToggleButton addToggleDialog(ToggleDialog dlg) {
IconToggleButton button = new IconToggleButton(dlg.getToggleAction());
toolBarToggle.add(button);
allDialogs.add(dlg);
if (dialogsPanel.initialized) {
dialogsPanel.add(dlg);
}
return button;
}
public void addMapMode(IconToggleButton b) {
toolBarActions.add(b);
toolGroup.add(b);
}
/**
* Fires an property changed event "visible".
*/
@Override public void setVisible(boolean aFlag) {
boolean old = isVisible();
super.setVisible(aFlag);
if (old != aFlag) {
firePropertyChange("visible", old, aFlag);
}
}
/**
* Change the operating map mode for the view. Will call unregister on the
* old MapMode and register on the new one.
* @param mapMode The new mode to set.
*/
public void selectMapMode(MapMode newMapMode) {
MapMode oldMapMode = this.mapMode;
if (newMapMode == oldMapMode)
return;
if (oldMapMode != null) {
oldMapMode.exitMode();
}
this.mapMode = newMapMode;
newMapMode.enterMode();
fireMapModeChanged(oldMapMode, newMapMode);
}
/**
* Fill the given panel by adding all necessary components to the different
* locations.
*
* @param panel The container to fill. Must have an BorderLayout.
*/
public void fillPanel(Container panel) {
panel.add(this, BorderLayout.CENTER);
JToolBar jb = new JToolBar(JToolBar.VERTICAL);
jb.setFloatable(false);
jb.add(toolBarActions);
jb.addSeparator(new Dimension(0,10));
jb.add(toolBarToggle);
if(Main.pref.getBoolean("sidetoolbar.visible", true))
{
if(Main.pref.getBoolean("sidetoolbar.scrollable", true)) {
final ScrollViewport svp = new ScrollViewport(jb, ScrollViewport.VERTICAL_DIRECTION);
panel.add(svp, BorderLayout.WEST);
jb.addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
svp.scroll(0,e.getUnitsToScroll() * 5);
}
});
} else {
panel.add(jb, BorderLayout.WEST);
}
}
if (statusLine != null && Main.pref.getBoolean("statusline.visible", true)) {
panel.add(statusLine, BorderLayout.SOUTH);
}
}
/**
* Replies the instance of a toggle dialog of type <code>type</code> managed by this
* map frame
*
* @param <T>
* @param type the class of the toggle dialog, i.e. UserListDialog.class
* @return the instance of a toggle dialog of type <code>type</code> managed by this
* map frame; null, if no such dialog exists
*
*/
public <T> T getToggleDialog(Class<T> type) {
return dialogsPanel.getToggleDialog(type);
}
/**
* Returns the current width of the (possibly resized) toggle dialog area
*/
public int getToggleDlgWidth() {
return dialogsPanel.getWidth();
}
/**
* Interface to notify listeners of the change of the mapMode.
*/
public interface MapModeChangeListener {
void mapModeChange(MapMode oldMapMode, MapMode newMapMode);
}
/**
* the mapMode listeners
*/
private static final CopyOnWriteArrayList<MapModeChangeListener> mapModeChangeListeners = new CopyOnWriteArrayList<MapModeChangeListener>();
/**
* Adds a mapMode change listener
*
* @param listener the listener. Ignored if null or already registered.
*/
public static void addMapModeChangeListener(MapModeChangeListener listener) {
if (listener != null) {
mapModeChangeListeners.addIfAbsent(listener);
}
}
/**
* Removes a mapMode change listener
*
* @param listener the listener. Ignored if null or already registered.
*/
public static void removeMapModeChangeListener(MapModeChangeListener listener) {
mapModeChangeListeners.remove(listener);
}
protected static void fireMapModeChanged(MapMode oldMapMode, MapMode newMapMode) {
for (MapModeChangeListener l : mapModeChangeListeners) {
l.mapModeChange(oldMapMode, newMapMode);
}
}
}