/******************************************************************************* * Copyright 2014 Rafael Garcia Moreno. * * 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 com.bladecoder.engineeditor.ui; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.Field; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.scenes.scene2d.Actor; import com.badlogic.gdx.scenes.scene2d.ui.ImageButton; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener; import com.badlogic.gdx.utils.Array; import com.bladecoder.engine.actions.AbstractControlAction; import com.bladecoder.engine.actions.AbstractIfAction; import com.bladecoder.engine.actions.Action; import com.bladecoder.engine.actions.ActorAnimationRef; import com.bladecoder.engine.actions.CommentAction; import com.bladecoder.engine.actions.DisableActionAction; import com.bladecoder.engine.actions.EndAction; import com.bladecoder.engine.actions.SceneActorRef; import com.bladecoder.engine.model.Verb; import com.bladecoder.engine.util.ActionUtils; import com.bladecoder.engineeditor.Ctx; import com.bladecoder.engineeditor.common.EditorLogger; import com.bladecoder.engineeditor.common.ElementUtils; import com.bladecoder.engineeditor.model.Project; import com.bladecoder.engineeditor.ui.panels.CellRenderer; import com.bladecoder.engineeditor.ui.panels.EditModelDialog; import com.bladecoder.engineeditor.ui.panels.ModelList; import com.bladecoder.engineeditor.ui.panels.ScopePanel; import com.bladecoder.engineeditor.undo.UndoDeleteAction; public class ActionList extends ModelList<Verb, Action> { private static final String CONTROL_ACTION_ID_ATTR = "caID"; Skin skin; private ImageButton upBtn; private ImageButton downBtn; private ImageButton disableBtn; private String scope; private final List<Action> multiClipboard = new ArrayList<Action>(); public ActionList(Skin skin) { super(skin, false); this.skin = skin; setCellRenderer(listCellRenderer); disableBtn = new ImageButton(skin); toolbar.addToolBarButton(disableBtn, "ic_eye", "Enable/Disable", "Enable/Disable"); disableBtn.setDisabled(false); disableBtn.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { toggleEnabled(); } }); upBtn = new ImageButton(skin); downBtn = new ImageButton(skin); toolbar.addToolBarButton(upBtn, "ic_up", "Move up", "Move up"); toolbar.addToolBarButton(downBtn, "ic_down", "Move down", "Move down"); toolbar.pack(); list.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { int pos = list.getSelectedIndex(); toolbar.disableEdit(pos == -1); disableBtn.setDisabled(pos == -1); upBtn.setDisabled(pos == -1 || pos == 0); downBtn.setDisabled(pos == -1 || pos == list.getItems().size - 1); } }); list.getSelection().setMultiple(true); upBtn.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { up(); } }); downBtn.addListener(new ChangeListener() { @Override public void changed(ChangeEvent event, Actor actor) { down(); } }); Ctx.project.addPropertyChangeListener(Project.NOTIFY_ELEMENT_CREATED, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getNewValue() instanceof Action && !(evt.getSource() instanceof EditActionDialog)) { addElements(parent, parent.getActions()); } } }); } private void toggleEnabled() { if (list.getSelection().size() <= 0) return; Array<Action> sel = new Array<Action>(); for (Action a : list.getSelection().toArray()) { // CONTROL ACTIONS CAN'T BE DISABLED if (a == null || isControlAction(a)) continue; Array<Action> items = list.getItems(); int pos = items.indexOf(a, true); if (a instanceof DisableActionAction) { Action a2 = ((DisableActionAction) a).getAction(); parent.getActions().set(pos, a2); items.set(pos, a2); sel.add(a2); } else { DisableActionAction a2 = new DisableActionAction(); a2.setAction(a); parent.getActions().set(pos, a2); items.set(pos, a2); sel.add(a2); } } Ctx.project.setModified(); list.getSelection().clear(); list.getSelection().addAll(sel); } @Override protected EditModelDialog<Verb, Action> getEditElementDialogInstance(Action e) { EditActionDialog editActionDialog = new EditActionDialog(skin, parent, e, scope, list.getSelectedIndex()); return editActionDialog; } public void setScope(String scope) { this.scope = scope; } @Override protected void create() { EditModelDialog<Verb, Action> dialog = getEditElementDialogInstance(null); dialog.show(getStage()); dialog.setListener(new ChangeListener() { @SuppressWarnings("unchecked") @Override public void changed(ChangeEvent event, Actor actor) { int pos = list.getSelectedIndex() + 1; Action e = ((EditModelDialog<Verb, Action>) actor).getElement(); list.getItems().insert(pos, e); parent.getActions().add(pos, e); list.getSelection().choose(list.getItems().get(pos)); if (isControlAction(e)) { insertEndAction(pos + 1, getOrCreateControlActionId((AbstractControlAction) e)); if (e instanceof AbstractIfAction) insertEndAction(pos + 2, getOrCreateControlActionId((AbstractControlAction) e)); } list.invalidateHierarchy(); } }); } private Action editedElement; @Override protected void edit() { Action e = list.getSelected(); if (e == null || e instanceof EndAction || e instanceof DisableActionAction) return; editedElement = (Action) ElementUtils.cloneElement(e); EditModelDialog<Verb, Action> dialog = getEditElementDialogInstance(e); dialog.show(getStage()); dialog.setListener(new ChangeListener() { @SuppressWarnings("unchecked") @Override public void changed(ChangeEvent event, Actor actor) { Action e = ((EditModelDialog<Verb, Action>) actor).getElement(); int pos = list.getSelectedIndex(); list.getItems().set(pos, e); parent.getActions().set(pos, e); Ctx.project.setModified(); if (isControlAction(editedElement)) { if (!editedElement.getClass().getName().equals(e.getClass().getName())) { deleteControlAction(pos, (AbstractControlAction) editedElement); if (isControlAction(e)) { insertEndAction(pos + 1, getOrCreateControlActionId((AbstractControlAction) e)); if (e instanceof AbstractIfAction) insertEndAction(pos + 2, getOrCreateControlActionId((AbstractControlAction) e)); } } else { // insert previous caId try { ActionUtils.setParam(e, CONTROL_ACTION_ID_ATTR, getOrCreateControlActionId((AbstractControlAction) editedElement)); } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e1) { EditorLogger.error(e1.getMessage()); } } } } }); } private String getOrCreateControlActionId(AbstractControlAction a) { String id = a.getControlActionID(); if (id == null || id.isEmpty()) { id = Integer.toString(MathUtils.random(1, Integer.MAX_VALUE)); try { ActionUtils.setParam(a, CONTROL_ACTION_ID_ATTR, id); } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { EditorLogger.error(e.getMessage()); } } return id; } private void insertEndAction(int pos, String id) { final Action e = new EndAction(); try { ActionUtils.setParam(e, CONTROL_ACTION_ID_ATTR, id); } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e1) { EditorLogger.error(e1.getMessage()); } list.getItems().insert(pos, e); parent.getActions().add(pos, e); } @Override protected void copy() { if (parent == null || list.getSelection().size() == 0) return; multiClipboard.clear(); for (Action e : getSortedSelection()) { if (e == null || e instanceof EndAction) return; Action cloned = (Action) ElementUtils.cloneElement(e); multiClipboard.add(cloned); toolbar.disablePaste(false); // TRANSLATIONS if (scope.equals(ScopePanel.WORLD_SCOPE)) Ctx.project.getI18N().putTranslationsInElement(cloned, true); else Ctx.project.getI18N().putTranslationsInElement(cloned, false); } } @Override protected void paste() { if (parent == null || multiClipboard.size() == 0) return; Array<Action> sel = new Array<Action>(); for (int i = multiClipboard.size() - 1; i >= 0; i--) { Action newElement = (Action) ElementUtils.cloneElement(multiClipboard.get(i)); int pos = list.getSelectedIndex() + 1; list.getItems().insert(pos, newElement); parent.getActions().add(pos, newElement); if (scope.equals(ScopePanel.WORLD_SCOPE)) Ctx.project.getI18N().extractStrings(null, null, parent.getHashKey(), pos, newElement); else if (scope.equals(ScopePanel.SCENE_SCOPE)) Ctx.project.getI18N().extractStrings(Ctx.project.getSelectedScene().getId(), null, parent.getHashKey(), pos, newElement); else Ctx.project.getI18N().extractStrings(Ctx.project.getSelectedScene().getId(), Ctx.project.getSelectedActor().getId(), parent.getHashKey(), pos, newElement); list.invalidateHierarchy(); if (isControlAction(newElement)) { try { ActionUtils.setParam(newElement, CONTROL_ACTION_ID_ATTR, null); } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { EditorLogger.error(e.getMessage()); } insertEndAction(pos + 1, getOrCreateControlActionId((AbstractControlAction) newElement)); if (newElement instanceof AbstractIfAction) insertEndAction(pos + 2, getOrCreateControlActionId((AbstractControlAction) newElement)); } sel.add(newElement); } list.getSelection().clear(); list.getSelection().addAll(sel); Ctx.project.setModified(); } @Override protected void delete() { if (list.getSelection().size() == 0) return; multiClipboard.clear(); int pos = list.getSelectedIndex(); for (Action e : getSortedSelection()) { if (e instanceof EndAction) continue; int pos2 = list.getItems().indexOf(e, true); list.getItems().removeValue(e, true); int idx = parent.getActions().indexOf(e); parent.getActions().remove(e); multiClipboard.add(e); // TRANSLATIONS if (scope.equals(ScopePanel.WORLD_SCOPE)) Ctx.project.getI18N().putTranslationsInElement(e, true); else Ctx.project.getI18N().putTranslationsInElement(e, false); // UNDO Ctx.project.getUndoStack().add(new UndoDeleteAction(parent, e, idx)); if (isControlAction(e)) deleteControlAction(pos2, (AbstractControlAction) e); } if (list.getItems().size == 0) { list.getSelection().clear(); } else if (pos >= list.getItems().size) { list.getSelection().choose(list.getItems().get(list.getItems().size - 1)); } else { list.getSelection().choose(list.getItems().get(pos)); } toolbar.disablePaste(false); Ctx.project.setModified(); } private Array<Action> getSortedSelection() { Array<Action> array = list.getSelection().toArray(); array.sort(new Comparator<Action>() { @Override public int compare(Action arg0, Action arg1) { Integer i0 = list.getItems().indexOf(arg0, true); Integer i1 = list.getItems().indexOf(arg1, true); return i0.compareTo(i1); } }); return array; } private boolean isControlAction(Action e) { return e instanceof AbstractControlAction; } private void deleteControlAction(int pos, final AbstractControlAction e) { final String id = getOrCreateControlActionId(e); if (e instanceof AbstractIfAction) { pos = deleteFirstActionNamed(pos, id); } deleteFirstActionNamed(pos, id); } private int deleteFirstActionNamed(int pos, String actionId) { while (!(list.getItems().get(pos) instanceof AbstractControlAction && getOrCreateControlActionId((AbstractControlAction) list.getItems().get(pos)).equals(actionId))) pos++; Action e2 = list.getItems().removeIndex(pos); parent.getActions().remove(e2); return pos; } private void up() { if (parent == null || list.getSelection().size() == 0) return; Array<Action> sel = new Array<Action>(); for (Action a : getSortedSelection()) { int pos = list.getItems().indexOf(a, true); if (pos == -1 || pos == 0) return; Array<Action> items = list.getItems(); Action e = items.get(pos); Action e2 = items.get(pos - 1); sel.add(e); if (isControlAction(e) && isControlAction(e2)) { continue; } parent.getActions().set(pos - 1, e); parent.getActions().set(pos, e2); items.set(pos - 1, e); items.set(pos, e2); } list.getSelection().clear(); list.getSelection().addAll(sel); upBtn.setDisabled(list.getSelectedIndex() == 0); downBtn.setDisabled(list.getSelectedIndex() == list.getItems().size - 1); Ctx.project.setModified(); } private void down() { if (parent == null || list.getSelection().size() == 0) return; Array<Action> sel = new Array<Action>(); Array<Action> sortedSelection = getSortedSelection(); for (int i = sortedSelection.size - 1; i >= 0; i--) { int pos = list.getItems().indexOf(sortedSelection.get(i), true); Array<Action> items = list.getItems(); if (pos == -1 || pos == items.size - 1) return; Action e = items.get(pos); Action e2 = items.get(pos + 1); sel.add(e); if (isControlAction(e) && isControlAction(e2)) { continue; } parent.getActions().set(pos + 1, e); parent.getActions().set(pos, e2); items.set(pos + 1, e); items.set(pos, e2); } list.getSelection().clear(); list.getSelection().addAll(sel); upBtn.setDisabled(list.getSelectedIndex() == 0); downBtn.setDisabled(list.getSelectedIndex() == list.getItems().size - 1); Ctx.project.setModified(); } // ------------------------------------------------------------------------- // ListCellRenderer // ------------------------------------------------------------------------- private final CellRenderer<Action> listCellRenderer = new CellRenderer<Action>() { @Override protected String getCellTitle(Action a) { boolean enabled = true; if (a instanceof CommentAction) { String comment = null; try { comment = ActionUtils.getStringValue(a, "comment"); } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { EditorLogger.error(e.getMessage()); } if(comment == null) comment = "COMMENT"; else if(comment.indexOf('\n') != -1) { comment = comment.substring(0, comment.indexOf('\n')); } return "[YELLOW]" + comment + "[]"; } if (a instanceof DisableActionAction) { a = ((DisableActionAction) a).getAction(); enabled = false; } String id = ActionUtils.getName(a.getClass()); if (id == null) id = a.getClass().getCanonicalName(); Field field = ActionUtils.getField(a.getClass(), "actor"); String actor = null; if (field != null) { try { field.setAccessible(true); Object v = field.get(a); if (v != null) actor = v.toString(); } catch (IllegalArgumentException | IllegalAccessException e) { EditorLogger.error(e.getMessage()); } } boolean animationAction = id.equals("Animation"); boolean controlAction = isControlAction(a); if (!enabled && !controlAction) { if (actor != null && !animationAction) { SceneActorRef sa = new SceneActorRef(actor); if (sa.getSceneId() != null) id = MessageFormat.format("[GRAY]{0} {1}.{2}[]", sa.getSceneId(), sa.getActorId(), id); else id = MessageFormat.format("[GRAY]{0}.{1}[]", sa.getActorId(), id); } else if (animationAction) { field = ActionUtils.getField(a.getClass(), "animation"); String animation = null; if (field != null) { try { field.setAccessible(true); animation = field.get(a).toString(); } catch (IllegalArgumentException | IllegalAccessException e) { EditorLogger.error(e.getMessage()); } } ActorAnimationRef aa = new ActorAnimationRef(animation); if (aa.getActorId() != null) id = MessageFormat.format("[GRAY]{0}.{1} {2}[]", aa.getActorId(), id, aa.getAnimationId()); else id = MessageFormat.format("[GRAY]{0} {1}[]", id, aa.getAnimationId()); } else { id = MessageFormat.format("[GRAY]{0}[]", id); } } else if (actor != null && !animationAction && !controlAction) { SceneActorRef sa = new SceneActorRef(actor); if (sa.getSceneId() != null) id = MessageFormat.format("[GREEN]{0}[] {1}.{2}", sa.getSceneId(), sa.getActorId(), id); else id = MessageFormat.format("{0}.{1}", sa.getActorId(), id); } else if (animationAction) { field = ActionUtils.getField(a.getClass(), "animation"); String animation = null; if (field != null) { try { field.setAccessible(true); animation = field.get(a).toString(); } catch (IllegalArgumentException | IllegalAccessException e) { EditorLogger.error(e.getMessage()); } } ActorAnimationRef aa = new ActorAnimationRef(animation); if (aa.getActorId() != null) id = MessageFormat.format("[GREEN]{0}.{1} {2}[]", aa.getActorId(), id, aa.getAnimationId()); else id = MessageFormat.format("[GREEN]{0} {1}[]", id, aa.getAnimationId()); } else if (controlAction) { if (a instanceof EndAction) { Action parentAction = findParentAction((EndAction) a); if (parentAction instanceof AbstractIfAction && isElse((AbstractIfAction) parentAction, (EndAction) a)) { id = "Else"; } else { id = "End" + ActionUtils.getName(parentAction.getClass()); } } if (actor != null) { SceneActorRef sa = new SceneActorRef(actor); if (sa.getSceneId() != null) id = MessageFormat.format("[GREEN]{0}[] [BLUE]{1}.{2}[]", sa.getSceneId(), sa.getActorId(), id); else id = MessageFormat.format("[BLUE]{0}.{1}[BLUE]", sa.getActorId(), id); } else id = MessageFormat.format("[BLUE]{0}[]", id); } return id; } private boolean isElse(AbstractIfAction parentAction, EndAction ea) { final String caID = ea.getControlActionID(); ArrayList<Action> actions = parent.getActions(); int idx = actions.indexOf(parentAction); for (int i = idx + 1; i < actions.size(); i++) { Action aa = actions.get(i); if (isControlAction(aa) && ((AbstractControlAction) aa).getControlActionID().equals(caID)) { if (aa == ea) return true; return false; } } return false; } private Action findParentAction(EndAction a) { final String caID = a.getControlActionID(); ArrayList<Action> actions = parent.getActions(); for (Action a2 : actions) { if (isControlAction(a2) && ((AbstractControlAction) a2).getControlActionID().equals(caID)) { return a2; } } return null; } @Override protected String getCellSubTitle(Action a) { if (a instanceof CommentAction) return ""; if (a instanceof DisableActionAction) a = ((DisableActionAction) a).getAction(); StringBuilder sb = new StringBuilder(); String[] params = ActionUtils.getFieldNames(a); String actionName = ActionUtils.getName(a.getClass()); for (String p : params) { if (p.equals("actor") || (actionName != null && actionName.equals("Animation") && p.equals("animation"))) continue; if(p.equals("caID")) continue; Field f = ActionUtils.getField(a.getClass(), p); try { final boolean accessible = f.isAccessible(); f.setAccessible(true); Object o = f.get(a); if (o == null) continue; String v = o.toString(); // Check world Scope for translations if (scope.equals(ScopePanel.WORLD_SCOPE)) sb.append(p).append(": ") .append(Ctx.project.getI18N().getWorldTranslation(v).replace("\n", "|")).append(' '); else sb.append(p).append(": ").append(Ctx.project.translate(v).replace("\n", "|")).append(' '); f.setAccessible(accessible); } catch (IllegalArgumentException | IllegalAccessException e) { EditorLogger.error(e.getMessage()); } } return sb.toString(); } @Override protected boolean hasSubtitle() { return true; } }; }