diff --git a/Android/res/drawable-hdpi/ic_action_copy.png b/Android/res/drawable-hdpi/ic_action_copy.png new file mode 100644 index 0000000000..5ff5f86239 Binary files /dev/null and b/Android/res/drawable-hdpi/ic_action_copy.png differ diff --git a/Android/res/drawable-hdpi/ic_action_copy_blue.png b/Android/res/drawable-hdpi/ic_action_copy_blue.png new file mode 100644 index 0000000000..4e5e4a6b46 Binary files /dev/null and b/Android/res/drawable-hdpi/ic_action_copy_blue.png differ diff --git a/Android/res/drawable-hdpi/ic_action_playback_schuffle.png b/Android/res/drawable-hdpi/ic_action_playback_schuffle.png new file mode 100644 index 0000000000..5de3096cac Binary files /dev/null and b/Android/res/drawable-hdpi/ic_action_playback_schuffle.png differ diff --git a/Android/res/drawable-hdpi/ic_action_trash.png b/Android/res/drawable-hdpi/ic_action_trash.png new file mode 100644 index 0000000000..a937aea060 Binary files /dev/null and b/Android/res/drawable-hdpi/ic_action_trash.png differ diff --git a/Android/res/drawable-mdpi/ic_action_copy.png b/Android/res/drawable-mdpi/ic_action_copy.png new file mode 100644 index 0000000000..efafdf7263 Binary files /dev/null and b/Android/res/drawable-mdpi/ic_action_copy.png differ diff --git a/Android/res/drawable-mdpi/ic_action_copy_blue.png b/Android/res/drawable-mdpi/ic_action_copy_blue.png new file mode 100644 index 0000000000..01ad9456d6 Binary files /dev/null and b/Android/res/drawable-mdpi/ic_action_copy_blue.png differ diff --git a/Android/res/drawable-mdpi/ic_action_playback_schuffle.png b/Android/res/drawable-mdpi/ic_action_playback_schuffle.png new file mode 100644 index 0000000000..25a72c77cd Binary files /dev/null and b/Android/res/drawable-mdpi/ic_action_playback_schuffle.png differ diff --git a/Android/res/drawable-mdpi/ic_action_trash.png b/Android/res/drawable-mdpi/ic_action_trash.png new file mode 100644 index 0000000000..a13e62c0e8 Binary files /dev/null and b/Android/res/drawable-mdpi/ic_action_trash.png differ diff --git a/Android/res/drawable-xhdpi/ic_action_copy.png b/Android/res/drawable-xhdpi/ic_action_copy.png new file mode 100644 index 0000000000..5e55f4eb98 Binary files /dev/null and b/Android/res/drawable-xhdpi/ic_action_copy.png differ diff --git a/Android/res/drawable-xhdpi/ic_action_copy_blue.png b/Android/res/drawable-xhdpi/ic_action_copy_blue.png new file mode 100644 index 0000000000..2830d29442 Binary files /dev/null and b/Android/res/drawable-xhdpi/ic_action_copy_blue.png differ diff --git a/Android/res/drawable-xhdpi/ic_action_playback_schuffle.png b/Android/res/drawable-xhdpi/ic_action_playback_schuffle.png new file mode 100644 index 0000000000..bf7c199459 Binary files /dev/null and b/Android/res/drawable-xhdpi/ic_action_playback_schuffle.png differ diff --git a/Android/res/drawable-xhdpi/ic_action_trash.png b/Android/res/drawable-xhdpi/ic_action_trash.png new file mode 100644 index 0000000000..9f910f5960 Binary files /dev/null and b/Android/res/drawable-xhdpi/ic_action_trash.png differ diff --git a/Android/res/drawable-xxhdpi/ic_action_copy.png b/Android/res/drawable-xxhdpi/ic_action_copy.png new file mode 100644 index 0000000000..2db460b89b Binary files /dev/null and b/Android/res/drawable-xxhdpi/ic_action_copy.png differ diff --git a/Android/res/drawable-xxhdpi/ic_action_copy_blue.png b/Android/res/drawable-xxhdpi/ic_action_copy_blue.png new file mode 100644 index 0000000000..3668f4277f Binary files /dev/null and b/Android/res/drawable-xxhdpi/ic_action_copy_blue.png differ diff --git a/Android/res/drawable-xxhdpi/ic_action_playback_schuffle.png b/Android/res/drawable-xxhdpi/ic_action_playback_schuffle.png new file mode 100644 index 0000000000..fa62ad4134 Binary files /dev/null and b/Android/res/drawable-xxhdpi/ic_action_playback_schuffle.png differ diff --git a/Android/res/drawable-xxhdpi/ic_action_trash.png b/Android/res/drawable-xxhdpi/ic_action_trash.png new file mode 100644 index 0000000000..e3d92c10e8 Binary files /dev/null and b/Android/res/drawable-xxhdpi/ic_action_trash.png differ diff --git a/Android/res/layout/fragment_editor_detail_circle.xml b/Android/res/layout/fragment_editor_detail_circle.xml index ef91de91cc..6e309c1be3 100644 --- a/Android/res/layout/fragment_editor_detail_circle.xml +++ b/Android/res/layout/fragment_editor_detail_circle.xml @@ -2,7 +2,6 @@ - + + android:layout_alignParentTop="true" + android:entries="@array/ExampleWaypointType"/> - + android:layout_height="64dp" + android:layout_alignParentTop="true" + android:layout_centerVertical="true" + android:gravity="center_vertical" + android:paddingLeft="25dp" + android:paddingRight="25dp" + android:background="@drawable/wp_title_rectangle" + android:text="Select Waypoint Type" + android:textAllCaps="true"/> - - - - + - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/Android/res/layout/fragment_editor_detail_loiter.xml b/Android/res/layout/fragment_editor_detail_loiter.xml deleted file mode 100644 index de6647bfcf..0000000000 --- a/Android/res/layout/fragment_editor_detail_loiter.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Android/res/layout/fragment_editor_detail_waypoint.xml b/Android/res/layout/fragment_editor_detail_waypoint.xml index ee3c26ed03..2da5b4f32c 100644 --- a/Android/res/layout/fragment_editor_detail_waypoint.xml +++ b/Android/res/layout/fragment_editor_detail_waypoint.xml @@ -98,7 +98,7 @@ android:orientation="vertical" > + + + + + + + + + + \ No newline at end of file diff --git a/Android/res/values/strings.xml b/Android/res/values/strings.xml index 2204d8b3bb..877ac9cab8 100644 --- a/Android/res/values/strings.xml +++ b/Android/res/values/strings.xml @@ -209,6 +209,7 @@ Delay before next waypoint Loiter Radius CCW + Circle Radius Altitude Yaw Angle Target Altitude @@ -483,5 +484,8 @@ Arm propellers? Warning! Propellers will spin and drone may begin to fly. Radius + Delete + Reverse + Multi-Edit diff --git a/Android/src/org/droidplanner/android/activities/EditorActivity.java b/Android/src/org/droidplanner/android/activities/EditorActivity.java index 2ed05899c9..8d65c51702 100644 --- a/Android/src/org/droidplanner/android/activities/EditorActivity.java +++ b/Android/src/org/droidplanner/android/activities/EditorActivity.java @@ -26,7 +26,9 @@ import org.droidplanner.core.helpers.coordinates.Coord2D; import org.droidplanner.core.helpers.units.Length; import org.droidplanner.core.helpers.units.Speed; +import org.droidplanner.core.mission.MissionItemType; import org.droidplanner.core.model.Drone; +import org.droidplanner.core.util.Pair; import android.os.Bundle; import android.support.v4.app.FragmentManager; @@ -76,9 +78,11 @@ public class EditorActivity extends DrawerNavigationUI implements OnPathFinished private View mSplineToggleContainer; private boolean mIsSplineEnabled; - private View mLocationButtonsContainer; private TextView infoView; + //TODO: change the multi edit icon based on its state. + private boolean mMultiEditEnabled; + /** * This view hosts the mission item detail fragment. On phone, or device * with limited screen estate, it's removed from the layout, and the item @@ -109,8 +113,6 @@ public void onCreate(Bundle savedInstanceState) { infoView = (TextView) findViewById(R.id.editorInfoWindow); - mLocationButtonsContainer = findViewById(R.id.location_button_container); - final ImageButton resetMapBearing = (ImageButton) findViewById(R.id.map_orientation_button); resetMapBearing.setOnClickListener(new View.OnClickListener() { @Override @@ -285,10 +287,8 @@ public void onDroneEvent(DroneEventsType event, Drone drone) { // Remove detail window if item is removed - if (itemDetailFragment != null) { - if (!missionProxy.contains(itemDetailFragment.getItem())) { + if (missionProxy.selection.getSelected().isEmpty() && itemDetailFragment != null) { removeItemDetail(); - } } break; @@ -379,16 +379,16 @@ private void enableSplineToggle(boolean isEnabled) { } } - private void showItemDetail(MissionItemProxy item) { + private void showItemDetail(MissionDetailFragment itemDetail) { if (itemDetailFragment == null) { - addItemDetail(item); + addItemDetail(itemDetail); } else { - switchItemDetail(item); + switchItemDetail(itemDetail); } } - private void addItemDetail(MissionItemProxy item) { - itemDetailFragment = item.getDetailFragment(); + private void addItemDetail(MissionDetailFragment itemDetail) { + itemDetailFragment = itemDetail; if (itemDetailFragment == null) return; @@ -401,9 +401,9 @@ private void addItemDetail(MissionItemProxy item) { } } - public void switchItemDetail(MissionItemProxy item) { + public void switchItemDetail(MissionDetailFragment itemDetail) { removeItemDetail(); - addItemDetail(item); + addItemDetail(itemDetail); } private void removeItemDetail() { @@ -445,27 +445,42 @@ public void onPathFinished(List path) { } @Override - public void onDetailDialogDismissed(MissionItemProxy item) { - missionProxy.selection.removeItemFromSelection(item); + public void onDetailDialogDismissed(List itemList) { + missionProxy.selection.removeItemsFromSelection(itemList); } @Override - public void onWaypointTypeChanged(MissionItemProxy newItem, MissionItemProxy oldItem) { - missionProxy.replace(oldItem, newItem); + public void onWaypointTypeChanged(List> oldNewItemsList) { + missionProxy.replaceAll(oldNewItemsList); } - private static final int MENU_DELETE = 1; - private static final int MENU_REVERSE = 2; - @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { switch (item.getItemId()) { - case MENU_DELETE: + case R.id.menu_action_multi_edit: + if(mMultiEditEnabled){ + removeItemDetail(); + enableMultiEdit(false); + return true; + } + + final List selectedProxies = missionProxy.selection.getSelected(); + if(selectedProxies.size() >= 1){ + showItemDetail(selectMissionDetailType(selectedProxies)); + enableMultiEdit(true); + return true; + } + + Toast.makeText(getApplicationContext(), "No Waypoint(s) selected.", Toast.LENGTH_LONG) + .show(); + return true; + + case R.id.menu_action_delete: missionProxy.removeSelection(missionProxy.selection); mode.finish(); return true; - case MENU_REVERSE: + case R.id.menu_action_reverse: missionProxy.reverse(); return true; @@ -474,10 +489,28 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { } } + private MissionDetailFragment selectMissionDetailType(List proxies){ + if(proxies == null || proxies.isEmpty()) + return null; + + MissionItemType referenceType = null; + for(MissionItemProxy proxy: proxies){ + final MissionItemType proxyType = proxy.getMissionItem().getType(); + if(referenceType == null){ + referenceType = proxyType; + } + else if(referenceType != proxyType){ + //Return a generic mission detail. + return new MissionDetailFragment(); + } + } + + return MissionDetailFragment.newInstance(referenceType); + } + @Override - public boolean onCreateActionMode(ActionMode arg0, Menu menu) { - menu.add(0, MENU_DELETE, 0, "Delete"); - menu.add(0, MENU_REVERSE, 0, "Reverse"); + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(R.menu.action_mode_editor, menu); editorToolsFragment.getView().setVisibility(View.INVISIBLE); return true; } @@ -486,12 +519,27 @@ public boolean onCreateActionMode(ActionMode arg0, Menu menu) { public void onDestroyActionMode(ActionMode arg0) { missionListFragment.updateChoiceMode(AbsListView.CHOICE_MODE_SINGLE); missionProxy.selection.clearSelection(); - contextualActionBar = null; + + contextualActionBar = null; + enableMultiEdit(false); + editorToolsFragment.getView().setVisibility(View.VISIBLE); } + private void enableMultiEdit(boolean enable){ + mMultiEditEnabled = enable; + + if(contextualActionBar != null){ + final Menu menu = contextualActionBar.getMenu(); + final MenuItem multiEdit = menu.findItem(R.id.menu_action_multi_edit); + multiEdit.setIcon(mMultiEditEnabled + ? R.drawable.ic_action_copy_blue + : R.drawable.ic_action_copy); + } + } + @Override - public boolean onPrepareActionMode(ActionMode arg0, Menu arg1) { + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; } @@ -514,6 +562,7 @@ public boolean onItemLongClick(MissionItemProxy item) { @Override public void onItemClick(MissionItemProxy item) { + enableMultiEdit(false); switch (getTool()) { default: if (contextualActionBar != null) { @@ -552,17 +601,17 @@ protected boolean enableMissionMenus(){ @Override public void onSelectionUpdate(List selected) { - final int selectedCount = selected.size(); + final boolean isEmpty = selected.isEmpty(); - missionListFragment.setArrowsVisibility(selectedCount > 0); + missionListFragment.setArrowsVisibility(!isEmpty); - if (selectedCount != 1) { + if (isEmpty) { removeItemDetail(); } else { - if (contextualActionBar != null) + if (contextualActionBar != null && !mMultiEditEnabled) removeItemDetail(); else { - showItemDetail(selected.get(0)); + showItemDetail(selected.get(0).getDetailFragment()); } } diff --git a/Android/src/org/droidplanner/android/proxy/mission/MissionProxy.java b/Android/src/org/droidplanner/android/proxy/mission/MissionProxy.java index d07c1f5afb..e2f1cb14c3 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/MissionProxy.java +++ b/Android/src/org/droidplanner/android/proxy/mission/MissionProxy.java @@ -20,8 +20,7 @@ import org.droidplanner.core.mission.waypoints.SpatialCoordItem; import org.droidplanner.core.mission.waypoints.SplineWaypoint; import org.droidplanner.core.mission.waypoints.Waypoint; - -import android.util.Pair; +import org.droidplanner.core.util.Pair; /** * This class is used to render a {@link org.droidplanner.core.mission.Mission} @@ -262,6 +261,49 @@ public void replace(MissionItemProxy oldItem, MissionItemProxy newItem) { } } + public void replaceAll(List> oldNewList){ + if(oldNewList == null){ + return; + } + + final int pairSize = oldNewList.size(); + if(pairSize == 0){ + return; + } + + final List> missionItemsToUpdate = new + ArrayList>(pairSize); + + final List selectionsToRemove = new ArrayList(pairSize); + final List itemsToSelect = new ArrayList(pairSize); + + for(int i = 0; i < pairSize; i++){ + final MissionItemProxy oldItem = oldNewList.get(i).first; + final int index = mMissionItems.indexOf(oldItem); + if(index == -1){ + continue; + } + + final MissionItemProxy newItem = oldNewList.get(i).second; + mMissionItems.remove(index); + mMissionItems.add(index, newItem); + + missionItemsToUpdate.add(Pair.create(oldItem.getMissionItem(), newItem.getMissionItem())); + + if(selection.selectionContains(oldItem)){ + selectionsToRemove.add(oldItem); + itemsToSelect.add(newItem); + } + } + + //Update the mission objects + mMission.replaceAll(missionItemsToUpdate); + + //Update the selection list. + selection.removeItemsFromSelection(selectionsToRemove); + selection.addToSelection(itemsToSelect); + } + /** * Reverse the order of the mission items renders. */ diff --git a/Android/src/org/droidplanner/android/proxy/mission/MissionSelection.java b/Android/src/org/droidplanner/android/proxy/mission/MissionSelection.java index 08679a858e..f7addedfbe 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/MissionSelection.java +++ b/Android/src/org/droidplanner/android/proxy/mission/MissionSelection.java @@ -17,24 +17,38 @@ public interface OnSelectionUpdateListener { /** * Stores the selected mission items renders. */ - public List mSelectedItems = new ArrayList(); + public final List mSelectedItems = new ArrayList(); /** * Stores the list of selection update listeners. */ public List mSelectionsListeners = new ArrayList(); /** - * Removes the given mission item render from the selected list. TODO: check - * the argument belongs to this mission render + * Removes the given mission item render from the selected list. * * @param item - * mission item rendere to remove from the selected list + * mission item render to remove from the selected list */ public void removeItemFromSelection(MissionItemProxy item) { mSelectedItems.remove(item); notifySelectionUpdate(); } + /** + * Removes the mission items in the given list from the selected list. + * @param itemList list of mission items to remove from the selected list. + */ + public void removeItemsFromSelection(List itemList){ + if(itemList == null || itemList.isEmpty()){ + return; + } + + for(MissionItemProxy item : itemList){ + mSelectedItems.remove(item); + } + notifySelectionUpdate(); + } + /** * Selects only the given mission items renders. TODO: check the mission * items renders belong to this mission render diff --git a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionCircleFragment.java b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionCircleFragment.java index 6b73090340..5373ab1992 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionCircleFragment.java +++ b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionCircleFragment.java @@ -13,6 +13,8 @@ import android.widget.CheckBox; import android.widget.CompoundButton; +import java.util.List; + public class MissionCircleFragment extends MissionDetailFragment implements CardWheelHorizontalView.OnCardWheelChangedListener, CompoundButton.OnCheckedChangeListener { @@ -21,7 +23,7 @@ public class MissionCircleFragment extends MissionDetailFragment implements private CheckBox checkBoxAdvanced; - private Circle mItem; + private List mItemsList; private CardWheelHorizontalView mNumberStepsPicker; private CardWheelHorizontalView mAltitudeStepPicker; @@ -38,21 +40,33 @@ public void onViewCreated(View view, Bundle savedInstanceState) { typeSpinner.setSelection(commandAdapter.getPosition(MissionItemType.CIRCLE)); - mItem = (Circle) this.itemRender.getMissionItem(); + mItemsList = (List) getMissionItems(); + + //Use the first one as reference. + final Circle firstItem = mItemsList.get(0); + + boolean isAdvanced = DEFAULT_IS_ADVANCED_ON; + if (savedInstanceState != null) { + isAdvanced = savedInstanceState + .getBoolean(EXTRA_IS_ADVANCED_ON, DEFAULT_IS_ADVANCED_ON); + } + checkBoxAdvanced = (CheckBox) view.findViewById(R.id.checkBoxAdvanced); + checkBoxAdvanced.setOnCheckedChangeListener(this); + checkBoxAdvanced.setChecked(isAdvanced); final NumericWheelAdapter altitudeStepAdapter = new NumericWheelAdapter(context, R.layout.wheel_text_centered, 1, 10, "%d m"); mAltitudeStepPicker = (CardWheelHorizontalView) view.findViewById(R.id.altitudeStepPicker); mAltitudeStepPicker.setViewAdapter(altitudeStepAdapter); - mAltitudeStepPicker.setCurrentValue((int) mItem.getAltitudeStep()); - mAltitudeStepPicker.addChangingListener(this); + mAltitudeStepPicker.addChangingListener(this); + mAltitudeStepPicker.setCurrentValue((int) firstItem.getAltitudeStep()); final NumericWheelAdapter numberStepsAdapter = new NumericWheelAdapter(context, R.layout.wheel_text_centered, 1, 10, "%d"); mNumberStepsPicker = (CardWheelHorizontalView) view.findViewById(R.id.numberStepsPicker); mNumberStepsPicker.setViewAdapter(numberStepsAdapter); - mNumberStepsPicker.setCurrentValue(mItem.getNumberOfSteps()); - mNumberStepsPicker.addChangingListener(this); + mNumberStepsPicker.addChangingListener(this); + mNumberStepsPicker.setCurrentValue(firstItem.getNumberOfSteps()); final NumericWheelAdapter altitudeAdapter = new NumericWheelAdapter(context, MIN_ALTITUDE, MAX_ALTITUDE, "%d m"); @@ -60,16 +74,17 @@ public void onViewCreated(View view, Bundle savedInstanceState) { final CardWheelHorizontalView altitudePicker = (CardWheelHorizontalView) view .findViewById(R.id.altitudePicker); altitudePicker.setViewAdapter(altitudeAdapter); - altitudePicker.setCurrentValue((int) mItem.getCoordinate().getAltitude().valueInMeters()); - altitudePicker.addChangingListener(this); + altitudePicker.addChangingListener(this); + altitudePicker.setCurrentValue((int) firstItem.getCoordinate().getAltitude().valueInMeters + ()); final NumericWheelAdapter loiterTurnAdapter = new NumericWheelAdapter(context, R.layout.wheel_text_centered, 0, 10, "%d"); final CardWheelHorizontalView loiterTurnPicker = (CardWheelHorizontalView) view .findViewById(R.id.loiterTurnPicker); loiterTurnPicker.setViewAdapter(loiterTurnAdapter); - loiterTurnPicker.setCurrentValue(mItem.getNumberOfTurns()); - loiterTurnPicker.addChangingListener(this); + loiterTurnPicker.addChangingListener(this); + loiterTurnPicker.setCurrentValue(firstItem.getNumberOfTurns()); final NumericWheelAdapter loiterRadiusAdapter = new NumericWheelAdapter(context, 0, 50, "%d m"); @@ -77,17 +92,8 @@ public void onViewCreated(View view, Bundle savedInstanceState) { final CardWheelHorizontalView loiterRadiusPicker = (CardWheelHorizontalView) view .findViewById(R.id.loiterRadiusPicker); loiterRadiusPicker.setViewAdapter(loiterRadiusAdapter); - loiterRadiusPicker.setCurrentValue((int) mItem.getRadius()); - loiterRadiusPicker.addChangingListener(this); - - boolean isAdvanced = DEFAULT_IS_ADVANCED_ON; - if (savedInstanceState != null) { - isAdvanced = savedInstanceState - .getBoolean(EXTRA_IS_ADVANCED_ON, DEFAULT_IS_ADVANCED_ON); - } - checkBoxAdvanced = (CheckBox) view.findViewById(R.id.checkBoxAdvanced); - checkBoxAdvanced.setOnCheckedChangeListener(this); - checkBoxAdvanced.setChecked(isAdvanced); + loiterRadiusPicker.addChangingListener(this); + loiterRadiusPicker.setCurrentValue((int) firstItem.getRadius()); } @Override @@ -103,11 +109,15 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { int visibility; if (isChecked) { visibility = View.VISIBLE; - mItem.setNumberOfSteps(mNumberStepsPicker.getCurrentValue()); - mItem.setAltitudeStep(mAltitudeStepPicker.getCurrentValue()); + for(Circle item: mItemsList) { + item.setNumberOfSteps(mNumberStepsPicker.getCurrentValue()); + item.setAltitudeStep(mAltitudeStepPicker.getCurrentValue()); + } } else { visibility = View.GONE; - mItem.setNumberOfSteps(1); + for(Circle item: mItemsList) { + item.setNumberOfSteps(1); + } } mAltitudeStepPicker.setVisibility(visibility); @@ -119,27 +129,37 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onChanged(CardWheelHorizontalView cardWheel, int oldValue, int newValue) { switch (cardWheel.getId()) { case R.id.altitudePicker: - mItem.setAltitude(new Altitude(newValue)); + for(Circle item: mItemsList) { + item.setAltitude(new Altitude(newValue)); + } break; case R.id.loiterRadiusPicker: - mItem.setRadius(newValue); - mItem.getMission().notifyMissionUpdate(); + for(Circle item: mItemsList) { + item.setRadius(newValue); + } + getMissionProxy().getMission().notifyMissionUpdate(); break; case R.id.loiterTurnPicker: - mItem.setTurns(newValue); + for(Circle item: mItemsList) { + item.setTurns(newValue); + } break; case R.id.numberStepsPicker: if (checkBoxAdvanced.isChecked()) { - mItem.setNumberOfSteps(newValue); + for(Circle item: mItemsList) { + item.setNumberOfSteps(newValue); + } } break; case R.id.altitudeStepPicker: if (checkBoxAdvanced.isChecked()) { - mItem.setAltitudeStep(newValue); + for(Circle item: mItemsList) { + item.setAltitudeStep(newValue); + } } break; } diff --git a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionDetailFragment.java b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionDetailFragment.java index d3f6a0791b..b3d449595e 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionDetailFragment.java +++ b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionDetailFragment.java @@ -1,5 +1,6 @@ package org.droidplanner.android.proxy.mission.item.fragments; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -14,7 +15,7 @@ import org.droidplanner.core.mission.MissionItemType; import org.droidplanner.core.mission.commands.MissionCMD; import org.droidplanner.core.mission.survey.Survey; -import org.droidplanner.core.mission.waypoints.SpatialCoordItem; +import org.droidplanner.core.util.Pair; import android.app.Activity; import android.content.DialogInterface; @@ -24,47 +25,47 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Spinner; import android.widget.TextView; -public abstract class MissionDetailFragment extends DialogFragment implements - OnItemSelectedListener { +public class MissionDetailFragment extends DialogFragment implements SpinnerSelfSelect.OnSpinnerItemSelectedListener { private static final String TAG = MissionDetailFragment.class.getSimpleName(); protected static final int MIN_ALTITUDE = 0; // meter protected static final int MAX_ALTITUDE = 200; // meters - public interface OnMissionDetailListener { + public interface OnMissionDetailListener { /** * Only fired when the mission detail is shown as a dialog. Notifies the * listener that the mission detail dialog has been dismissed. * - * @param item - * mission item proxy whose details the dialog is showing. + * @param itemList + * list of mission items proxies whose details the dialog is showing. */ - public void onDetailDialogDismissed(MissionItemProxy item); + public void onDetailDialogDismissed(List itemList); /** * Notifies the listener that the mission item proxy was changed. - * - * @param newItem - * previous mission item proxy - * @param oldItem - * new mission item proxy + * + * @param oldNewItemsList a list of pairs containing the previous, + * and the new mission item proxy. */ - public void onWaypointTypeChanged(MissionItemProxy newItem, MissionItemProxy oldItem); + public void onWaypointTypeChanged(List> oldNewItemsList); } - protected abstract int getResource(); + protected int getResource(){ + return R.layout.fragment_editor_detail_generic; + } protected SpinnerSelfSelect typeSpinner; protected AdapterMissionItems commandAdapter; private OnMissionDetailListener mListener; - private MissionProxy mMissionProxy; - protected MissionItemProxy itemRender; + private MissionProxy mMissionProxy; + private List mSelectedItems; + private List mSelectedProxies; public static MissionDetailFragment newInstance(MissionItemType itemType) { MissionDetailFragment fragment; @@ -108,77 +109,102 @@ public void onCreate(Bundle savedInstanceState) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - final MissionProxy missionProxy = ((DroidPlannerApp) getActivity().getApplication()).missionProxy; - final List selections = missionProxy.selection.getSelected(); - if (selections.isEmpty()) { + mMissionProxy = ((DroidPlannerApp) getActivity().getApplication()).missionProxy; + mSelectedProxies = new ArrayList(mMissionProxy.selection.getSelected()); + if (mSelectedProxies.isEmpty()) { return null; } - itemRender = selections.get(0); + mSelectedItems = new ArrayList(mSelectedProxies.size()); + for(MissionItemProxy mip : mSelectedProxies){ + mSelectedItems.add(mip.getMissionItem()); + } + return inflater.inflate(getResource(), container, false); } + protected MissionProxy getMissionProxy(){ + return mMissionProxy; + } + + protected List getMissionItems(){ + return mSelectedItems; + } + @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - mMissionProxy = itemRender.getMissionProxy(); - List list = new LinkedList(Arrays.asList(MissionItemType - .values())); - MissionItem currentItem = itemRender.getMissionItem(); - - if ((currentItem instanceof Survey)) { - list.clear(); - list.add(MissionItemType.SURVEY); - }else{ - list.remove(MissionItemType.SURVEY); - } - - if (mMissionProxy.getItems().indexOf(itemRender) != 0) { - list.remove(MissionItemType.TAKEOFF); - } + List list = new LinkedList(Arrays.asList(MissionItemType.values())); - if (mMissionProxy.getItems().indexOf(itemRender) != (mMissionProxy.getItems().size() - 1)) { - list.remove(MissionItemType.LAND); - list.remove(MissionItemType.RTL); - } - - if(currentItem instanceof MissionCMD) { - list.remove(MissionItemType.LAND); - list.remove(MissionItemType.SPLINE_WAYPOINT); - list.remove(MissionItemType.CIRCLE); - list.remove(MissionItemType.ROI); - list.remove(MissionItemType.WAYPOINT); - } - - - commandAdapter = new AdapterMissionItems(this.getActivity(), - android.R.layout.simple_list_item_1, list.toArray(new MissionItemType[0])); + if(mSelectedProxies.size() == 1) { + final MissionItemProxy itemProxy = mSelectedProxies.get(0); + final MissionItem currentItem = itemProxy.getMissionItem(); - typeSpinner = (SpinnerSelfSelect) view.findViewById(R.id.spinnerWaypointType); - typeSpinner.setAdapter(commandAdapter); - typeSpinner.setOnItemSelectedListener(this); + if ((currentItem instanceof Survey)) { + list.clear(); + list.add(MissionItemType.SURVEY); + } else { + list.remove(MissionItemType.SURVEY); + } - final TextView waypointIndex = (TextView) view.findViewById(R.id.WaypointIndex); - if (waypointIndex != null) { - final int itemOrder = mMissionProxy.getOrder(itemRender); - waypointIndex.setText(String.valueOf(itemOrder)); - } + if (mMissionProxy.getItems().indexOf(itemProxy) != 0) { + list.remove(MissionItemType.TAKEOFF); + } - final TextView distanceView = (TextView) view.findViewById(R.id.DistanceValue); - if (distanceView != null) { - try { - distanceView.setText(mMissionProxy.getDistanceFromLastWaypoint(itemRender) - .toString()); - } catch (IllegalArgumentException e) { - Log.w(TAG, e.getMessage(), e); - } - } + if (mMissionProxy.getItems().indexOf(itemProxy) != (mMissionProxy.getItems().size() - 1)) { + list.remove(MissionItemType.LAND); + list.remove(MissionItemType.RTL); + } - final TextView distanceLabelView = (TextView) view.findViewById(R.id.DistanceLabel); - if (distanceLabelView != null) { - distanceLabelView.setVisibility(View.VISIBLE); - } + if (currentItem instanceof MissionCMD) { + list.remove(MissionItemType.LAND); + list.remove(MissionItemType.SPLINE_WAYPOINT); + list.remove(MissionItemType.CIRCLE); + list.remove(MissionItemType.ROI); + list.remove(MissionItemType.WAYPOINT); + } + + final TextView waypointIndex = (TextView) view.findViewById(R.id.WaypointIndex); + if (waypointIndex != null) { + final int itemOrder = mMissionProxy.getOrder(itemProxy); + waypointIndex.setText(String.valueOf(itemOrder)); + } + + final TextView distanceView = (TextView) view.findViewById(R.id.DistanceValue); + if (distanceView != null) { + try { + distanceView.setText(mMissionProxy.getDistanceFromLastWaypoint(itemProxy) + .toString()); + } catch (IllegalArgumentException e) { + Log.w(TAG, e.getMessage(), e); + } + } + + final TextView distanceLabelView = (TextView) view.findViewById(R.id.DistanceLabel); + if (distanceLabelView != null) { + distanceLabelView.setVisibility(View.VISIBLE); + } + } + else if(mSelectedProxies.size() > 1){ + //Remove the mission item types that don't apply to multiple items. + list.remove(MissionItemType.TAKEOFF); + list.remove(MissionItemType.LAND); + list.remove(MissionItemType.RTL); + list.remove(MissionItemType.SURVEY); + } + else{ + //Invalid state. We should not have been able to get here. + throw new IllegalStateException("Mission Detail Fragment cannot be shown when no " + + "mission items is selected."); + } + + commandAdapter = new AdapterMissionItems(getActivity(), + android.R.layout.simple_list_item_1, list.toArray(new MissionItemType[list.size()])); + + typeSpinner = (SpinnerSelfSelect) view.findViewById(R.id.spinnerWaypointType); + typeSpinner.setAdapter(commandAdapter); + typeSpinner.setOnSpinnerItemSelectedListener(this); } @Override @@ -202,33 +228,35 @@ public void onDetach() { public void onDismiss(DialogInterface dialog) { super.onDismiss(dialog); if (mListener != null) { - mListener.onDetailDialogDismissed(itemRender); + mListener.onDetailDialogDismissed(mSelectedProxies); } } - @Override - public void onItemSelected(AdapterView arg0, View v, int position, long id) { - MissionItemType selected = commandAdapter.getItem(position); - try { - final MissionItem oldItem = itemRender.getMissionItem(); - if (oldItem.getType() != selected) { - Log.d("CLASS", "Different waypoint Classes"); - MissionItem newItem = selected.getNewItem(oldItem); - mListener.onWaypointTypeChanged(new MissionItemProxy(itemRender.getMissionProxy(), - newItem), itemRender); - dismiss(); - } - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } - } + @Override + public void onSpinnerItemSelected(Spinner spinner, int position) { + final MissionItemType selectedType = commandAdapter.getItem(position); - @Override - public void onNothingSelected(AdapterView arg0) { - } + try { + if (mSelectedProxies == null || mSelectedProxies.isEmpty()) + return; - public MissionItemProxy getItem() { - return itemRender; - } + final List> updatesList = new ArrayList>( + mSelectedProxies.size()); + + for (MissionItemProxy missionItemProxy : mSelectedProxies) { + final MissionItem oldItem = missionItemProxy.getMissionItem(); + if (oldItem.getType() != selectedType) { + updatesList.add(Pair.create(missionItemProxy, new MissionItemProxy( + mMissionProxy, selectedType.getNewItem(oldItem)))); + } + } + if(!updatesList.isEmpty()) { + mListener.onWaypointTypeChanged(updatesList); + dismiss(); + } + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } } \ No newline at end of file diff --git a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionRegionOfInterestFragment.java b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionRegionOfInterestFragment.java index 8be0273066..b0d734fb88 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionRegionOfInterestFragment.java +++ b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionRegionOfInterestFragment.java @@ -4,6 +4,7 @@ import org.droidplanner.android.widgets.spinnerWheel.CardWheelHorizontalView; import org.droidplanner.android.widgets.spinnerWheel.adapters.NumericWheelAdapter; import org.droidplanner.core.helpers.units.Altitude; +import org.droidplanner.core.mission.MissionItem; import org.droidplanner.core.mission.MissionItemType; import org.droidplanner.core.mission.waypoints.RegionOfInterest; @@ -24,21 +25,23 @@ public void onViewCreated(View view, Bundle savedInstanceState) { typeSpinner.setSelection(commandAdapter.getPosition(MissionItemType.ROI)); final NumericWheelAdapter altitudeAdapter = new NumericWheelAdapter(getActivity() - .getApplicationContext(), MIN_ALTITUDE, MAX_ALTITUDE, "%d m"); - altitudeAdapter.setItemResource(R.layout.wheel_text_centered); + .getApplicationContext(), R.layout.wheel_text_centered, MIN_ALTITUDE, + MAX_ALTITUDE, "%d m"); final CardWheelHorizontalView altitudePicker = (CardWheelHorizontalView) view .findViewById(R.id.altitudePicker); altitudePicker.setViewAdapter(altitudeAdapter); - altitudePicker.setCurrentValue((int) ((RegionOfInterest) itemRender.getMissionItem()) + altitudePicker.addChangingListener(this); + altitudePicker.setCurrentValue((int) ((RegionOfInterest) getMissionItems().get(0)) .getCoordinate().getAltitude().valueInMeters()); - altitudePicker.addChangingListener(this); } @Override public void onChanged(CardWheelHorizontalView wheel, int oldValue, int newValue) { switch (wheel.getId()) { case R.id.altitudePicker: - ((RegionOfInterest) itemRender.getMissionItem()).setAltitude(new Altitude(newValue)); + for(MissionItem missionItem: getMissionItems()) { + ((RegionOfInterest) missionItem).setAltitude(new Altitude(newValue)); + } break; } } diff --git a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSplineWaypointFragment.java b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSplineWaypointFragment.java index 29dccf04d1..43fac1b320 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSplineWaypointFragment.java +++ b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSplineWaypointFragment.java @@ -4,6 +4,7 @@ import org.droidplanner.android.widgets.spinnerWheel.CardWheelHorizontalView; import org.droidplanner.android.widgets.spinnerWheel.adapters.NumericWheelAdapter; import org.droidplanner.core.helpers.units.Altitude; +import org.droidplanner.core.mission.MissionItem; import org.droidplanner.core.mission.MissionItemType; import org.droidplanner.core.mission.waypoints.SplineWaypoint; @@ -29,39 +30,39 @@ public void onViewCreated(View view, Bundle savedInstanceState) { typeSpinner.setSelection(commandAdapter.getPosition(MissionItemType.SPLINE_WAYPOINT)); - SplineWaypoint item = (SplineWaypoint) this.itemRender.getMissionItem(); + SplineWaypoint item = (SplineWaypoint) getMissionItems().get(0); - final NumericWheelAdapter delayAdapter = new NumericWheelAdapter(context, 0, 60, "%d s"); - delayAdapter.setItemResource(R.layout.wheel_text_centered); + final NumericWheelAdapter delayAdapter = new NumericWheelAdapter(context, + R.layout.wheel_text_centered, 0, 60, "%d s"); final CardWheelHorizontalView delayPicker = (CardWheelHorizontalView) view .findViewById(R.id.waypointDelayPicker); delayPicker.setViewAdapter(delayAdapter); + delayPicker.addChangingListener(this); delayPicker.setCurrentValue((int) item.getDelay()); - delayPicker.addChangingListener(this); - final NumericWheelAdapter altitudeAdapter = new NumericWheelAdapter(context, MIN_ALTITUDE, - MAX_ALTITUDE, "%d m"); - altitudeAdapter.setItemResource(R.layout.wheel_text_centered); + final NumericWheelAdapter altitudeAdapter = new NumericWheelAdapter(context, + R.layout.wheel_text_centered, MIN_ALTITUDE, MAX_ALTITUDE, "%d m"); final CardWheelHorizontalView altitudePicker = (CardWheelHorizontalView) view .findViewById(R.id.altitudePicker); altitudePicker.setViewAdapter(altitudeAdapter); + altitudePicker.addChangingListener(this); altitudePicker.setCurrentValue((int) item.getCoordinate().getAltitude().valueInMeters()); - altitudePicker.addChangingListener(this); } @Override public void onChanged(CardWheelHorizontalView wheel, int oldValue, int newValue) { - final SplineWaypoint item = (SplineWaypoint) this.itemRender.getMissionItem(); - switch (wheel.getId()) { case R.id.altitudePicker: - item.setAltitude(new Altitude(newValue)); + for(MissionItem item: getMissionItems()) { + ((SplineWaypoint)item).setAltitude(new Altitude(newValue)); + } break; case R.id.waypointDelayPicker: - item.setDelay(newValue); + for(MissionItem item: getMissionItems()) { + ((SplineWaypoint)item).setDelay(newValue); + } break; } - } } diff --git a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSurveyFragment.java b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSurveyFragment.java index c955cbb312..53b9fc0f04 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSurveyFragment.java +++ b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionSurveyFragment.java @@ -10,6 +10,7 @@ import org.droidplanner.core.drone.DroneInterfaces; import org.droidplanner.core.helpers.units.Altitude; import org.droidplanner.core.mission.MissionItemType; +import org.droidplanner.core.mission.survey.CameraInfo; import org.droidplanner.core.mission.survey.Survey; import org.droidplanner.core.model.Drone; @@ -23,6 +24,8 @@ import android.widget.Spinner; import android.widget.TextView; +import java.util.List; + public class MissionSurveyFragment extends MissionDetailFragment implements OnClickListener, CardWheelHorizontalView.OnCardWheelChangedListener, SpinnerSelfSelect.OnSpinnerItemSelectedListener, DroneInterfaces.OnDroneListener { @@ -47,7 +50,7 @@ public class MissionSurveyFragment extends MissionDetailFragment implements OnCl public CheckBox footprintCheckBox; private CamerasAdapter cameraAdapter; - private Survey survey; + private List surveyList; @Override protected int getResource() { @@ -71,10 +74,14 @@ public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final Context context = getActivity().getApplicationContext(); - this.survey = ((Survey) itemRender.getMissionItem()); - typeSpinner.setSelection(commandAdapter.getPosition(MissionItemType.SURVEY)); + cameraAdapter = new CamerasAdapter(getActivity(), + android.R.layout.simple_spinner_dropdown_item); + + this.surveyList = ((List) getMissionItems()); cameraSpinner = (SpinnerSelfSelect) view.findViewById(id.cameraFileSpinner); + cameraSpinner.setAdapter(cameraAdapter); + footprintCheckBox = (CheckBox) view.findViewById(id.CheckBoxFootprints); mAnglePicker = (CardWheelHorizontalView) view.findViewById(id.anglePicker); @@ -105,25 +112,27 @@ public void onViewCreated(View view, Bundle savedInstanceState) { numberOfStripsView = (TextView) view.findViewById(id.numberOfStripsTextView); lengthView = (TextView) view.findViewById(id.lengthTextView); - cameraAdapter = new CamerasAdapter(getActivity(), - android.R.layout.simple_spinner_dropdown_item); - cameraSpinner.setAdapter(cameraAdapter); - footprintCheckBox.setOnClickListener(this); innerWPsCheckbox.setOnClickListener(this); cameraSpinner.setOnSpinnerItemSelectedListener(this); + mAnglePicker.addChangingListener(this); + mOverlapPicker.addChangingListener(this); + mSidelapPicker.addChangingListener(this); + mAltitudePicker.addChangingListener(this); + updateViews(); - mAnglePicker.addChangingListener(this); - mOverlapPicker.addChangingListener(this); - mSidelapPicker.addChangingListener(this); - mAltitudePicker.addChangingListener(this); + typeSpinner.setSelection(commandAdapter.getPosition(MissionItemType.SURVEY)); } @Override public void onSpinnerItemSelected(Spinner spinner, int position) { - survey.setCameraInfo(cameraAdapter.getCamera(position)); + final CameraInfo cameraInfo = cameraAdapter.getCamera(position); + for(Survey survey: surveyList) { + survey.setCameraInfo(cameraInfo); + } + onChanged(mAnglePicker, 0, 0); } @@ -134,13 +143,15 @@ public void onChanged(CardWheelHorizontalView cardWheel, int oldValue, int newVa case R.id.altitudePicker: case R.id.overlapPicker: case R.id.sidelapPicker: - survey.update(mAnglePicker.getCurrentValue(), - new Altitude(mAltitudePicker.getCurrentValue()), - mOverlapPicker.getCurrentValue(), mSidelapPicker.getCurrentValue()); - try { - survey.build(); - mAltitudePicker.setBackgroundResource(R.drawable.bg_cell_white); + for (Survey survey : surveyList) { + survey.update(mAnglePicker.getCurrentValue(), + new Altitude(mAltitudePicker.getCurrentValue()), + mOverlapPicker.getCurrentValue(), mSidelapPicker.getCurrentValue()); + + survey.build(); + } + mAltitudePicker.setBackgroundResource(R.drawable.bg_cell_white); } catch (Exception e) { Log.e(TAG, "Error while building the survey.", e); mAltitudePicker.setBackgroundColor(Color.RED); @@ -173,36 +184,36 @@ private void updateViews() { } private void updateCameraSpinner() { - cameraAdapter.setTitle(survey.surveyData.getCameraName()); + cameraAdapter.setTitle(surveyList.get(0).surveyData.getCameraName()); } private void updateSeekBars() { - mAnglePicker.setCurrentValue(survey.surveyData.getAngle().intValue()); - mOverlapPicker.setCurrentValue((int) survey.surveyData.getOverlap()); - mSidelapPicker.setCurrentValue((int) survey.surveyData.getSidelap()); - mAltitudePicker.setCurrentValue((int) survey.surveyData.getAltitude().valueInMeters()); + mAnglePicker.setCurrentValue(surveyList.get(0).surveyData.getAngle().intValue()); + mOverlapPicker.setCurrentValue((int) surveyList.get(0).surveyData.getOverlap()); + mSidelapPicker.setCurrentValue((int) surveyList.get(0).surveyData.getSidelap()); + mAltitudePicker.setCurrentValue((int) surveyList.get(0).surveyData.getAltitude().valueInMeters()); } private void updateTextViews() { Context context = getActivity(); try { footprintTextView.setText(context.getString(R.string.footprint) + ": " - + survey.surveyData.getLateralFootPrint() + " x" - + survey.surveyData.getLongitudinalFootPrint()); + + surveyList.get(0).surveyData.getLateralFootPrint() + " x" + + surveyList.get(0).surveyData.getLongitudinalFootPrint()); groundResolutionTextView.setText(context.getString(R.string.ground_resolution) + ": " - + survey.surveyData.getGroundResolution() + "/px"); + + surveyList.get(0).surveyData.getGroundResolution() + "/px"); distanceTextView.setText(context.getString(R.string.distance_between_pictures) + ": " - + survey.surveyData.getLongitudinalPictureDistance()); + + surveyList.get(0).surveyData.getLongitudinalPictureDistance()); distanceBetweenLinesTextView.setText(context.getString(R.string.distance_between_lines) - + ": " + survey.surveyData.getLateralPictureDistance()); + + ": " + surveyList.get(0).surveyData.getLateralPictureDistance()); areaTextView - .setText(context.getString(R.string.area) + ": " + survey.polygon.getArea()); + .setText(context.getString(R.string.area) + ": " + surveyList.get(0).polygon.getArea()); lengthView.setText(context.getString(R.string.mission_length) + ": " - + survey.grid.getLength()); + + surveyList.get(0).grid.getLength()); numberOfPicturesView.setText(context.getString(R.string.pictures) + ": " - + survey.grid.getCameraCount()); + + surveyList.get(0).grid.getCameraCount()); numberOfStripsView.setText(context.getString(R.string.number_of_strips) + ": " - + survey.grid.getNumberOfLines()); + + surveyList.get(0).grid.getNumberOfLines()); } catch (Exception e) { footprintTextView.setText(context.getString(R.string.footprint) + ": " + "???"); groundResolutionTextView.setText(context.getString(R.string.ground_resolution) + ": " diff --git a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionTakeoffFragment.java b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionTakeoffFragment.java index 84a0320f2a..f73fa2a0f2 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionTakeoffFragment.java +++ b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionTakeoffFragment.java @@ -4,6 +4,7 @@ import org.droidplanner.android.widgets.spinnerWheel.CardWheelHorizontalView; import org.droidplanner.android.widgets.spinnerWheel.adapters.NumericWheelAdapter; import org.droidplanner.core.helpers.units.Altitude; +import org.droidplanner.core.mission.MissionItem; import org.droidplanner.core.mission.MissionItemType; import org.droidplanner.core.mission.commands.Takeoff; @@ -23,24 +24,26 @@ public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); typeSpinner.setSelection(commandAdapter.getPosition(MissionItemType.TAKEOFF)); - Takeoff item = (Takeoff) this.itemRender.getMissionItem(); + Takeoff item = (Takeoff) getMissionItems().get(0); final NumericWheelAdapter altitudeAdapter = new NumericWheelAdapter(getActivity() - .getApplicationContext(), MIN_ALTITUDE, MAX_ALTITUDE, "%d m"); - altitudeAdapter.setItemResource(R.layout.wheel_text_centered); + .getApplicationContext(), R.layout.wheel_text_centered, MIN_ALTITUDE, + MAX_ALTITUDE, "%d m"); final CardWheelHorizontalView cardAltitudePicker = (CardWheelHorizontalView) view .findViewById(R.id.altitudePicker); cardAltitudePicker.setViewAdapter(altitudeAdapter); + cardAltitudePicker.addChangingListener(this); cardAltitudePicker.setCurrentValue((int) item.getFinishedAlt().valueInMeters()); - cardAltitudePicker.addChangingListener(this); } @Override public void onChanged(CardWheelHorizontalView wheel, int oldValue, int newValue) { switch (wheel.getId()) { case R.id.altitudePicker: - Takeoff item = (Takeoff) this.itemRender.getMissionItem(); - item.setFinishedAlt(new Altitude(newValue)); + for(MissionItem missionItem : getMissionItems()) { + Takeoff item = (Takeoff) missionItem; + item.setFinishedAlt(new Altitude(newValue)); + } break; } } diff --git a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionWaypointFragment.java b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionWaypointFragment.java index 38bf8000ae..6cd4ca6025 100644 --- a/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionWaypointFragment.java +++ b/Android/src/org/droidplanner/android/proxy/mission/item/fragments/MissionWaypointFragment.java @@ -4,6 +4,7 @@ import org.droidplanner.android.widgets.spinnerWheel.CardWheelHorizontalView; import org.droidplanner.android.widgets.spinnerWheel.adapters.NumericWheelAdapter; import org.droidplanner.core.helpers.units.Altitude; +import org.droidplanner.core.mission.MissionItem; import org.droidplanner.core.mission.MissionItemType; import org.droidplanner.core.mission.waypoints.Waypoint; @@ -26,38 +27,38 @@ public void onViewCreated(View view, Bundle savedInstanceState) { typeSpinner.setSelection(commandAdapter.getPosition(MissionItemType.WAYPOINT)); - final Waypoint item = (Waypoint) this.itemRender.getMissionItem(); + final Waypoint item = (Waypoint) getMissionItems().get(0); - final NumericWheelAdapter delayAdapter = new NumericWheelAdapter(context, 0, 60, "%d s"); - delayAdapter.setItemResource(R.layout.wheel_text_centered); + final NumericWheelAdapter delayAdapter = new NumericWheelAdapter(context, + R.layout.wheel_text_centered, 0, 60, "%d s"); final CardWheelHorizontalView delayPicker = (CardWheelHorizontalView) view .findViewById(R.id.waypointDelayPicker); delayPicker.setViewAdapter(delayAdapter); + delayPicker.addChangingListener(this); delayPicker.setCurrentValue((int) item.getDelay()); - delayPicker.addChangingListener(this); - final NumericWheelAdapter altitudeAdapter = new NumericWheelAdapter(context, MIN_ALTITUDE, - MAX_ALTITUDE, "%d m"); - altitudeAdapter.setItemResource(R.layout.wheel_text_centered); + final NumericWheelAdapter altitudeAdapter = new NumericWheelAdapter(context, + R.layout.wheel_text_centered, MIN_ALTITUDE, MAX_ALTITUDE, "%d m"); final CardWheelHorizontalView altitudePicker = (CardWheelHorizontalView) view .findViewById(R.id.altitudePicker); altitudePicker.setViewAdapter(altitudeAdapter); + altitudePicker.addChangingListener(this); altitudePicker.setCurrentValue((int) item.getCoordinate().getAltitude().valueInMeters()); - altitudePicker.addChangingListener(this); - } @Override public void onChanged(CardWheelHorizontalView wheel, int oldValue, int newValue) { - final Waypoint item = (Waypoint) this.itemRender.getMissionItem(); - switch (wheel.getId()) { case R.id.altitudePicker: - item.setAltitude(new Altitude(newValue)); + for(MissionItem item: getMissionItems()) { + ((Waypoint)item).setAltitude(new Altitude(newValue)); + } break; case R.id.waypointDelayPicker: - item.setDelay(newValue); + for(MissionItem item: getMissionItems()) { + ((Waypoint)item).setDelay(newValue); + } break; } diff --git a/Core/src/org/droidplanner/core/mission/Mission.java b/Core/src/org/droidplanner/core/mission/Mission.java index baeb713fd3..2d3e2401b2 100644 --- a/Core/src/org/droidplanner/core/mission/Mission.java +++ b/Core/src/org/droidplanner/core/mission/Mission.java @@ -20,6 +20,7 @@ import org.droidplanner.core.mission.waypoints.SplineWaypoint; import org.droidplanner.core.mission.waypoints.Waypoint; import org.droidplanner.core.model.Drone; +import org.droidplanner.core.util.Pair; import com.MAVLink.Messages.ardupilotmega.msg_mission_ack; import com.MAVLink.Messages.ardupilotmega.msg_mission_item; @@ -134,12 +135,41 @@ public Altitude getLastAltitude() { * new mission item */ public void replace(MissionItem oldItem, MissionItem newItem) { - int index = items.indexOf(oldItem); + final int index = items.indexOf(oldItem); + if(index == -1){ + return; + } + items.remove(index); items.add(index, newItem); notifyMissionUpdate(); } + public void replaceAll(List> updatesList){ + if(updatesList == null || updatesList.isEmpty()){ + return; + } + + boolean wasUpdated = false; + for(Pair updatePair : updatesList){ + final MissionItem oldItem = updatePair.first; + final int index = items.indexOf(oldItem); + if(index == -1){ + continue; + } + + final MissionItem newItem = updatePair.second; + items.remove(index); + items.add(index, newItem); + + wasUpdated = true; + } + + if(wasUpdated) { + notifyMissionUpdate(); + } + } + /** * Reverse the order of the mission items. */ diff --git a/Core/src/org/droidplanner/core/util/Pair.java b/Core/src/org/droidplanner/core/util/Pair.java new file mode 100644 index 0000000000..d6288d0e41 --- /dev/null +++ b/Core/src/org/droidplanner/core/util/Pair.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.droidplanner.core.util; + +/** + * Container to ease passing around a tuple of two objects. This object provides a sensible + * implementation of equals(), returning true if equals() is true on each of the contained + * objects. + */ +public class Pair { + public final F first; + public final S second; + + /** + * Constructor for a Pair. + * + * @param first the first object in the Pair + * @param second the second object in the pair + */ + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + /** + * Checks the two objects for equality by delegating to their respective + * {@link Object#equals(Object)} methods. + * + * @param o the {@link Pair} to which this one is to be checked for equality + * @return true if the underlying objects of the Pair are both considered + * equal + */ + @Override + public boolean equals(Object o) { + if(o == this) + return true; + + if (!(o instanceof Pair)) { + return false; + } + Pair p = (Pair) o; + return (p.first == null) ? (first == null) : p.first.equals(first) + && (p.second == null) ? (second == null) : p.second.equals(second); + } + + /** + * Compute a hash code using the hash codes of the underlying objects + * + * @return a hashcode of the Pair + */ + @Override + public int hashCode() { + return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); + } + + /** + * Convenience method for creating an appropriately typed pair. + * @param a the first object in the Pair + * @param b the second object in the pair + * @return a Pair that is templatized with the types of a and b + */ + public static Pair create(A a, B b) { + return new Pair(a, b); + } +}