package com.opendoorlogistics.studio.components.map.plugins; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JMenu; import javax.swing.JPopupMenu; import com.opendoorlogistics.api.standardcomponents.map.MapApi; 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.OnDisposedListener; 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.codefromweb.DropDownMenuButton; import com.opendoorlogistics.utils.ui.Icons; public class ViewSyncer implements MapPlugin, OnChangeListener{ private HashMap<MapApi,SyncerState> states = new HashMap<MapApi,ViewSyncer.SyncerState>(); private boolean syncOngoing; private static final ImageIcon SYNCED_ICON = Icons.loadFromStandardPath("map-synced.png"); private static final ImageIcon UNSYNCED_ICON = Icons.loadFromStandardPath("map-unsynced.png"); @Override public String getId() { return "com.opendoorlogistics.studio.components.plugins.viewsyncer"; } @Override public void initMap(MapApi api) { // add the state to the list maintained by the plugin final SyncerState state = new SyncerState(api); states.put(api, state); // but remove it when the map is disposed api.registerDisposedListener(new OnDisposedListener() { @Override public void onDispose(MapApi api) { states.remove(api); api.removeViewChanged(ViewSyncer.this); } }, 0); // add the sync button (which has the popup menu) to the toolbar api.registerOnBuildToolbarListener(new OnBuildToolbarListener() { @Override public void onBuildToolbar(MapApi api, MapToolbar toolBar) { toolBar.add(state.button, "ViewSync"); } }, StandardMapMenuOrdering.SYNC); // create the context menu api.registerOnBuildContextMenuListener(new OnBuildContextMenu() { @Override public void onBuildContextMenu(MapApi api, MapPopupMenu menu) { for(Action a : state.createActions()){ menu.add(a, "ViewSync"); } } }, StandardMapMenuOrdering.SYNC); // add this listener which does the syncing api.registerViewChanged(this, 0); } private class SyncerState{ private final MapApi api; private boolean syncing; private final DropDownMenuButton button = new DropDownMenuButton(UNSYNCED_ICON,false) { @Override protected JPopupMenu getPopupMenu() { if(syncing){ // no menu needed ... just stop syncing (which is done by the other listener) return null; } JPopupMenu popup = new JPopupMenu(); for(Action a : createActions()){ popup.add(a); } return popup; } }; SyncerState(MapApi api) { this.api = api; button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if(syncing){ setSyncing(false); }else{ button.processPopup(); } } }); } private void setSyncing(boolean on){ if(on!=syncing){ syncing = on; button.setIcon(on ? SYNCED_ICON : UNSYNCED_ICON); } } List<Action> createActions(){ boolean wasSyncing = syncing; ArrayList<Action> ret = new ArrayList<Action>(); if(syncing){ ret.add(new AbstractAction("Stop syncing view", UNSYNCED_ICON) { @Override public void actionPerformed(ActionEvent e) { setSyncing(false); } }); } else{ ret.add(new AbstractAction("Sync to other view", SYNCED_ICON) { @Override public void actionPerformed(ActionEvent e) { setSyncing(true); for(SyncerState other : states.values()){ if(other!=SyncerState.this){ other.api.setView(other.api.getZoom(), other.api.getWorldBitmapMapCentre()); break; } } } }); ret.add(new AbstractAction("Sync others to my view", SYNCED_ICON) { @Override public void actionPerformed(ActionEvent e) { setSyncing(true); api.setView(api.getZoom(), api.getWorldBitmapMapCentre()); } }); } return ret; } } @Override public void onChanged(MapApi api) { // are we already in an sync? if(syncOngoing){ return; } // should this state trigger a sync? SyncerState state = states.get(api); if(state!=null && state.syncing){ // then trigger a sync in all other ones syncOngoing = true; try { for(SyncerState other : states.values()){ if(other!=state && other.syncing){ other.api.setView(api.getZoom(), api.getWorldBitmapMapCentre()); } } } catch (Exception e) { throw new RuntimeException(e); } finally{ syncOngoing = false; } } } }