/* * Catroid: An on-device visual programming system for Android devices * Copyright (C) 2010-2016 The Catrobat Team * (<http://developer.catrobat.org/credits>) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * An additional term exception under section 7 of the GNU Affero * General Public License, version 3, is available at * http://developer.catrobat.org/license_additional_term * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.catrobat.catroid.ui.adapter; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ListView; import org.catrobat.catroid.ProjectManager; import org.catrobat.catroid.R; import org.catrobat.catroid.content.Script; import org.catrobat.catroid.content.Sprite; import org.catrobat.catroid.content.StartScript; import org.catrobat.catroid.content.bricks.AllowedAfterDeadEndBrick; import org.catrobat.catroid.content.bricks.Brick; import org.catrobat.catroid.content.bricks.BrickBaseType; import org.catrobat.catroid.content.bricks.BrickViewProvider; import org.catrobat.catroid.content.bricks.DeadEndBrick; import org.catrobat.catroid.content.bricks.FormulaBrick; import org.catrobat.catroid.content.bricks.IfLogicElseBrick; import org.catrobat.catroid.content.bricks.IfLogicEndBrick; import org.catrobat.catroid.content.bricks.IfThenLogicEndBrick; import org.catrobat.catroid.content.bricks.LoopEndBrick; import org.catrobat.catroid.content.bricks.LoopEndlessBrick; import org.catrobat.catroid.content.bricks.NestingBrick; import org.catrobat.catroid.content.bricks.ScriptBrick; import org.catrobat.catroid.content.bricks.UserBrick; import org.catrobat.catroid.content.bricks.UserBrickParameter; import org.catrobat.catroid.content.bricks.UserScriptDefinitionBrick; import org.catrobat.catroid.ui.ViewSwitchLock; import org.catrobat.catroid.ui.controller.BackPackListManager; import org.catrobat.catroid.ui.dialogs.CustomAlertDialogBuilder; import org.catrobat.catroid.ui.dragndrop.BrickDragAndDropListView; import org.catrobat.catroid.ui.dragndrop.DragAndDropListener; import org.catrobat.catroid.ui.fragment.CategoryBricksFactory; import org.catrobat.catroid.ui.fragment.ScriptFragment; import org.catrobat.catroid.utils.SnackbarUtil; import org.catrobat.catroid.utils.UtilDeviceInfo; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.locks.Lock; public class BrickAdapter extends BrickBaseAdapter implements DragAndDropListener, OnClickListener, ActionModeActivityAdapterInterface { public enum ActionModeEnum { NO_ACTION, COPY_DELETE, BACKPACK, COMMENT_OUT } private static final String TAG = BrickAdapter.class.getSimpleName(); public int listItemCount = 0; private Sprite sprite; private UserBrick userBrick; private Script script; private int dragTargetPosition; private Brick draggedBrick; private BrickDragAndDropListView brickDragAndDropListView; private View insertionView; private boolean initInsertedBrick; private boolean addingNewBrick; private int positionOfInsertedBrick; private Script scriptToDelete; private boolean firstDrag; private int fromBeginDrag; private int toEndDrag; private boolean retryScriptDragging; private boolean showDetails = false; public boolean isDragging = false; private List<Brick> animatedBricks; private int selectMode; private Lock viewSwitchLock = new ViewSwitchLock(); private int clickItemPosition = 0; private AlertDialog alertDialog = null; private ActionModeEnum actionMode = ActionModeEnum.NO_ACTION; public BrickAdapter(ScriptFragment scriptFragment, Sprite sprite, BrickDragAndDropListView listView) { this.scriptFragment = scriptFragment; this.context = scriptFragment.getActivity(); this.sprite = sprite; brickDragAndDropListView = listView; insertionView = View.inflate(context, R.layout.brick_insert, null); initInsertedBrick = false; addingNewBrick = false; firstDrag = true; retryScriptDragging = false; animatedBricks = new ArrayList<>(); this.selectMode = ListView.CHOICE_MODE_NONE; initBrickList(); } public Context getContext() { return context; } public void initBrickList() { brickList = new ArrayList<>(); if (userBrick != null) { initBrickListUserScript(); return; } Sprite sprite = ProjectManager.getInstance().getCurrentSprite(); int numberOfScripts = sprite.getNumberOfScripts(); for (int scriptPosition = 0; scriptPosition < numberOfScripts; scriptPosition++) { Script script = sprite.getScript(scriptPosition); brickList.add(script.getScriptBrick()); script.getScriptBrick().setBrickAdapter(this); for (Brick brick : script.getBrickList()) { brickList.add(brick); brick.setBrickAdapter(this); } } } private void initBrickListUserScript() { script = getUserScript(); brickList = new ArrayList<>(); brickList.add(script.getScriptBrick()); script.getScriptBrick().setBrickAdapter(this); for (Brick brick : script.getBrickList()) { brickList.add(brick); brick.setBrickAdapter(this); } } private Script getUserScript() { UserScriptDefinitionBrick definitionBrick = userBrick.getDefinitionBrick(); return definitionBrick.getScriptSafe(); } public void resetAlphas() { for (Brick brick : brickList) { brick.setAlpha(BrickViewProvider.ALPHA_FULL); } notifyDataSetChanged(); } public ActionModeEnum getActionMode() { return actionMode; } public void setActionMode(ActionModeEnum actionMode) { this.actionMode = actionMode; } public List<Brick> getBrickList() { return brickList; } public void setBrickList(List<Brick> brickList) { this.brickList = brickList; } @Override public void drag(int from, int to) { int toOriginal = to; if (to < 0 || to >= brickList.size()) { to = brickList.size() - 1; } if (from < 0 || from >= brickList.size()) { from = brickList.size() - 1; } if (draggedBrick == null) { draggedBrick = (Brick) getItem(from); notifyDataSetChanged(); } if (firstDrag) { fromBeginDrag = from; firstDrag = false; } if (draggedBrick instanceof NestingBrick) { NestingBrick nestingBrick = (NestingBrick) draggedBrick; if (nestingBrick.isInitialized()) { if (nestingBrick.getAllNestingBrickParts(true).get(0) == nestingBrick) { to = adjustNestingBrickDraggedPosition(nestingBrick, fromBeginDrag, to); } else { to = getDraggedNestingBricksToPosition(nestingBrick, to); } } } else if (draggedBrick instanceof ScriptBrick) { int currentPosition = to; brickList.remove(draggedBrick); brickList.add(to, draggedBrick); to = getNewPositionForScriptBrick(to, draggedBrick); dragTargetPosition = to; if (currentPosition != to) { retryScriptDragging = true; } else { retryScriptDragging = false; } } to = getNewPositionIfEndingBrickIsThere(to, draggedBrick); if (!(draggedBrick instanceof ScriptBrick)) { if (to != 0) { dragTargetPosition = to; } else { dragTargetPosition = 1; to = 1; } } brickList.remove(draggedBrick); if (dragTargetPosition >= 0 && dragTargetPosition <= brickList.size()) { brickList.add(dragTargetPosition, draggedBrick); toEndDrag = to; } else { brickList.add(toOriginal, draggedBrick); toEndDrag = toOriginal; } animatedBricks.clear(); notifyDataSetChanged(); } private int getNewPositionIfEndingBrickIsThere(int to, Brick brick) { int currentPosition = brickList.indexOf(brick); if (getItem(to) instanceof AllowedAfterDeadEndBrick && !(getItem(to) instanceof DeadEndBrick) && getItem(to - 1) instanceof DeadEndBrick) { if (currentPosition > to) { return to + 1; } else { return to; } } else if (getItem(to) instanceof DeadEndBrick) { for (int i = to - 1; i >= 0; i--) { if (!(getItem(i) instanceof DeadEndBrick)) { if (currentPosition > i) { return i + 1; } else { return i; } } } } return to; } private int adjustNestingBrickDraggedPosition(NestingBrick nestingBrick, int from, int to) { List<NestingBrick> nestingBrickList = nestingBrick.getAllNestingBrickParts(true); NestingBrick endBrick = nestingBrickList.get(nestingBrickList.size() - 1); int endBrickPosition = brickList.indexOf(endBrick); boolean isNewPositionBetweenStartAndEndNestedBrick = to > from && to < endBrickPosition; if (isNewPositionBetweenStartAndEndNestedBrick) { return endBrickPosition; } return to; } private int getDraggedNestingBricksToPosition(NestingBrick nestingBrick, int to) { List<NestingBrick> nestingBrickList = nestingBrick.getAllNestingBrickParts(true); int restrictedTop = 0; int restrictedBottom = brickList.size(); int tempPosition; int currentPosition = to; boolean passedBrick = false; for (NestingBrick temp : nestingBrickList) { tempPosition = brickList.indexOf(temp); if (temp != nestingBrick) { if (!passedBrick) { restrictedTop = tempPosition; } if (passedBrick) { restrictedBottom = tempPosition; break; } } else { passedBrick = true; currentPosition = tempPosition; } } for (int i = currentPosition; i > restrictedTop; i--) { if (checkIfScriptOrOtherNestingBrick(brickList.get(i), nestingBrickList)) { restrictedTop = i; break; } } for (int i = currentPosition; i < restrictedBottom; i++) { if (checkIfScriptOrOtherNestingBrick(brickList.get(i), nestingBrickList)) { restrictedBottom = i; break; } } to = to <= restrictedTop ? restrictedTop + 1 : to; to = to >= restrictedBottom ? restrictedBottom - 1 : to; return to; } private boolean checkIfScriptOrOtherNestingBrick(Brick brick, List<NestingBrick> nestingBrickList) { if (brick instanceof ScriptBrick) { return true; } if (brick instanceof NestingBrick && !nestingBrickList.contains(brick)) { return true; } return false; } @Override public void drop() { int to = toEndDrag; if (to < 0 || to >= brickList.size()) { to = brickList.size() - 1; } if (retryScriptDragging || to != getNewPositionForScriptBrick(to, draggedBrick)) { scrollToPosition(dragTargetPosition); draggedBrick = null; initInsertedBrick = true; positionOfInsertedBrick = dragTargetPosition; notifyDataSetChanged(); retryScriptDragging = false; return; } int tempTo = getNewPositionIfEndingBrickIsThere(to, draggedBrick); if (to != tempTo) { to = tempTo; } if (addingNewBrick) { if (draggedBrick instanceof ScriptBrick) { addScriptToProject(to, (ScriptBrick) draggedBrick); } else { if (script != null) { addBrickToPositionInUserScript(to, draggedBrick); } else { addBrickToPositionInProject(to, draggedBrick); } } if (!draggedBrick.isCommentedOut()) { enableCorrespondingScriptBrick(to); } if (draggedBrick instanceof UserBrick) { ((UserBrick) draggedBrick).updateUserBrickParametersAndVariables(); } addingNewBrick = false; } else { if (script != null) { moveUserBrick(fromBeginDrag, toEndDrag); } else { if (draggedBrick instanceof NestingBrick) { moveNestingBrick(fromBeginDrag, toEndDrag); } else { moveExistingProjectBrick(fromBeginDrag, toEndDrag); } } if (!draggedBrick.isCommentedOut()) { enableCorrespondingScriptBrick(toEndDrag); } } draggedBrick = null; firstDrag = true; initBrickList(); notifyDataSetChanged(); int scrollTo = to; if (scrollTo >= brickList.size() - 1) { scrollTo = getCount() - 1; } brickDragAndDropListView.smoothScrollToPosition(scrollTo); setSpinnersEnabled(true); isDragging = false; SnackbarUtil.showHintSnackbar(((Activity) getContext()), R.string.hint_brick_added); } private void addScriptToProject(int position, ScriptBrick scriptBrick) { Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); int[] temp = getScriptAndBrickIndexFromProject(position); int scriptPosition = temp[0]; int brickPosition = temp[1]; Script newScript = scriptBrick.getScriptSafe(); if (currentSprite.getNumberOfBricks() > 0) { int addScriptTo = position == 0 ? 0 : scriptPosition + 1; currentSprite.addScript(addScriptTo, newScript); } else { currentSprite.addScript(newScript); } Script previousScript = currentSprite.getScript(scriptPosition); if (previousScript != null) { Brick brick; int size = previousScript.getBrickList().size(); for (int i = brickPosition; i < size; i++) { brick = previousScript.getBrick(brickPosition); previousScript.removeBrick(brick); newScript.addBrick(brick); } } ProjectManager.getInstance().setCurrentScript(newScript); } private void moveUserBrick(int from, int to) { Brick brick = script.getBrick(getPositionInUserScript(from)); script.removeBrick(brick); script.addBrick(getPositionInUserScript(to), brick); } private void moveNestingBrick(int from, int to) { Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); int[] tempFrom = getScriptAndBrickIndexFromProject(from); int scriptPositionFrom = tempFrom[0]; int brickPositionFrom = tempFrom[1]; Script fromScript = currentSprite.getScript(scriptPositionFrom); Brick brick = fromScript.getBrick(brickPositionFrom); NestingBrick nestingBrick = (NestingBrick) brick; List<NestingBrick> nestingBricks = nestingBrick.getAllNestingBrickParts(true); NestingBrick endNestingBrick = nestingBricks.get(nestingBricks.size() - 1); List<Brick> fromScriptBrickList = fromScript.getBrickList(); int endPosition = fromScriptBrickList.indexOf(endNestingBrick); int count = endPosition - brickPositionFrom; boolean isNewPositionBetweenStartAndEndNestedBrick = to > from && count > to - from; if (isNewPositionBetweenStartAndEndNestedBrick) { return; // moved nested block into itself. prevent this by the UI! } List<Brick> block = fromScriptBrickList.subList(brickPositionFrom, endPosition + 1); List<Brick> removedBlock = new ArrayList<>(); removedBlock.add(block.remove(0)); int[] tempTo = getScriptAndBrickIndexFromProject(to); int scriptPositionTo = tempTo[0]; int brickPositionTo = tempTo[1]; Script toScript = currentSprite.getScript(scriptPositionTo); removedBlock.addAll(block); block.clear(); int finalBrickPositionTo = brickPositionTo; boolean moveBrickInSameScript = scriptPositionTo == scriptPositionFrom && to > from; if (moveBrickInSameScript) { finalBrickPositionTo -= count; } toScript.getBrickList().addAll(finalBrickPositionTo, removedBlock); } private void moveExistingProjectBrick(int from, int to) { Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); int[] tempFrom = getScriptAndBrickIndexFromProject(from); int scriptPositionFrom = tempFrom[0]; int brickPositionFrom = tempFrom[1]; Script fromScript = currentSprite.getScript(scriptPositionFrom); Brick brick = fromScript.getBrick(brickPositionFrom); if (draggedBrick != brick) { Log.e(TAG, "Want to save wrong brick"); return; } fromScript.removeBrick(brick); int[] tempTo = getScriptAndBrickIndexFromProject(to); int scriptPositionTo = tempTo[0]; int brickPositionTo = tempTo[1]; Script toScript = currentSprite.getScript(scriptPositionTo); toScript.addBrick(brickPositionTo, brick); } private void addBrickToPositionInProject(int position, Brick brick) { Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); int[] temp = getScriptAndBrickIndexFromProject(position); int scriptPosition = temp[0]; int brickPosition = temp[1]; Script script = currentSprite.getScript(scriptPosition); if (brick instanceof NestingBrick) { ((NestingBrick) draggedBrick).initialize(); List<NestingBrick> nestingBrickList = ((NestingBrick) draggedBrick).getAllNestingBrickParts(true); for (int i = 0; i < nestingBrickList.size(); i++) { if (nestingBrickList.get(i) instanceof DeadEndBrick) { if (i < nestingBrickList.size() - 1) { Log.w(TAG, "Adding a DeadEndBrick in the middle of the NestingBricks"); } position = getPositionForDeadEndBrick(position); temp = getScriptAndBrickIndexFromProject(position); script.addBrick(temp[1], (Brick) nestingBrickList.get(i)); } else { script.addBrick(brickPosition + i, (Brick) nestingBrickList.get(i)); } } } else { script.addBrick(brickPosition, brick); } } private void addBrickToPositionInUserScript(int position, Brick brick) { position = getPositionInUserScript(position); if (brick instanceof NestingBrick) { ((NestingBrick) draggedBrick).initialize(); List<NestingBrick> nestingBrickList = ((NestingBrick) draggedBrick).getAllNestingBrickParts(true); for (int i = 0; i < nestingBrickList.size(); i++) { script.addBrick(position + i, (Brick) nestingBrickList.get(i)); } } else { script.addBrick(position, brick); } } private int getPositionForDeadEndBrick(int position) { for (int i = position + 1; i < brickList.size(); i++) { if (brickList.get(i) instanceof AllowedAfterDeadEndBrick || brickList.get(i) instanceof DeadEndBrick) { return i; } if (brickList.get(i) instanceof NestingBrick) { List<NestingBrick> tempList = ((NestingBrick) brickList.get(i)).getAllNestingBrickParts(true); int currentPosition = i; i = brickList.indexOf(tempList.get(tempList.size() - 1)) + 1; if (i < 0) { i = currentPosition; } else if (i >= brickList.size()) { return brickList.size(); } } if (brickList.get(i) instanceof AllowedAfterDeadEndBrick || brickList.get(i) instanceof DeadEndBrick) { return i; } } return brickList.size(); } private int getPositionInUserScript(int position) { position--; if (position < 0) { position = 0; } if (position >= brickList.size()) { return brickList.size() - 1; } List<Brick> brickListFromScript = script.getBrickList(); Brick scriptBrick; if (brickListFromScript.size() != 0 && position < brickListFromScript.size()) { scriptBrick = brickListFromScript.get(position); } else { scriptBrick = null; } int returnValue = script.getBrickList().indexOf(scriptBrick); if (returnValue < 0) { returnValue = script.getBrickList().size(); } return returnValue; } private int[] getScriptAndBrickIndexFromProject(int position) { int[] returnValue = new int[2]; if (position >= brickList.size()) { returnValue[0] = sprite.getNumberOfScripts() - 1; if (returnValue[0] < 0) { returnValue[0] = 0; returnValue[1] = 0; } else { Script script = sprite.getScript(returnValue[0]); if (script != null) { returnValue[1] = script.getBrickList().size(); } else { returnValue[1] = 0; } } return returnValue; } int scriptPosition = 0; int scriptOffset; for (scriptOffset = 0; scriptOffset < position; ) { scriptOffset += sprite.getScript(scriptPosition).getBrickList().size() + 1; if (scriptOffset < position) { scriptPosition++; } } scriptOffset -= sprite.getScript(scriptPosition).getBrickList().size(); returnValue[0] = scriptPosition; List<Brick> brickListFromProject = sprite.getScript(scriptPosition).getBrickList(); int brickPosition = position; if (scriptOffset > 0) { brickPosition -= scriptOffset; } Brick brickFromProject; if (brickListFromProject.size() != 0 && brickPosition < brickListFromProject.size()) { brickFromProject = brickListFromProject.get(brickPosition); } else { brickFromProject = null; } returnValue[1] = sprite.getScript(scriptPosition).getBrickList().indexOf(brickFromProject); if (returnValue[1] < 0) { returnValue[1] = sprite.getScript(scriptPosition).getBrickList().size(); } return returnValue; } private void scrollToPosition(final int position) { BrickDragAndDropListView list = brickDragAndDropListView; if (list.getFirstVisiblePosition() < position && position < list.getLastVisiblePosition()) { return; } list.setIsScrolling(); if (position <= list.getFirstVisiblePosition()) { list.smoothScrollToPosition(0, position + 2); } else { list.smoothScrollToPosition(brickList.size() - 1, position - 2); } } public void addNewBrick(int position, Brick brickToBeAdded, boolean initInsertedBrick) { if (draggedBrick != null) { Log.w(TAG, "Want to add Brick while there is another one currently dragged."); return; } Sprite currentSprite = ProjectManager.getInstance().getCurrentSprite(); int scriptCount = currentSprite.getNumberOfScripts(); if (scriptCount == 0 && brickToBeAdded instanceof ScriptBrick) { currentSprite.addScript(((ScriptBrick) brickToBeAdded).getScriptSafe()); initBrickList(); notifyDataSetChanged(); return; } if (position < 0) { position = 0; } else if (position > brickList.size()) { position = brickList.size(); } if (brickToBeAdded instanceof ScriptBrick) { brickList.add(position, brickToBeAdded); position = getNewPositionForScriptBrick(position, brickToBeAdded); brickList.remove(brickToBeAdded); brickList.add(position, brickToBeAdded); scrollToPosition(position); } else { position = getNewPositionIfEndingBrickIsThere(position, brickToBeAdded); position = position <= 0 ? 1 : position; position = position > brickList.size() ? brickList.size() : position; brickList.add(position, brickToBeAdded); } this.initInsertedBrick = initInsertedBrick; this.positionOfInsertedBrick = position; if (scriptCount == 0 && userBrick == null) { Script script = new StartScript(); currentSprite.addScript(script); brickList.add(0, script.getScriptBrick()); ProjectManager.getInstance().setCurrentScript(script); clearCheckedItems(); positionOfInsertedBrick = 1; } notifyDataSetChanged(); } private int getNewPositionForScriptBrick(int position, Brick brick) { if (brickList.size() == 0) { return 0; } if (!(brick instanceof ScriptBrick)) { return position; } int lastPossiblePosition = position; int nextPossiblePosition = position; for (int i = position; i < brickList.size(); i++) { if (brickList.get(i) instanceof NestingBrick) { List<NestingBrick> bricks = ((NestingBrick) brickList.get(i)).getAllNestingBrickParts(true); int beginningPosition = brickList.indexOf(bricks.get(0)); int endingPosition = brickList.indexOf(bricks.get(bricks.size() - 1)); if (position >= beginningPosition && position <= endingPosition) { lastPossiblePosition = beginningPosition; nextPossiblePosition = endingPosition; i = endingPosition; } } if (brickList.get(i) instanceof ScriptBrick && brickList.get(i) != brick) { break; } } if (position <= lastPossiblePosition) { return position; } else if (position - lastPossiblePosition < nextPossiblePosition - position) { return lastPossiblePosition; } else { return nextPossiblePosition; } } @Override public void remove(int iWillBeIgnored) { // list will not be changed until user action ACTION_UP - therefore take the value from the begin removeFromBrickListAndProject(fromBeginDrag, false); } public void removeFromBrickListAndProject(int index, boolean removeScript) { if (addingNewBrick) { brickList.remove(draggedBrick); } else if (script == null) { int[] temp = getScriptAndBrickIndexFromProject(index); Script script = ProjectManager.getInstance().getCurrentSprite().getScript(temp[0]); if (script != null) { BrickBaseType brick = (BrickBaseType) script.getBrick(temp[1]); if (brick instanceof NestingBrick) { for (NestingBrick tempBrick : ((NestingBrick) brick).getAllNestingBrickParts(true)) { script.removeBrick((Brick) tempBrick); } } else { script.removeBrick(brick); } if (removeScript) { brickList.remove(script); } } } else { BrickBaseType brick = (BrickBaseType) script.getBrick(getPositionInUserScript(index)); if (brick instanceof NestingBrick) { for (NestingBrick tempBrick : ((NestingBrick) brick).getAllNestingBrickParts(true)) { script.removeBrick((Brick) tempBrick); } } else { script.removeBrick(brick); } } firstDrag = true; draggedBrick = null; addingNewBrick = false; initBrickList(); notifyDataSetChanged(); } public void removeDraggedBrick() { if (!addingNewBrick) { draggedBrick = null; firstDrag = true; notifyDataSetChanged(); return; } brickList.remove(draggedBrick); firstDrag = true; draggedBrick = null; addingNewBrick = false; initBrickList(); notifyDataSetChanged(); } @Override public int getCount() { return brickList.size(); } @Override public Object getItem(int element) { if (element < 0 || element >= brickList.size()) { return null; } return brickList.get(element); } @Override public long getItemId(int index) { return index; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (draggedBrick != null && dragTargetPosition == position) { return insertionView; } listItemCount = position + 1; BrickBaseType brick = (BrickBaseType) getItem(position); View currentBrickView = brick.getView(context, position, this); BrickViewProvider.setSaturationOnView(currentBrickView, brick.isCommentedOut()); currentBrickView.setOnClickListener(this); if (!(brick instanceof ScriptBrick)) { currentBrickView.setOnLongClickListener(brickDragAndDropListView); } boolean enableSpinners = !isDragging && actionMode == ActionModeEnum.NO_ACTION; setSpinnersEnabled(enableSpinners); if (position == positionOfInsertedBrick && initInsertedBrick && (selectMode == ListView.CHOICE_MODE_NONE)) { initInsertedBrick = false; addingNewBrick = true; brickDragAndDropListView.setInsertedBrick(position); brickDragAndDropListView.setDraggingNewBrick(); brickDragAndDropListView.onLongClick(currentBrickView); return insertionView; } return currentBrickView; } public void updateProjectBrickList() { initBrickList(); notifyDataSetChanged(); } @Override public void setTouchedScript(int index) { if (index >= 0 && index < brickList.size() && brickList.get(index) instanceof ScriptBrick && draggedBrick == null) { int scriptIndex = getScriptIndexFromProject(index); if (scriptIndex == -1) { Log.e(TAG, "setTouchedScript() Could not get ScriptIndex. index was " + index); return; } ProjectManager.getInstance().setCurrentScript(sprite.getScript(scriptIndex)); } } private int getScriptIndexFromProject(int index) { int scriptIndex = 0; Script temporaryScript; for (int i = 0; i < index; ) { temporaryScript = sprite.getScript(scriptIndex); if (temporaryScript == null) { Log.e(TAG, "getScriptIndexFromProject() tmpScript was null. Index was " + index + " scriptIndex was " + scriptIndex); return -1; } i += temporaryScript.getBrickList().size() + 1; if (i <= index) { scriptIndex++; } } return scriptIndex; } public int getChildCountFromLastGroup() { return ProjectManager.getInstance().getCurrentSprite().getScript(getScriptCount() - 1).getBrickList().size(); } public Brick getChild(int scriptPosition, int brickPosition) { Sprite sprite = ProjectManager.getInstance().getCurrentSprite(); return sprite.getScript(scriptPosition).getBrick(brickPosition); } public int getScriptCount() { return ProjectManager.getInstance().getCurrentSprite().getNumberOfScripts(); } public AlertDialog getAlertDialog() { return alertDialog; } @Override public void onClick(final View view) { if (actionMode != ActionModeEnum.NO_ACTION) { return; } if (isDragging) { return; } if (!viewSwitchLock.tryLock()) { return; } animatedBricks.clear(); final int itemPosition = calculateItemPositionAndTouchPointY(view); final List<CharSequence> items = new ArrayList<>(); if (brickList.get(itemPosition) instanceof ScriptBrick) { int scriptIndex = getScriptIndexFromProject(itemPosition); ProjectManager.getInstance().setCurrentScript(sprite.getScript(scriptIndex)); } if (!(brickList.get(itemPosition) instanceof DeadEndBrick) && !(brickList.get(itemPosition) instanceof ScriptBrick)) { items.add(context.getText(R.string.brick_context_dialog_move_brick)); } if (brickList.get(itemPosition) instanceof NestingBrick) { items.add(context.getText(R.string.brick_context_dialog_animate_bricks)); } if (!(brickList.get(itemPosition) instanceof ScriptBrick)) { items.add(context.getText(R.string.brick_context_dialog_copy_brick)); items.add(context.getText(R.string.brick_context_dialog_delete_brick)); } else { items.add(context.getText(R.string.brick_context_dialog_delete_script)); items.add(context.getText(R.string.backpack_add)); } if (brickHasAFormula(brickList.get(itemPosition))) { items.add(context.getText(R.string.brick_context_dialog_formula_edit_brick)); } if (brickList.get(itemPosition).isCommentedOut()) { items.add(context.getText(R.string.brick_context_dialog_comment_in)); } else { items.add(context.getText(R.string.brick_context_dialog_comment_out)); } if (!(brickList.get(itemPosition) instanceof UserBrick) && !isBrickWithoutDescription(brickList.get(itemPosition))) { items.add(context.getText(R.string.brick_context_dialog_help)); } AlertDialog.Builder builder = new AlertDialog.Builder(context); boolean drawingCacheEnabled = view.isDrawingCacheEnabled(); view.setDrawingCacheEnabled(true); view.setDrawingCacheBackgroundColor(Color.TRANSPARENT); view.buildDrawingCache(true); if (view.getDrawingCache() != null) { Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache()); view.setDrawingCacheEnabled(drawingCacheEnabled); ImageView imageView = brickDragAndDropListView.getGlowingBorder(bitmap); builder.setCustomTitle(imageView); } builder.setItems(items.toArray(new CharSequence[items.size()]), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { CharSequence clickedItemText = items.get(item); if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_move_brick))) { view.performLongClick(); } else if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_copy_brick))) { copyBrickListAndProject(itemPosition); } else if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_delete_brick)) || clickedItemText.equals(context.getText(R.string.brick_context_dialog_delete_script))) { showConfirmDeleteDialog(itemPosition); } else if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_animate_bricks))) { int itemPosition = calculateItemPositionAndTouchPointY(view); Brick brick = brickList.get(itemPosition); if (brick instanceof NestingBrick) { List<NestingBrick> list = ((NestingBrick) brick).getAllNestingBrickParts(true); for (NestingBrick tempBrick : list) { animatedBricks.add((Brick) tempBrick); } } notifyDataSetChanged(); } else if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_formula_edit_brick))) { clickedEditFormula(brickList.get(itemPosition), view); } else if (clickedItemText.equals(context.getText(R.string.backpack_add))) { int currentPosition = itemPosition; checkedBricks.add(brickList.get(currentPosition)); currentPosition++; while (!(currentPosition >= brickList.size() || brickList.get(currentPosition) instanceof ScriptBrick)) { checkedBricks.add(brickList.get(currentPosition)); currentPosition++; } List<String> backPackedScriptGroups = BackPackListManager.getInstance().getAllBackPackedScriptGroups(); showNewGroupBackPackDialog(backPackedScriptGroups, false); } else if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_comment_in))) { commentBrickOut(brickList.get(itemPosition), false); } else if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_comment_out))) { commentBrickOut(brickList.get(itemPosition), true); } else if (clickedItemText.equals(context.getText(R.string.brick_context_dialog_help))) { openHelpPageForBrick(brickList.get(itemPosition)); } } }); alertDialog = builder.create(); if ((selectMode == ListView.CHOICE_MODE_NONE)) { alertDialog.show(); } } protected void copyBrickListAndProject(int itemPosition) { Brick origin = (Brick) (brickDragAndDropListView.getItemAtPosition(itemPosition)); Brick copy; try { copy = origin.clone(); copy.setCommentedOut(origin.isCommentedOut()); addNewBrick(itemPosition, copy, true); notifyDataSetChanged(); } catch (CloneNotSupportedException exception) { Log.e(TAG, Log.getStackTraceString(exception)); } } private void showConfirmDeleteDialog(int itemPosition) { this.clickItemPosition = itemPosition; int titleId; if (getItem(clickItemPosition) instanceof ScriptBrick) { titleId = R.string.dialog_confirm_delete_script_title; } else { titleId = R.string.dialog_confirm_delete_brick_title; } AlertDialog.Builder builder = new CustomAlertDialogBuilder(context); builder.setTitle(titleId); builder.setMessage(R.string.dialog_confirm_delete_brick_message); builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { if (getItem(clickItemPosition) instanceof ScriptBrick) { scriptToDelete = ((ScriptBrick) getItem(clickItemPosition)).getScriptSafe(); handleScriptDelete(sprite, scriptToDelete); scriptToDelete = null; } else { removeFromBrickListAndProject(clickItemPosition, false); } } }); builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { scriptToDelete = null; } }); AlertDialog alertDialog = builder.create(); alertDialog.show(); } private void clickedEditFormula(Brick brick, View view) { FormulaBrick formulaBrick = null; if (brick instanceof FormulaBrick) { formulaBrick = (FormulaBrick) brick; } if (brick instanceof UserBrick) { List<UserBrickParameter> userBrickParameters = ((UserBrick) brick).getUserBrickParameters(); if (userBrickParameters != null && userBrickParameters.size() > 0) { formulaBrick = userBrickParameters.get(0); } } if (formulaBrick != null) { formulaBrick.showFormulaEditorToEditFormula(view); } } private boolean brickHasAFormula(Brick brick) { boolean multiFormulaValid = false; if (brick instanceof UserBrick) { multiFormulaValid = ((UserBrick) brick).getFormulas().size() > 0; } return (brick instanceof FormulaBrick || multiFormulaValid); } private boolean isBrickWithoutDescription(Brick brick) { String name = brick.getClass().getSimpleName(); if (name.equals(IfLogicElseBrick.class.getSimpleName())) { return true; } else if (name.equals(IfLogicEndBrick.class.getSimpleName())) { return true; } else if (name.equals(IfThenLogicEndBrick.class.getSimpleName())) { return true; } else if (name.equals(LoopEndlessBrick.class.getSimpleName())) { return true; } else if (name.equals(LoopEndBrick.class.getSimpleName())) { return true; } return false; } private void openHelpPageForBrick(Brick brick) { CategoryBricksFactory categoryBricksFactory = new CategoryBricksFactory(); String language = UtilDeviceInfo.getUserLanguageCode(); String category = categoryBricksFactory.getBrickCategory(brick, sprite, context); String name = brick.getClass().getSimpleName(); if (!language.equals("en") && !language.equals("de") && !language.equals("es")) { language = "en"; } Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://wiki.catrob.at/index" + ".php?title=" + category + "_Bricks/" + language + "#" + name)); getContext().startActivity(browserIntent); } private int calculateItemPositionAndTouchPointY(View view) { return brickDragAndDropListView.pointToPosition(view.getLeft(), view.getTop()); } @Override public boolean getShowDetails() { return showDetails; } @Override public void setShowDetails(boolean showDetails) { this.showDetails = showDetails; } @Override public int getSelectMode() { return selectMode; } @Override public void setSelectMode(int mode) { selectMode = mode; } @Override public int getAmountOfCheckedItems() { return getCheckedBricks().size(); } @Override public Set<Integer> getCheckedItems() { return null; } @Override public void clearCheckedItems() { actionMode = ActionModeEnum.NO_ACTION; checkedBricks.clear(); enableAllBricks(); notifyDataSetChanged(); } public void setCheckbox(Brick brick, boolean enabled) { CheckBox checkBox = brick.getCheckBox(); if (checkBox != null) { checkBox.setChecked(enabled); } } private void enableAllBricks() { unCheckAllItems(); for (Brick brick : brickList) { BrickViewProvider.setCheckboxVisibility(brick, View.GONE); BrickViewProvider.setAlphaForBrick(brick, BrickViewProvider.ALPHA_FULL); } } public void checkAllItems() { for (Brick brick : brickList) { setCheckbox(brick, true); handleCheck(brick, true); } } private void unCheckAllItems() { for (Brick brick : brickList) { setCheckbox(brick, false); handleCheck(brick, false); } } public void checkCommentedOutItems() { for (Brick brick : brickList) { if (brick.isCommentedOut()) { setCheckbox(brick, true); } } } public void setCheckboxVisibility() { for (Brick brick : brickList) { switch (actionMode) { case NO_ACTION: BrickViewProvider.setCheckboxVisibility(brick, View.GONE); break; case BACKPACK: if (brick instanceof ScriptBrick) { BrickViewProvider.setCheckboxVisibility(brick, View.VISIBLE); } else { BrickViewProvider.setCheckboxVisibility(brick, View.INVISIBLE); } break; case COPY_DELETE: case COMMENT_OUT: BrickViewProvider.setCheckboxVisibility(brick, View.VISIBLE); break; } } } private void commentBrickOut(Brick brick, boolean commentOut) { actionMode = ActionModeEnum.COMMENT_OUT; handleCheck(brick, commentOut); actionMode = ActionModeEnum.NO_ACTION; } public void setSpinnersEnabled(boolean enabled) { for (Brick brick : brickList) { BrickViewProvider.setSpinnerClickability(((BrickBaseType) brick).view, enabled); } } public void handleCheck(Brick brick, boolean checked) { int positionFrom = brickList.indexOf(brick); int positionTo = brickList.indexOf(brick); if (brick instanceof NestingBrick) { List<NestingBrick> nestingBricks = ((NestingBrick) brick).getAllNestingBrickParts(true); NestingBrick firstNestingBrick = nestingBricks.get(0); NestingBrick lastNestingBrick = nestingBricks.get(nestingBricks.size() - 1); if (actionMode != ActionModeEnum.NO_ACTION) { setCheckbox((Brick) firstNestingBrick, checked); } positionFrom = brickList.indexOf(firstNestingBrick); positionTo = brickList.indexOf(lastNestingBrick); } else if (brick instanceof ScriptBrick) { positionTo = brickList.size() - 1; } if (checked) { addElementToCheckedBricks(brick); } else { checkedBricks.remove(brick); } if (actionMode == ActionModeEnum.COMMENT_OUT) { brick.setCommentedOut(checked); BrickViewProvider.setSaturationOnBrick(brick, checked); } positionFrom++; for (int position = positionFrom; position <= positionTo; position++) { Brick currentBrick = brickList.get(position); if (currentBrick == null) { break; } if (currentBrick instanceof ScriptBrick) { break; } if (checked) { addElementToCheckedBricks(currentBrick); } else { checkedBricks.remove(currentBrick); } switch (actionMode) { case NO_ACTION: break; case COPY_DELETE: case BACKPACK: int alphaValue = checked ? BrickViewProvider.ALPHA_GREYED : BrickViewProvider.ALPHA_FULL; BrickViewProvider.setAlphaForBrick(currentBrick, alphaValue); setCheckbox(currentBrick, checked); BrickViewProvider.setCheckboxClickability(currentBrick, !checked); break; case COMMENT_OUT: currentBrick.setCommentedOut(checked); BrickViewProvider.setSaturationOnBrick(currentBrick, checked); setCheckbox(currentBrick, checked); BrickViewProvider.setCheckboxClickability(currentBrick, !checked); break; } } if (scriptFragment.getActionModeActive()) { scriptFragment.updateActionModeTitle(); } } void enableCorrespondingScriptBrick(int indexBegin) { for (int i = indexBegin; i >= 0; i--) { Brick currentBrick = brickList.get(i); if (currentBrick instanceof ScriptBrick) { currentBrick.setCommentedOut(false); BrickViewProvider.setSaturationOnBrick(currentBrick, false); break; } } } private void addElementToCheckedBricks(Brick brick) { if (!(checkedBricks.contains(brick)) && !(brick instanceof UserScriptDefinitionBrick)) { checkedBricks.add(brick); } } public void onDestroyActionModeBackPack() { actionMode = ActionModeEnum.NO_ACTION; List<String> backPackedScriptGroups = BackPackListManager.getInstance().getAllBackPackedScriptGroups(); showNewGroupBackPackDialog(backPackedScriptGroups, false); } public void handleScriptDelete(Sprite spriteToEdit, Script scriptToDelete) { spriteToEdit.removeScript(scriptToDelete); if (spriteToEdit.getNumberOfScripts() == 0) { ProjectManager.getInstance().setCurrentScript(null); updateProjectBrickList(); } else { int lastScriptIndex = spriteToEdit.getNumberOfScripts() - 1; Script lastScript = spriteToEdit.getScript(lastScriptIndex); ProjectManager.getInstance().setCurrentScript(lastScript); updateProjectBrickList(); } } public List<Brick> getCheckedBricks() { return checkedBricks; } public List<Brick> getReversedCheckedBrickList() { List<Brick> reverseCheckedList = new ArrayList<>(); for (int counter = checkedBricks.size() - 1; counter >= 0; counter--) { reverseCheckedList.add(checkedBricks.get(counter)); } return reverseCheckedList; } public UserBrick getUserBrick() { return userBrick; } public void setUserBrick(UserBrick userBrick) { this.userBrick = userBrick; } public void animateUnpackingFromBackpack(int numberOfInsertedBricks) { int insertedBricksStartPosition = brickList.size() - 1 - numberOfInsertedBricks; if (insertedBricksStartPosition < 0) { return; } int maxNumberAnimatedBricks = 4; for (int position = insertedBricksStartPosition; position < brickList.size() && ((position - insertedBricksStartPosition) < maxNumberAnimatedBricks); position++) { if (position < brickList.size()) { animatedBricks.add(brickList.get(position)); } } scrollToPosition(insertedBricksStartPosition); } }