package com.opendoorlogistics.studio.components.map.plugins.selection;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JMenu;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import com.opendoorlogistics.api.standardcomponents.map.MapActionFactory;
import com.opendoorlogistics.api.standardcomponents.map.MapApi;
import com.opendoorlogistics.api.standardcomponents.map.MapApi.PanelPosition;
import com.opendoorlogistics.api.standardcomponents.map.MapApiListeners;
import com.opendoorlogistics.api.standardcomponents.map.MapApiListeners.OnBuildContextMenu;
import com.opendoorlogistics.api.standardcomponents.map.MapApiListeners.OnBuildToolbarListener;
import com.opendoorlogistics.api.standardcomponents.map.MapApiListeners.OnChangeListener;
import com.opendoorlogistics.api.standardcomponents.map.MapApiListeners.OnObjectsChanged;
import com.opendoorlogistics.api.standardcomponents.map.MapPlugin;
import com.opendoorlogistics.api.standardcomponents.map.MapPopupMenu;
import com.opendoorlogistics.api.standardcomponents.map.MapToolbar;
import com.opendoorlogistics.api.standardcomponents.map.StandardMapMenuOrdering;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.TableFlags;
import com.opendoorlogistics.codefromweb.DropDownMenuButton;
import com.opendoorlogistics.core.tables.utils.TableUtils;
import com.opendoorlogistics.studio.components.map.AbstractMapMode;
import com.opendoorlogistics.studio.components.map.plugins.utils.PluginUtils;
import com.opendoorlogistics.studio.tables.grid.GridEditPermissions;
import com.opendoorlogistics.studio.tables.grid.ODLGridTable;
import com.opendoorlogistics.studio.tables.grid.PreferredColumnWidths;
import com.opendoorlogistics.utils.ui.Icons;
import gnu.trove.set.hash.TLongHashSet;
public class SelectPlugin implements MapPlugin {
public final static long NEEDS_FLAGS = TableFlags.UI_SET_ALLOWED;
private static final ImageIcon SELECTION_TOOLS_ICON = Icons.loadFromStandardPath("select-options.png");
private static final ImageIcon SHOW_ALL_ICON = Icons.loadFromStandardPath("show-all-objects.png");
private static final ImageIcon SHOW_SEL_ONLY = Icons.loadFromStandardPath("show-selected-objects-only.png");
@Override
public String getId() {
return "com.opendoorlogistics.studio.components.map.plugins.SelectPlugin";
}
private ArrayList<Action> buildSubmenuActions(final MapApi api,final SelectionHandler handler) {
final ArrayList<Action> ret = new ArrayList<Action>();
class Helper {
void add(Action action, String name, String description, String iconName, long requiresFlags) {
ret.add(action);
PluginUtils.initAction(name, description, iconName, action);
action.setEnabled(PluginUtils.getIsEnabled(api, requiresFlags));
}
}
Helper helper = new Helper();
helper.add(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
zoomOnSelection(api);
}
}, "Zoom to selection", "Zoom to selected objects", "zoom-selected.png", NEEDS_FLAGS);
helper.add(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
SelectObjectsPopup popup = new SelectObjectsPopup(api, handler.lastSelectObjectsPopup != null ? handler.lastSelectObjectsPopup.getBoundsOnDispose() : null,
handler.prefColumnWidths);
if (handler.lastSelectObjectsPopup == null || handler.lastSelectObjectsPopup.getBoundsOnDispose() == null) {
popup.setLocationRelativeTo(api.getMapWindowComponent());
}
popup.setVisible(true);
handler.lastSelectObjectsPopup = popup;
}
}, "Search and select objects", "Search and select objects", "map-search-object-for-selection.png", NEEDS_FLAGS);
helper.add(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
api.clearSelection();
}
}, "Clear selection", "Clear selection", "map-clear-selection.png", NEEDS_FLAGS);
helper.add(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
long[] ids = api.getSelectedIds();
if (ids == null || ids.length == 0) {
JOptionPane.showMessageDialog(api.getMapWindowComponent(), "No objects are selected");
return;
}
TableUtils.deleteByGlobalId(api.getMapDataApi().getGlobalDatastore(), true, ids);
}
}, "Delete selected objects", "Delete selected objects", "edit-delete-6.png", NEEDS_FLAGS|TableFlags.UI_DELETE_ALLOWED | TableFlags.UI_SET_ALLOWED);
ret.add(handler.unselectedVisibilityHandler.createToggleAction());
return ret;
}
@Override
public void initMap(final MapApi api) {
String groupId = "selectmode";
final SelectionHandler handler = new SelectionHandler(api);
// selection mode
PluginUtils.registerActionFactory(api, new MapActionFactory() {
@Override
public Action create(MapApi api) {
return createSelectionModeAction(api);
}
}, StandardMapMenuOrdering.SELECT_MODE, groupId, NEEDS_FLAGS);
api.registerOnBuildToolbarListener(new OnBuildToolbarListener() {
@Override
public void onBuildToolbar(MapApi api, MapToolbar toolBar) {
JButton subButton = new DropDownMenuButton(SELECTION_TOOLS_ICON) {
@Override
protected JPopupMenu getPopupMenu() {
return createSelectionToolsPopupMenu(handler, api);
}
};
subButton.setToolTipText("Show selection tools popup");
toolBar.add(subButton);
}
}, StandardMapMenuOrdering.SELECT_MODE);
api.registerOnBuildContextMenuListener(new OnBuildContextMenu() {
@Override
public void onBuildContextMenu(MapApi api, MapPopupMenu menu) {
JMenu submenu = new JMenu("Selection tools");
submenu.setIcon(SELECTION_TOOLS_ICON);
for(Action a : buildSubmenuActions(api, handler)){
submenu.add(a);
}
menu.add(submenu);
// menu.add(createSelectionToolsPopupMenu(handler, api));
}
}, StandardMapMenuOrdering.SELECT_MODE);
// // zoom to selection
// PluginUtils.registerActionFactory(api, new ActionFactory() {
//
// @Override
// public Action create(MapApi api) {
// AbstractAction action = new AbstractAction() {
//
// @Override
// public void actionPerformed(ActionEvent e) {
// zoomOnSelection(api);
// }
// };
//
// PluginUtils.initAction("Zoom to selection", "Zoom to selected objects", "zoom-selected.png", action);
// return action;
// }
// }, StandardMapMenuOrdering.SELECT_MODE, groupId, NEEDS_FLAGS);
//
// // search and add to selection
// PluginUtils.registerActionFactory(api, new ActionFactory() {
// @Override
// public Action create(MapApi api) {
// AbstractAction action = new AbstractAction() {
//
// @Override
// public void actionPerformed(ActionEvent e) {
// SelectObjectsPopup popup = new SelectObjectsPopup(api, handler.lastSelectObjectsPopup != null ? handler.lastSelectObjectsPopup.getBoundsOnDispose() : null,
// handler.prefColumnWidths);
// if (handler.lastSelectObjectsPopup == null || handler.lastSelectObjectsPopup.getBoundsOnDispose() == null) {
// popup.setLocationRelativeTo(api.getMapWindowComponent());
// }
// popup.setVisible(true);
// handler.lastSelectObjectsPopup = popup;
// }
// };
//
// PluginUtils.initAction("Search and select objects", "Search and select objects", "map-search-object-for-selection.png", action);
// return action;
// }
// }, StandardMapMenuOrdering.SELECT_MODE, groupId, NEEDS_FLAGS);
//
// // clear selection
// PluginUtils.registerActionFactory(api, new ActionFactory() {
//
// @Override
// public Action create(MapApi api) {
//
// AbstractAction action = new AbstractAction() {
//
// @Override
// public void actionPerformed(ActionEvent e) {
// api.clearSelection();
// }
// };
//
// PluginUtils.initAction("Clear selection", "Clear selection", "map-clear-selection.png", action);
// return action;
// }
// }, StandardMapMenuOrdering.SELECT_MODE, groupId, NEEDS_FLAGS);
//
// // delete selection
// PluginUtils.registerActionFactory(api, new ActionFactory() {
//
// @Override
// public Action create(MapApi api) {
// AbstractAction action = new AbstractAction() {
//
// @Override
// public void actionPerformed(ActionEvent e) {
// long[] ids = api.getSelectedIds();
//
// if (ids == null || ids.length == 0) {
// JOptionPane.showMessageDialog(api.getMapWindowComponent(), "No objects are selected");
// return;
// }
//
// TableUtils.deleteByGlobalId(api.getMapDataApi().getGlobalDatastore(), true, ids);
// }
// };
//
// PluginUtils.initAction("Delete selected objects", "Delete selected objects", "edit-delete-6.png", action);
// return action;
// }
// }, StandardMapMenuOrdering.SELECT_MODE, groupId, TableFlags.UI_DELETE_ALLOWED | TableFlags.UI_SET_ALLOWED);
api.registerSelectionChanged(handler, 0);
// register a listener which unselects everything if selection is no longer allowed
api.registerObjectsChangedListener(new OnObjectsChanged() {
@Override
public void onObjectsChanged(MapApi api) {
if (!PluginUtils.getIsEnabled(api, NEEDS_FLAGS)) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
api.clearSelection();
}
});
}
}
}, 0);
}
private Action createSelectionModeAction(final MapApi api) {
AbstractAction action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
api.setMapMode(new SelectMode(api));
}
};
PluginUtils.initAction("Selection mode", "Select objects on the map.", "cursor-crosshair-small-16x16.png", action);
return action;
}
private JPopupMenu createSelectionToolsPopupMenu(final SelectionHandler handler, MapApi api) {
JPopupMenu popup = new JPopupMenu();
for(Action a : buildSubmenuActions(api, handler)){
popup.add(a);
}
return popup;
}
private static void zoomOnSelection(MapApi api) {
ODLTableReadOnly table = api.getMapDataApi().getActiveTableSelectedOnly();
if (table != null && table.getRowCount() > 0) {
api.setViewToBestFit(table);
}
}
private static class SelectMode extends AbstractMapMode {
private final MapApi api;
public SelectMode(MapApi api) {
this.api = api;
}
@Override
public Cursor getCursor() {
return PluginUtils.createCursor("cursor-crosshair-small.png", 11, 11);
}
@Override
public void onObjectsChanged(MapApi api) {
PluginUtils.exitModeIfNeeded(api, SelectMode.class, NEEDS_FLAGS, true);
}
@Override
public void paint(MapApi api, Graphics2D g) {
Rectangle rectangle = getDragRectangle();
if (rectangle != null) {
Color fillColour = new Color(128, 192, 255, 100);
PluginUtils.drawRectangle(g, rectangle, fillColour);
}
}
@Override
public void mouseDragged(MouseEvent evt) {
super.mouseDragged(evt);
api.repaint(true);
}
@Override
public void mouseReleased(MouseEvent e) {
if (!SwingUtilities.isLeftMouseButton(e)) {
return;
}
Rectangle rectangle = getDragRectangle();
super.mouseReleased(e);
TLongHashSet set = new TLongHashSet();
if (rectangle != null) {
long[] ids = api.getSelectableIdsWithinPixelRectangle(rectangle);
set.addAll(ids);
// check for ctrl
if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0) {
ids = api.getSelectedIds();
if (ids != null) {
set.addAll(ids);
}
}
}
// changing the selection state will call a full repaint
api.setSelectedIds(set.toArray());
}
}
private static class SelectionHandler implements OnChangeListener {
TablesPanel panel;
SelectObjectsPopup lastSelectObjectsPopup;
final UnselectedVisibilityHandler unselectedVisibilityHandler;
final JCheckBox box;
final MapApi api;
final PreferredColumnWidths prefColumnWidths = new PreferredColumnWidths();
public SelectionHandler(MapApi api) {
this.api = api;
unselectedVisibilityHandler = new UnselectedVisibilityHandler(api);
box = new JCheckBox("Sel list", true);
box.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
onChanged(api);
}
});
api.registerOnBuildToolbarListener(new OnBuildToolbarListener() {
@Override
public void onBuildToolbar(MapApi api, MapToolbar toolBar) {
toolBar.add(box, "mapmode");
}
}, StandardMapMenuOrdering.SHOW_SELECTED);
}
@Override
public void onChanged(MapApi api) {
long[] selected = api.getSelectedIds();
boolean hasSel = selected != null && selected.length > 0 && box.isSelected();
if (panel == null && hasSel) {
panel = new TablesPanel(api.getMapDataApi().getGlobalDatastore()) {
@Override
public void dispose() {
panel = null;
}
};
api.setSidePanel(panel, PanelPosition.BOTTOM);
} else if (panel != null && !hasSel) {
// disposable callback will set panel to null
api.setSidePanel(null, PanelPosition.BOTTOM);
}
if (panel != null) {
setSelectedRows(panel, api.getSelectedIds());
}
}
void setSelectedRows(TablesPanel panel, long[] visible) {
TLongHashSet filtered = new TLongHashSet(visible);
panel.filteredDs.update(filtered, false);
panel.updateTableTabs(false, new ODLGridTableFactory() {
@Override
public ODLGridTable createODLGridTable(int tableId) {
return new ODLGridTable(panel.filteredDs, tableId, true, null, api.getMapDataApi().getGlobalDatastore(), new GridEditPermissions(true, false, false, false, false),
prefColumnWidths);
};
});
}
}
private static class UnselectedVisibilityHandler implements MapApiListeners.FilterVisibleObjects, MapApiListeners.OnChangeListener{
private volatile boolean active;
private final MapApi api;
UnselectedVisibilityHandler(MapApi mapApi){
this.api = mapApi;
api.registerFilterVisibleObjectsListener(this, 0);
api.registerSelectionChanged(this, 0);
}
@Override
public boolean acceptObject(ODLTableReadOnly table, int row) {
return active==false|| api.isSelectedId(table.getRowId(row));
}
@Override
public void onChanged(MapApi api) {
// selection has changed
if(active){
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
api.updateObjectFiltering();
}
});
}
}
public Action createToggleAction(){
final boolean wasActive= active;
return new AbstractAction(wasActive ? "Unhide unselected objects" : "Hide unselected objects",
wasActive? SHOW_ALL_ICON : SHOW_SEL_ONLY) {
@Override
public void actionPerformed(ActionEvent e) {
active = !wasActive;
api.updateObjectFiltering();
}
};
}
@Override
public void startFilter(MapApi api, ODLDatastore<? extends ODLTable> newMapDatastore) {
// TODO Auto-generated method stub
}
@Override
public void endFilter(MapApi api) {
// TODO Auto-generated method stub
}
}
}