/* * GeoSolutions map - Digital field mapping on Android based devices * Copyright (C) 2013 - 2014 GeoSolutions (www.geo-solutions.it) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package it.geosolutions.android.map.overlay.switcher; import it.geosolutions.android.map.MapsActivity; import it.geosolutions.android.map.R; import it.geosolutions.android.map.activities.BrowseSourcesActivity; import it.geosolutions.android.map.activities.MBTilesLayerOpacitySettingActivity; import it.geosolutions.android.map.adapters.LayerSwitcherAdapter; import it.geosolutions.android.map.listeners.LayerChangeListener; import it.geosolutions.android.map.mbtiles.MbTilesLayer; import it.geosolutions.android.map.model.Layer; import it.geosolutions.android.map.overlay.managers.MultiSourceOverlayManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; import android.app.Activity; import android.content.Intent; import android.content.res.Resources; import android.graphics.Color; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ImageButton; import android.widget.ListView; import android.widget.TextView; import com.actionbarsherlock.app.SherlockListFragment; import com.actionbarsherlock.view.ActionMode; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; /** * This fragment shows a view o the attributes of a single feature from a * feature passed as Extra. * * Is binded with The <SimpleOverlayManager> methods. * * This object is retained, but the activity itself is recreated. So it has also * to provide to the loaders that initializes all the data. This is why it * implements <LayerProvider>. * * Catch events from <LayerChangeListener> to be synchronized with add/changes to * the map. * * When things change (visibility,add) the list and the map are refreshed. * * * @author Lorenzo Natali (www.geo-solutions.it) */ public class LayerSwitcherFragment extends SherlockListFragment implements LayerChangeListener, LoaderCallbacks<List<Layer>>, LayerProvider, ActionMode.Callback { private int LOADER_INDEX = 1290; private LayerSwitcherAdapter adapter; private LayerListLoader loader; private boolean isLoading = false; private ActionMode actionMode; private ArrayList<Layer<?>> selected = new ArrayList<Layer<?>>(); private boolean mbTilesLayerSelected = false; public final static int OPACITY_SETTIN_REQUEST_ID = 999; /** * Called only once */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); // set the adapter for the layers adapter = new LayerSwitcherAdapter(getSherlockActivity(), R.layout.layer_checklist_row, this); setListAdapter(adapter); // star loading Layers getSherlockActivity().getSupportLoaderManager().initLoader( LOADER_INDEX, null, this); // return the layout inflater return inflater.inflate(R.layout.layer_switcher, container, false); } /* * (non-Javadoc) * * @see android.support.v4.app.Fragment#onResume() */ @Override public void onResume() { super.onResume(); getSherlockActivity().getSupportLoaderManager().initLoader( LOADER_INDEX, null, this); } /* * (non-Javadoc) * * @see android.support.v4.app.ListFragment#onViewCreated(android.view.View, * android.os.Bundle) */ @Override public void onViewCreated(View view, Bundle savedInstanceState) { // the map selection activity launcher ImageButton msEd = (ImageButton) view.findViewById(R.id.layer_add); msEd.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { closeActionMode(); Activity fa = getActivity(); if(fa != null){ Intent pref = new Intent(fa, BrowseSourcesActivity.class); fa.startActivityForResult(pref, MapsActivity.LAYER_ADD); } } }); // reset the ActionBar ActionMode closeActionMode(); // force reload reload(); // // Set Contextual ACTION BAR CALLBACKS // final LayerSwitcherFragment callback = this; ListView lv = getListView(); lv.setLongClickable(true); lv.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); lv.setOnItemLongClickListener(new OnItemLongClickListener() { public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Log.d(LayerSwitcherFragment.class.getSimpleName(), "layer long pressed : "+position); Layer<?> sel = adapter.getItem(position); if(sel instanceof MbTilesLayer){ mbTilesLayerSelected = true; }else{ mbTilesLayerSelected = false; } boolean wasDeselected = false; if (!selected.contains(sel)) { selected.add(sel); } else { wasDeselected = true; selected.remove(sel); } updateSelected(); int numSelected = selected.size(); if (numSelected > 0) { if (actionMode != null) { updateCAB(numSelected,wasDeselected); } else { actionMode = getSherlockActivity().startActionMode( callback); // override the done button to deselect all when the // button is pressed int doneButtonId = Resources.getSystem().getIdentifier( "action_mode_close_button", "id", "android"); View doneButton = getActivity().findViewById( doneButtonId); doneButton .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { closeActionMode(); } }); } } else { closeActionMode(); } return true; } }); lv.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { updateSelected(); } }); super.onViewCreated(view, savedInstanceState); // setup of the checkboxes } @Override public Loader<List<Layer>> onCreateLoader(int arg0, Bundle arg1) { this.loader = new LayerListLoader(getSherlockActivity(), this); return this.loader; } @Override public void onLoadFinished(Loader<List<Layer>> arg0, List<Layer> layers) { isLoading = true; adapter.clear(); ArrayList<Layer> ll = new ArrayList<Layer>(); int size = layers.size(); setLoading(); // reverse add to the layer list to // have the checkbox stacked as the layers if (size > 0) { // prevents notifyDataSetChanged() // to be called on each add() adapter.setNotifyOnChange(false); for (int i = size - 1; i >= 0; i--) { adapter.add(layers.get(i)); } } else { setNoData(); } isLoading = false; adapter.notifyDataSetChanged(); } /** * Set the GUI to display loading info */ private void setLoading() { getView().findViewById(R.id.progress_bar).setVisibility( TextView.VISIBLE); ((TextView) getView().findViewById(R.id.empty_text)) .setText(R.string.loading_layers); } /** * Set the GUI to show no data is present */ private void setNoData() { getView().findViewById(R.id.progress_bar).setVisibility(TextView.GONE); ((TextView) getView().findViewById(R.id.empty_text)) .setText(R.string.no_layer_loaded); } @Override public void onLoaderReset(Loader<List<Layer>> layers) { adapter.clear(); } @Override public void onSetLayers(ArrayList<Layer> layers) { reload(); } /** * Clears the adapter and triggers a loader reload */ private void reload() { if (adapter != null) { adapter.clear(); // force reload Loader<?> l = getSherlockActivity().getSupportLoaderManager() .getLoader(LOADER_INDEX); if (l != null) { l.forceLoad(); } else { Log.e("LAYER_SWITCHER", "Unable to reload layers"); } } } /** * Provide the overlayManager binded to this layer switcher * * @return */ public MultiSourceOverlayManager getOverlayManager() { final MapsActivity ac = (MapsActivity) getActivity(); return (MultiSourceOverlayManager) ac.overlayManager; } @Override public void onLayerVisibilityChange(Layer layer) { if (!isLoading) { getOverlayManager().redrawLayer(layer); } } @Override public ArrayList<Layer> getLayers() { // returns the layers from the current overlayManager // NOTE: this is needed because the instance of the overlay manager // can change during time. It needs to be get from the current // activity. return getOverlayManager().getLayers(); } // ACTION MODE CALLBACKS public boolean onPrepareActionMode(ActionMode mode, Menu menu) { Log.d(LayerSwitcherFragment.class.getSimpleName(), "onPrepareActionMode"); ListView lv = getListView(); Resources res = getResources(); int number = selected.size(); updateCAB(number,false); return false; } public void onDestroyActionMode(ActionMode mode) { Log.d(LayerSwitcherFragment.class.getSimpleName(), "onDestroyActionmode "); ListView lv = getListView(); lv.clearFocus(); lv.clearChoices(); selected = new ArrayList<Layer<?>>(); if (this.actionMode != null) { this.actionMode = null; } adapter.notifyDataSetChanged(); } public boolean onCreateActionMode(ActionMode mode, Menu menu) { Log.d(LayerSwitcherFragment.class.getSimpleName(), "onCreateActionMode"); if(mbTilesLayerSelected){ mode.getMenuInflater().inflate(R.menu.edit_delete_up_down, menu); //for some reasons the icon is dark, though this is the holo_dark icon menu.findItem(R.id.edit).getIcon().mutate().setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP); }else{ mode.getMenuInflater().inflate(R.menu.delete_up_down, menu); } this.actionMode = mode; return true; } public boolean onActionItemClicked(ActionMode mode, MenuItem menu) { int itemId = menu.getItemId(); ArrayList<Layer> layers = getLayers(); Log.d(LayerSwitcherFragment.class.getSimpleName(), "actionitemclicked "+itemId); ListView lv = getListView(); MultiSourceOverlayManager om = getOverlayManager(); if(itemId == R.id.edit){ getActivity().startActivityForResult(new Intent(getActivity(), MBTilesLayerOpacitySettingActivity.class),OPACITY_SETTIN_REQUEST_ID); }else if (itemId == R.id.delete) { layers.removeAll(selected); om.setLayers(new ArrayList<Layer>(layers), true); om.forceRedraw(); closeActionMode(); } else if (itemId == R.id.up || itemId == R.id.down) { // check if already on tom or bottom int size = layers.size(); List<Integer> indexes = new ArrayList<Integer>(); for (Layer<?> l : selected) { indexes.add(layers.indexOf(l)); } int min = Collections.min(indexes); int max = Collections.max(indexes); if (max < size - 1 && itemId == R.id.up) { for (Layer<?> l : selected) { int i = layers.indexOf(l); layers.remove(l); layers.add(Math.min(i + 1, size - 1), l); // keep adapter sync adapter.remove(l); adapter.insert(l, Math.max(size - 2 - i, 0)); } refreshOverlays(layers, om); } else if (min > 0 && itemId == R.id.down) { // NOTE:the listView is in the reverse order for (Layer<?> l : selected) { int i = layers.indexOf(l); layers.remove(l); layers.add(Math.max(i - 1, 0), l); // keep adapter sync adapter.remove(l); adapter.insert(l, Math.min(size - i, size - 1)); } refreshOverlays(layers, om); } updateSelected(); } return true; } /** * Launch a thread to update the layers * * @param layers * @param om */ private void refreshOverlays(final ArrayList<Layer> layers, final MultiSourceOverlayManager om) { om.setLayers(new ArrayList<Layer>(layers), false); om.forceRedraw(); } /** * Clear selections and close the action mode */ private void closeActionMode() { Log.d(LayerSwitcherFragment.class.getSimpleName(), "close actionmode "); selected = new ArrayList<Layer<?>>(); if (actionMode != null) { actionMode.finish(); actionMode = null; } } /** * Update the selected layers in the list view */ private void updateSelected() { ListView lv = getListView(); lv.clearFocus(); lv.clearChoices(); ArrayList<Layer> layers = getLayers(); int size = layers.size(); for (Layer l : selected) { int i = layers.indexOf(l); lv.setItemChecked(size - 1 - i, true); } } /* * (non-Javadoc) * * @see * it.geosolutions.android.map.listeners.LayerChangeListener#onLayerStatusChange * () */ @Override public void onLayerStatusChange() { reload(); } /** * Updates the ActionBar if it is in "ACTION_MODE" * @param numSelected */ private void updateCAB(int numSelected, boolean wasDeselected) { if (actionMode == null){ // nothing to update return; } String title = getResources().getQuantityString( R.plurals.quantity_layers_selected, numSelected, numSelected); actionMode.setTitle(title); Menu menu = actionMode.getMenu(); if (numSelected > 1) { menu.findItem(R.id.up).setVisible(false); menu.findItem(R.id.down).setVisible(false); } else if(numSelected == 1 && wasDeselected) { //from multiple selections, one remains, what is it ? if(selected != null && selected.size() > 0){ MenuItem editItem = menu.findItem(R.id.edit); if(selected.get(0) instanceof MbTilesLayer){ //the remaining is an MbTilesLayer item, config and add edit item if necessary menu.findItem(R.id.up).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); menu.findItem(R.id.down).setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); menu.findItem(R.id.up).setVisible(true); menu.findItem(R.id.down).setVisible(true); //this was a vector entry, add the edit entry as first item if(editItem == null){ Drawable d = getResources().getDrawable(R.drawable.ic_action_settings); d.mutate().setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP); menu.add(Menu.CATEGORY_SYSTEM, R.id.edit, Menu.FIRST, getString(R.string.edit)) .setIcon(d) .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); } }else{ //the remaining is a raster layer, config and remove edit if necessary menu.findItem(R.id.up).setVisible(true); menu.findItem(R.id.down).setVisible(true); //this was a raster entry, remove edit if(editItem != null){ menu.removeItem(R.id.edit); } } } } else { //should not be called anymore menu.findItem(R.id.up).setVisible(true); menu.findItem(R.id.down).setVisible(true); } } }