package com.opendoorlogistics.studio.components.map.plugins; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JInternalFrame; import javax.swing.JPanel; import com.opendoorlogistics.api.Tables; 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.MapDataApi; import com.opendoorlogistics.api.standardcomponents.map.MapPlugin; 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.ui.Disposable; import com.opendoorlogistics.core.gis.map.Legend; import com.opendoorlogistics.core.gis.map.Legend.LegendDrawableTableBuilder; import com.opendoorlogistics.core.utils.strings.StandardisedStringTreeMap; import com.opendoorlogistics.core.utils.ui.SwingUtils; import com.opendoorlogistics.studio.components.map.plugins.selection.SelectPlugin; import com.opendoorlogistics.studio.components.map.plugins.utils.PluginUtils; import com.opendoorlogistics.studio.controls.checkboxtable.CheckBoxItem; import com.opendoorlogistics.studio.controls.checkboxtable.CheckBoxItemImpl; import com.opendoorlogistics.studio.controls.checkboxtable.CheckboxTable; import com.opendoorlogistics.studio.controls.checkboxtable.CheckboxTable.ButtonClickedListener; import com.opendoorlogistics.studio.controls.checkboxtable.CheckboxTable.CheckChangedListener; import com.opendoorlogistics.utils.ui.Icons; import com.opendoorlogistics.utils.ui.SimpleAction; import gnu.trove.set.hash.TLongHashSet; public class LegendPlugin implements MapPlugin { private static class LegendState { final List<Map.Entry<String, BufferedImage>> items; final StandardisedStringTreeMap<Boolean> visible; boolean isVisible(String legendKey){ if(legendKey!=null){ Boolean b = visible.get(legendKey); if(b!=null){ return b; } } return true; } void setVisible(String legendKey, boolean visible){ this.visible.put(legendKey, visible); } public LegendState(LegendDrawableTableBuilder builder, LegendState oldState) { items = builder.build(new Dimension(20,20)); visible = new StandardisedStringTreeMap<Boolean>(true); for(Map.Entry<String, BufferedImage> item : items){ // visible by default boolean isVisible = true; // use old state if we have it if(oldState!=null){ isVisible = oldState.isVisible(item.getKey()); } this.visible.put(item.getKey(), isVisible); } } } @Override public String getId(){ return "com.opendoorlogistics.studio.components.map.plugins.LegendPlugin"; } @Override public void initMap(final MapApi api) { final LegendHandler handler = new LegendHandler(); PluginUtils.registerActionFactory(api, new MapActionFactory() { @Override public Action create(MapApi api) { return handler.createAction(api); } }, StandardMapMenuOrdering.LEGEND, "legend"); } private static class LegendHandler{ LegendPanel panel; Action createAction(final MapApi api){ return new SimpleAction("Show legend", "Show / hide legend", "legend-16x16.png") { @Override public void actionPerformed(ActionEvent e) { if(panel == null){ panel = new LegendPanel(api){ @Override public void dispose() { super.dispose(); panel = null; } }; api.setSidePanel(panel, PanelPosition.RIGHT); } else{ // disposable callback will set panel to null api.setSidePanel(null, PanelPosition.RIGHT); } } }; } } private static class LegendPanel extends JPanel implements Disposable , CheckChangedListener, ButtonClickedListener, MapApiListeners.FilterVisibleObjects, MapApiListeners.OnObjectsChanged{ private static final Color LEGEND_BACKGROUND_COLOUR = new Color(240, 240, 240); private final CheckboxTable legendFilterTable; private final MapApi api; private volatile LegendState state; LegendPanel(MapApi api){ this.api = api; setLayout(new BorderLayout()); // init table legendFilterTable = new CheckboxTable(new Icon[]{Icons.loadFromStandardPath("legend-zoom-best.png"), Icons.loadFromStandardPath("select.png")},new Dimension(20, 20), new ArrayList<CheckBoxItem>()); legendFilterTable.addCheckChangedListener(this); legendFilterTable.addButtonClickedListener(this); legendFilterTable.setPreferredSize(new Dimension(150, 150)); add(legendFilterTable, BorderLayout.CENTER); // add in a scrollpane // JScrollPane scroller = new JScrollPane(legendFilterTable); // add(scroller, BorderLayout.CENTER); // scroller.setPreferredSize(new Dimension(150, 150)); setBackground(LEGEND_BACKGROUND_COLOUR); setAlignmentX(JInternalFrame.LEFT_ALIGNMENT); JPanel showHidePanel = new JPanel(); showHidePanel.setLayout(new GridLayout(1, 2)); showHidePanel.add(new JButton(new AbstractAction("Show all") { @Override public void actionPerformed(ActionEvent e) { legendFilterTable.showHideAll(true); } })); showHidePanel.add(new JButton(new AbstractAction("Hide all") { @Override public void actionPerformed(ActionEvent e) { legendFilterTable.showHideAll(false); } })); add(showHidePanel , BorderLayout.SOUTH); // updateLegend(api.getMapDataApi().getMapDatastore()); api.registerFilterVisibleObjectsListener(this, 0); api.registerObjectsChangedListener(this, 0); onObjectsChanged(api); api.updateObjectFiltering(); } @Override public void dispose() { api.removeFilterVisibleObjectsListener(this); api.removeObjectsChangedListener(this); api.updateObjectFiltering(); } @Override public void buttonClicked(CheckBoxItem item, int buttonColumn) { abstract class ParseLegendItems{ void parse(boolean activeTableOnly){ MapDataApi mdApi = api.getMapDataApi(); ODLTableReadOnly all = activeTableOnly? mdApi.getUnfilteredActiveTable() :mdApi.getUnfilteredAllLayersTable(true); int n = all!=null ? all.getRowCount() : 0; for(int i =0 ; i < n ; i++){ String key =Legend.getStandardisedLegendKey(all, i); if(api.getControlLauncherApi().getApi().stringConventions().equalStandardised(key, item.getText())){ parseItem(all, i); } } } abstract void parseItem(ODLTableReadOnly table, int row); } if(buttonColumn==0){ // set view to the items Tables tablesApi = api.getControlLauncherApi().getApi().tables(); final ODLTable copy = tablesApi.createTable(api.getMapDataApi().getUnfilteredAllLayersTable(true)); new ParseLegendItems() { @Override void parseItem(ODLTableReadOnly table, int row) { tablesApi.copyRow(table, row, copy); } }.parse(false); api.setViewToBestFit(copy); } else if(buttonColumn == 1){ ODLTableReadOnly drawables = api.getMapDataApi().getUnfilteredActiveTable(); if(drawables==null || ( (drawables.getFlags() & SelectPlugin.NEEDS_FLAGS)!=SelectPlugin.NEEDS_FLAGS)){ // check we can select things return; } // select the items final TLongHashSet ids = new TLongHashSet(); new ParseLegendItems() { @Override void parseItem(ODLTableReadOnly table, int row) { ids.add(table.getRowId(row)); } }.parse(true); api.setSelectedIds(ids.toArray()); } } @Override public void checkStateChanged() { // read the check states into the state if(state!=null){ for(CheckBoxItem item : legendFilterTable.getItems()){ state.setVisible(item.getText(), item.isSelected()); } } api.updateObjectFiltering(); } @Override public boolean acceptObject(ODLTableReadOnly table, int row) { String s = Legend.getStandardisedLegendKey(table, row); if(s==null){ return true; } if(state!=null){ return state.isVisible(s); } return true; } @Override public void startFilter(MapApi api, ODLDatastore<? extends ODLTable> newMapDatastore) { } @Override public void endFilter(MapApi api) { } @Override public void onObjectsChanged(MapApi api) { // get immutable table on EDT thread ODLTableReadOnly table = api.getMapDataApi().getUnfilteredAllLayersTable(true); api.submitWork(new Runnable() { @Override public void run() { // create new state on worker thread LegendDrawableTableBuilder builder = new LegendDrawableTableBuilder(); int n = table.getRowCount(); for(int i =0 ; i < n ; i++){ builder.processRow(table, i); } // create state using old state if we have it, doing an atomic set // to the state variable so nobody sees a half-set state state = new LegendState(builder, state); // finally update the control on the EDT SwingUtils.invokeLaterOnEDT(new Runnable() { @Override public void run() { // build checkbox items ArrayList<CheckBoxItem> newItems = new ArrayList<>(); if(state!=null){ for (Map.Entry<String, BufferedImage> item : state.items) { // create new checkbox item CheckBoxItemImpl cbItem = new CheckBoxItemImpl(item.getValue(), item.getKey()); cbItem.setSelected(state.isVisible(item.getKey())); newItems.add(cbItem); } } // update the table by setting the new items legendFilterTable.setItems(newItems); } }); } }); // // get immutable table // ODLTableReadOnly table = api.getMapDataApi().getUnfilteredAllLayersTable(true); // LegendDrawableTableBuilder builder = new LegendDrawableTableBuilder(); // int n = table.getRowCount(); // for(int i =0 ; i < n ; i++){ // builder.processRow(table, i); // } // // // create state using old state if we have it // state = new LegendState(builder, state); // // // build checkbox items // ArrayList<CheckBoxItem> newItems = new ArrayList<>(); // if(state!=null){ // for (Map.Entry<String, BufferedImage> item : state.items) { // // // create new checkbox item // CheckBoxItemImpl cbItem = new CheckBoxItemImpl(item.getValue(), item.getKey()); // cbItem.setSelected(state.isVisible(item.getKey())); // newItems.add(cbItem); // // } // } // // // update the table by setting the new items // legendFilterTable.setItems(newItems); } } }