diff options
author | Raphael Moll <ralf@android.com> | 2010-09-08 20:05:12 -0700 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2010-09-08 20:05:12 -0700 |
commit | b7412598b955d6140d394bbb298529e4cdf8d913 (patch) | |
tree | d8f01b53ce5298d643bc813a7ed7b62558938f7d | |
parent | 077468de25fadccf407a5264658d38019d32bb81 (diff) | |
parent | 13bbd4f3631ee307bad27b0a6332997386dc9ba3 (diff) | |
download | sdk-b7412598b955d6140d394bbb298529e4cdf8d913.zip sdk-b7412598b955d6140d394bbb298529e4cdf8d913.tar.gz sdk-b7412598b955d6140d394bbb298529e4cdf8d913.tar.bz2 |
Merge "GLE2: perform all context menu edits in the same undo session."
13 files changed, 346 insertions, 256 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/MenuAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/MenuAction.java index 63454a4..d3f34e5 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/MenuAction.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/editors/layout/gscripts/MenuAction.java @@ -157,9 +157,9 @@ public abstract class MenuAction { /** * A closure executed when the action is selected in the context menu. * - * @see #getAction() for details on the closure parameters. + * @see #getClosure() for details on the closure parameters. */ - private final Closure mAction; + private final Closure mClosure; /** * An optional group id, to place the action in a given sub-menu. * @null This value can be null. @@ -174,13 +174,13 @@ public abstract class MenuAction { * @param title The title of the action. Must not be null. * @param groupId The optional group id, to place the action in a given sub-menu. * Can be null. - * @param action The closure executed when the action is selected. Must not be null. - * See {@link #getAction()} for the closure parameters. + * @param closure The closure executed when the action is selected. Must not be null. + * See {@link #getClosure()} for the closure parameters. */ - private Action(String id, String title, String groupId, Closure action) { + private Action(String id, String title, String groupId, Closure closure) { super(id, title); mGroupId = groupId; - mAction = action; + mClosure = closure; } /** @@ -196,8 +196,8 @@ public abstract class MenuAction { * useful; however for flags it allows one to add or remove items to the flag's * choices. */ - public Closure getAction() { - return mAction; + public Closure getClosure() { + return mClosure; } /** @@ -250,10 +250,10 @@ public abstract class MenuAction { * @param id The unique id of the action. Cannot be null. * @param title The UI-visible title of the context menu item. Cannot be null. * @param isChecked Whether the context menu item has a check mark. - * @param action A closure to execute when the context menu item is selected. + * @param closure A closure to execute when the context menu item is selected. */ - public Toggle(String id, String title, boolean isChecked, Closure action) { - this(id, title, isChecked, null /*group-id*/, action); + public Toggle(String id, String title, boolean isChecked, Closure closure) { + this(id, title, isChecked, null /*group-id*/, closure); } /** @@ -264,10 +264,10 @@ public abstract class MenuAction { * @param isChecked Whether the context menu item has a check mark. * @param groupId The optional group id, to place the action in a given sub-menu. * Can be null. - * @param action A closure to execute when the context menu item is selected. + * @param closure A closure to execute when the context menu item is selected. */ - public Toggle(String id, String title, boolean isChecked, String groupId, Closure action) { - super(id, title, groupId, action); + public Toggle(String id, String title, boolean isChecked, String groupId, Closure closure) { + super(id, title, groupId, closure); mIsChecked = isChecked; } @@ -337,13 +337,13 @@ public abstract class MenuAction { * @param current The id(s) of the current choice(s) that will be check marked. * Can be null. Can be an id not present in the choices map. * There can be more than one id separated by {@link #CHOICE_SEP}. - * @param action A closure to execute when the context menu item is selected. + * @param closure A closure to execute when the context menu item is selected. */ public Choices(String id, String title, Map<String, String> choices, String current, - Closure action) { - this(id, title, choices, current, null /*group-id*/, action); + Closure closure) { + this(id, title, choices, current, null /*group-id*/, closure); } /** @@ -357,14 +357,14 @@ public abstract class MenuAction { * There can be more than one id separated by {@link #CHOICE_SEP}. * @param groupId The optional group id, to place the action in a given sub-menu. * Can be null. - * @param action A closure to execute when the context menu item is selected. + * @param closure A closure to execute when the context menu item is selected. */ public Choices(String id, String title, Map<String, String> choices, String current, String groupId, - Closure action) { - super(id, title, groupId, action); + Closure closure) { + super(id, title, groupId, closure); mChoices = choices; mCurrent = current; } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java index 68e1146..ed5e79d 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java @@ -106,8 +106,12 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh /** flag set during page creation */ private boolean mIsCreatingPage = false; - /** flag indicating we're inside {@link #editXmlModel(Runnable)}. */ - private boolean mIsEditXmlModelPending; + /** + * Flag indicating we're inside {@link #wrapEditXmlModel(Runnable)}. + * This is a counter, which allows us to nest the edit XML calls. + * There is no pending operation when the counter is at zero. + */ + private int mIsEditXmlModelPending; /** * Creates a form editor. @@ -634,7 +638,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * Callers <em>must</em> call model.releaseFromEdit() when done, typically * in a try..finally clause. Because of this, it is highly recommended * to <b>NOT</b> use this method directly and instead use the wrapper - * {@link #editXmlModel(Runnable)} which executes a runnable into a + * {@link #wrapEditXmlModel(Runnable)} which executes a runnable into a * properly configured model and then performs whatever cleanup is necessary. * * @return The model for the XML document or null if cannot be obtained from the editor @@ -662,30 +666,93 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * <p/> * The method is synchronous. As soon as the {@link IStructuredModel#changedModel()} method * is called, XML model listeners will be triggered. + * <p/> + * Calls can be nested: only the first outer call will actually start and close the edit + * session. + * <p/> + * This method is <em>not synchronized</em> and is not thread safe. + * Callers must be using it from the the main UI thread. * - * @param edit_action Something that will change the XML. + * @param editAction Something that will change the XML. */ - public final void editXmlModel(Runnable edit_action) { - IStructuredModel model = getModelForEdit(); + public final void wrapEditXmlModel(Runnable editAction) { + IStructuredModel model = null; try { - model.aboutToChangeModel(); - mIsEditXmlModelPending = true; - edit_action.run(); + if (mIsEditXmlModelPending == 0) { + try { + model = getModelForEdit(); + model.aboutToChangeModel(); + } catch (Throwable t) { + // This is never supposed to happen unless we suddenly don't have a model. + // If it does, we don't want to even try to modify anyway. + AdtPlugin.log(t, "XML Editor failed to get model to edit"); //$NON-NLS-1$ + return; + } + } + mIsEditXmlModelPending++; + editAction.run(); } finally { - // Notify the model we're done modifying it. This must *always* be executed. - mIsEditXmlModelPending = false; - model.changedModel(); - model.releaseFromEdit(); + mIsEditXmlModelPending--; + if (model != null) { + // Notify the model we're done modifying it. This must *always* be executed. + model.changedModel(); + model.releaseFromEdit(); + + if (mIsEditXmlModelPending < 0) { + AdtPlugin.log(IStatus.ERROR, + "wrapEditXmlModel finished with invalid nested counter==%d", //$NON-NLS-1$ + mIsEditXmlModelPending); + mIsEditXmlModelPending = 0; + } + } } } /** - * Returns true when the runnable of {@link #editXmlModel(Runnable)} is currently + * Creates an "undo recording" session by calling the undoableAction runnable + * using {@link #beginUndoRecording(String)} and {@link #endUndoRecording()}. + * <p/> + * This also automatically starts an edit XML session, as if + * {@link #wrapEditXmlModel(Runnable)} had been called. + * <p> + * You can nest several calls to {@link #wrapUndoEditXmlModel(String, Runnable)}, only one + * recording session will be created. + * + * @param label The label for the undo operation. Can be null. Ideally we should really try + * to put something meaningful if possible. + */ + public void wrapUndoEditXmlModel(String label, Runnable undoableAction) { + boolean recording = false; + try { + recording = beginUndoRecording(label); + + if (!recording) { + // This can only happen if we don't have an underlying model to edit + // or it's not a structured document, which in this context is + // highly unlikely. Abort the operation in this case. + AdtPlugin.logAndPrintError( + null, //exception, + getProject() != null ? getProject().getName() : "XML Editor", //$NON-NLS-1$ //tag + "Action '%s' failed: could not start an undo session, document might be corrupt.", //$NON-NLS-1$ + label); + return; + } + + wrapEditXmlModel(undoableAction); + } finally { + if (recording) { + endUndoRecording(); + } + } + } + + /** + * Returns true when the runnable of {@link #wrapEditXmlModel(Runnable)} is currently * being executed. This means it is safe to actually edit the XML model returned * by {@link #getModelForEdit()}. */ public boolean isEditXmlModelPending() { - return mIsEditXmlModelPending; + return mIsEditXmlModelPending > 0; } /** @@ -696,6 +763,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * <p/> * beginUndoRecording/endUndoRecording calls can be nested (inner calls are ignored, only one * undo operation is recorded.) + * To guarantee that, only access this via {@link #wrapUndoEditXmlModel(String, Runnable)}. * * @param label The label for the undo operation. Can be null but we should really try to put * something meaningful if possible. @@ -722,6 +790,7 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh * <p/> * This is the counterpart call to {@link #beginUndoRecording(String)} and should only be * used if the initial call returned true. + * To guarantee that, only access this via {@link #wrapUndoEditXmlModel(String, Runnable)}. */ private final void endUndoRecording() { IStructuredDocument document = getStructuredDocument(); @@ -737,28 +806,6 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh } /** - * Creates an "undo recording" session by calling the undoableAction runnable - * using {@link #beginUndoRecording(String)} and {@link #endUndoRecording()}. - * <p> - * You can nest several calls to {@link #wrapUndoRecording(String, Runnable)}, only one - * recording session will be created. - * - * @param label The label for the undo operation. Can be null. Ideally we should really try - * to put something meaningful if possible. - */ - public void wrapUndoRecording(String label, Runnable undoableAction) { - boolean recording = false; - try { - recording = beginUndoRecording(label); - undoableAction.run(); - } finally { - if (recording) { - endUndoRecording(); - } - } - } - - /** * Returns the XML {@link Document} or null if we can't get it */ protected final Document getXmlDocument(IStructuredModel model) { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java index 96db650..f9657af 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java @@ -16,8 +16,10 @@ package com.android.ide.eclipse.adt.internal.editors.layout.gle2; +import com.android.ide.eclipse.adt.AdtPlugin; import com.android.ide.eclipse.adt.editors.layout.gscripts.IViewRule; import com.android.ide.eclipse.adt.editors.layout.gscripts.MenuAction; +import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; import com.android.ide.eclipse.adt.internal.editors.layout.gre.NodeProxy; import com.android.ide.eclipse.adt.internal.editors.layout.gre.RulesEngine; @@ -38,9 +40,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; -import java.util.Map.Entry; import java.util.regex.Pattern; @@ -57,11 +59,16 @@ import java.util.regex.Pattern; */ /* package */ class DynamicContextMenu { + /** The XML layout editor that contains the canvas that uses this menu. */ + private final LayoutEditor mEditor; + + /** The layout canvas that displays this context menu. */ private final LayoutCanvas mCanvas; /** The root menu manager of the context menu. */ private final MenuManager mMenuManager; + /** * Creates a new helper responsible for adding and managing the dynamic menu items * contributed by the {@link IViewRule} groovy instances, based on the current selection @@ -72,7 +79,8 @@ import java.util.regex.Pattern; * @param rootMenu The root of the context menu displayed. In practice this may be the * context menu manager of the {@link LayoutCanvas} or the one from {@link OutlinePage2}. */ - public DynamicContextMenu(LayoutCanvas canvas, MenuManager rootMenu) { + public DynamicContextMenu(LayoutEditor editor, LayoutCanvas canvas, MenuManager rootMenu) { + mEditor = editor; mCanvas = canvas; mMenuManager = rootMenu; @@ -159,23 +167,25 @@ import java.util.regex.Pattern; continue; } - final MenuAction.Action action = (MenuAction.Action) actions.get(0); + // Arbitrarily select the first action, as all the actions with the same id + // should have the same constant attributes such as id and title. + final MenuAction.Action firstAction = (MenuAction.Action) actions.get(0); IContributionItem contrib = null; - if (action instanceof MenuAction.Toggle) { - contrib = createDynamicMenuToggle((MenuAction.Toggle) action, actionsMap); + if (firstAction instanceof MenuAction.Toggle) { + contrib = createDynamicMenuToggle((MenuAction.Toggle) firstAction, actionsMap); - } else if (action instanceof MenuAction.Choices) { - Map<String, String> choiceMap = ((MenuAction.Choices) action).getChoices(); + } else if (firstAction instanceof MenuAction.Choices) { + Map<String, String> choiceMap = ((MenuAction.Choices) firstAction).getChoices(); if (choiceMap != null && !choiceMap.isEmpty()) { contrib = createDynamicChoices( - (MenuAction.Choices)action, choiceMap, actionsMap); + (MenuAction.Choices)firstAction, choiceMap, actionsMap); } } if (contrib != null) { - MenuManager groupMenu = menuGroups.get(action.getGroupId()); + MenuManager groupMenu = menuGroups.get(firstAction.getGroupId()); if (groupMenu != null) { groupMenu.add(contrib); } else { @@ -288,36 +298,59 @@ import java.util.regex.Pattern; * <p/> * Toggles are represented by a checked menu item. * - * @param action The toggle action to convert to a menu item. + * @param firstAction The toggle action to convert to a menu item. In the case of a + * multiple selection, this is the first of many similar actions. * @param actionsMap Map of all contributed actions. * @return a new {@link IContributionItem} to add to the context menu */ private IContributionItem createDynamicMenuToggle( - final MenuAction.Toggle action, + final MenuAction.Toggle firstAction, final TreeMap<String, ArrayList<MenuAction>> actionsMap) { final RulesEngine gre = mCanvas.getRulesEngine(); - final boolean isChecked = action.isChecked(); - Action a = new Action(action.getTitle(), IAction.AS_CHECK_BOX) { + final boolean isChecked = firstAction.isChecked(); + + Action a = new Action(firstAction.getTitle(), IAction.AS_CHECK_BOX) { @Override public void run() { - // Invoke the closures of all the actions using the same action-id - for (MenuAction a2 : actionsMap.get(action.getId())) { - if (a2 instanceof MenuAction.Action) { - Closure c = ((MenuAction.Action) a2).getAction(); - if (c != null) { - gre.callClosure( - ((MenuAction.Action) a2).getAction(), - // Closure parameters are action, valueId, newValue - action, - null, // no valueId for a toggle - !isChecked); + final List<MenuAction> actions = actionsMap.get(firstAction.getId()); + if (actions == null || actions.isEmpty()) { + return; + } + + String label = String.format("Toggle attribute %s", actions.get(0).getTitle()); + if (actions.size() > 1) { + label += String.format(" (%d elements)", actions.size()); + } + + if (mEditor.isEditXmlModelPending()) { + // This should not be happening. + logError("Action '%s' failed: XML changes pending, document might be corrupt.", //$NON-NLS-1$ + label); + return; + } + + mEditor.wrapUndoEditXmlModel(label, new Runnable() { + public void run() { + // Invoke the closures of all the actions using the same action-id + for (MenuAction a2 : actions) { + if (a2 instanceof MenuAction.Action) { + Closure c = ((MenuAction.Action) a2).getClosure(); + if (c != null) { + gre.callClosure( + ((MenuAction.Action) a2).getClosure(), + // Closure parameters are action, valueId, newValue + a2, + null, // no valueId for a toggle + !isChecked); + } + } } } - } + }); } }; - a.setId(action.getId()); + a.setId(firstAction.getId()); a.setChecked(isChecked); return new ActionContributionItem(a); @@ -329,24 +362,25 @@ import java.util.regex.Pattern; * <p/> * Multiple-choices are represented by a sub-menu containing checked items. * - * @param action The choices action to convert to a menu item. + * @param firstAction The choices action to convert to a menu item. In the case of a + * multiple selection, this is the first of many similar actions. * @param actionsMap Map of all contributed actions. * @return a new {@link IContributionItem} to add to the context menu */ private IContributionItem createDynamicChoices( - final MenuAction.Choices action, + final MenuAction.Choices firstAction, Map<String, String> choiceMap, final TreeMap<String, ArrayList<MenuAction>> actionsMap) { final RulesEngine gre = mCanvas.getRulesEngine(); - MenuManager submenu = new MenuManager(action.getTitle(), action.getId()); + MenuManager submenu = new MenuManager(firstAction.getTitle(), firstAction.getId()); // Convert to a tree map as needed so that keys be naturally ordered. if (!(choiceMap instanceof TreeMap<?, ?>)) { choiceMap = new TreeMap<String, String>(choiceMap); } - String current = action.getCurrent(); + String current = firstAction.getCurrent(); Set<String> currents = null; if (current.indexOf(MenuAction.Choices.CHOICE_SEP) >= 0) { currents = new HashSet<String>( @@ -375,24 +409,53 @@ import java.util.regex.Pattern; Action a = new Action(title, IAction.AS_CHECK_BOX) { @Override public void run() { - // Invoke the closures of all the actions using the same action-id - for (MenuAction a2 : actionsMap.get(action.getId())) { - if (a2 instanceof MenuAction.Action) { - gre.callClosure( - ((MenuAction.Action) a2).getAction(), - // Closure parameters are action, valueId, newValue - action, - key, - !isChecked); - } + final List<MenuAction> actions = actionsMap.get(firstAction.getId()); + if (actions == null || actions.isEmpty()) { + return; + } + + String label = String.format("Change attribute %s", actions.get(0).getTitle()); + if (actions.size() > 1) { + label += String.format(" (%d elements)", actions.size()); + } + + if (mEditor.isEditXmlModelPending()) { + // This should not be happening. + logError("Action '%s' failed: XML changes pending, document might be corrupt.", //$NON-NLS-1$ + label); + return; } + + mEditor.wrapUndoEditXmlModel(label, new Runnable() { + public void run() { + // Invoke the closures of all the actions using the same action-id + for (MenuAction a2 : actions) { + if (a2 instanceof MenuAction.Action) { + gre.callClosure( + ((MenuAction.Action) a2).getClosure(), + // Closure parameters are action, valueId, newValue + a2, + key, + !isChecked); + } + } + } + }); } }; - a.setId(String.format("%s_%s", action.getId(), key)); //$NON-NLS-1$ + a.setId(String.format("%s_%s", firstAction.getId(), key)); //$NON-NLS-1$ a.setChecked(isChecked); submenu.add(a); } return submenu; } + + private void logError(String format, Object...args) { + AdtPlugin.logAndPrintError( + null, // exception + mCanvas.getRulesEngine().getProject().getName(), // tag + format, args); + } + } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java index 9cb6385..dbdd7aa 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/LayoutCanvas.java @@ -1504,17 +1504,12 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { // Remove from source. Since we know the selection, we'll simply // create a cut operation on the existing drag selection. - // Create an undo wrapper, which takes a runnable - mLayoutEditor.wrapUndoRecording( + // Create an undo edit XML wrapper, which takes a runnable + mLayoutEditor.wrapUndoEditXmlModel( "Remove drag'n'drop source elements", new Runnable() { public void run() { - // Create an edit-XML wrapper, which takes a runnable - mLayoutEditor.editXmlModel(new Runnable() { - public void run() { - deleteSelection("Remove", mDragSelection); - } - }); + deleteSelection("Remove", mDragSelection); } }); } @@ -1820,7 +1815,7 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { // Fill the menu manager with the static & dynamic actions setupStaticMenuActions(mMenuManager); - new DynamicContextMenu(this, mMenuManager); + new DynamicContextMenu(mLayoutEditor, this, mMenuManager); Menu menu = mMenuManager.createContextMenu(this); setMenu(menu); } @@ -1979,21 +1974,17 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { // the elements. An update XML model event should happen when the model gets released // which will trigger a recompute of the layout, thus reloading the model thus // resetting the selection. - mLayoutEditor.wrapUndoRecording(title, new Runnable() { + mLayoutEditor.wrapUndoEditXmlModel(title, new Runnable() { public void run() { - mLayoutEditor.editXmlModel(new Runnable() { - public void run() { - for (CanvasSelection cs : selection) { - CanvasViewInfo vi = cs.getViewInfo(); - if (vi != null) { - UiViewElementNode ui = vi.getUiViewKey(); - if (ui != null) { - ui.deleteXmlNode(); - } - } + for (CanvasSelection cs : selection) { + CanvasViewInfo vi = cs.getViewInfo(); + if (vi != null) { + UiViewElementNode ui = vi.getUiViewKey(); + if (ui != null) { + ui.deleteXmlNode(); } } - }); + } } }); } @@ -2067,76 +2058,72 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { } title = String.format("Paste root %1$s in document", title); - mLayoutEditor.wrapUndoRecording(title, new Runnable() { + mLayoutEditor.wrapUndoEditXmlModel(title, new Runnable() { public void run() { - mLayoutEditor.editXmlModel(new Runnable() { - public void run() { - UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc); - - // A root node requires the Android XMLNS - uiNew.setAttributeValue( - "android", - XmlnsAttributeDescriptor.XMLNS_URI, - SdkConstants.NS_RESOURCES, - true /*override*/); - - // Copy all the attributes from the pasted element - for (IDragAttribute attr : pastedElement.getAttributes()) { - uiNew.setAttributeValue( - attr.getName(), - attr.getUri(), - attr.getValue(), - true /*override*/); - } + UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc); + + // A root node requires the Android XMLNS + uiNew.setAttributeValue( + "android", + XmlnsAttributeDescriptor.XMLNS_URI, + SdkConstants.NS_RESOURCES, + true /*override*/); + + // Copy all the attributes from the pasted element + for (IDragAttribute attr : pastedElement.getAttributes()) { + uiNew.setAttributeValue( + attr.getName(), + attr.getUri(), + attr.getValue(), + true /*override*/); + } - // Adjust the attributes, adding the default layout_width/height - // only if they are not present (the original element should have - // them though.) - DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); + // Adjust the attributes, adding the default layout_width/height + // only if they are not present (the original element should have + // them though.) + DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); - uiNew.createXmlNode(); + uiNew.createXmlNode(); - // Now process all children - for (IDragElement childElement : pastedElement.getInnerElements()) { - addChild(uiNew, childElement); - } - } + // Now process all children + for (IDragElement childElement : pastedElement.getInnerElements()) { + addChild(uiNew, childElement); + } + } - private void addChild(UiElementNode uiParent, IDragElement childElement) { - String childFqcn = childElement.getFqcn(); - final ViewElementDescriptor childDesc = - mLayoutEditor.getFqcnViewDescritor(childFqcn); - if (childDesc == null) { - // TODO this could happen if pasting a custom view - debugPrintf("Failed to paste element, unknown FQCN %1$s", childFqcn); - return; - } + private void addChild(UiElementNode uiParent, IDragElement childElement) { + String childFqcn = childElement.getFqcn(); + final ViewElementDescriptor childDesc = + mLayoutEditor.getFqcnViewDescritor(childFqcn); + if (childDesc == null) { + // TODO this could happen if pasting a custom view + debugPrintf("Failed to paste element, unknown FQCN %1$s", childFqcn); + return; + } - UiElementNode uiChild = uiParent.appendNewUiChild(childDesc); + UiElementNode uiChild = uiParent.appendNewUiChild(childDesc); - // Copy all the attributes from the pasted element - for (IDragAttribute attr : childElement.getAttributes()) { - uiChild.setAttributeValue( - attr.getName(), - attr.getUri(), - attr.getValue(), - true /*override*/); - } + // Copy all the attributes from the pasted element + for (IDragAttribute attr : childElement.getAttributes()) { + uiChild.setAttributeValue( + attr.getName(), + attr.getUri(), + attr.getValue(), + true /*override*/); + } - // Adjust the attributes, adding the default layout_width/height - // only if they are not present (the original element should have - // them though.) - DescriptorsUtils.setDefaultLayoutAttributes( - uiChild, false /*updateLayout*/); + // Adjust the attributes, adding the default layout_width/height + // only if they are not present (the original element should have + // them though.) + DescriptorsUtils.setDefaultLayoutAttributes( + uiChild, false /*updateLayout*/); - uiChild.createXmlNode(); + uiChild.createXmlNode(); - // Now process all grand children - for (IDragElement grandChildElement : childElement.getInnerElements()) { - addChild(uiChild, grandChildElement); - } - } - }); + // Now process all grand children + for (IDragElement grandChildElement : childElement.getInnerElements()) { + addChild(uiChild, grandChildElement); + } } }); } @@ -2178,25 +2165,21 @@ class LayoutCanvas extends Canvas implements ISelectionProvider { } title = String.format("Create root %1$s in document", title); - mLayoutEditor.wrapUndoRecording(title, new Runnable() { + mLayoutEditor.wrapUndoEditXmlModel(title, new Runnable() { public void run() { - mLayoutEditor.editXmlModel(new Runnable() { - public void run() { - UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc); + UiElementNode uiNew = uiDoc.appendNewUiChild(viewDesc); - // A root node requires the Android XMLNS - uiNew.setAttributeValue( - "android", - XmlnsAttributeDescriptor.XMLNS_URI, - SdkConstants.NS_RESOURCES, - true /*override*/); + // A root node requires the Android XMLNS + uiNew.setAttributeValue( + "android", + XmlnsAttributeDescriptor.XMLNS_URI, + SdkConstants.NS_RESOURCES, + true /*override*/); - // Adjust the attributes - DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); + // Adjust the attributes + DescriptorsUtils.setDefaultLayoutAttributes(uiNew, false /*updateLayout*/); - uiNew.createXmlNode(); - } - }); + uiNew.createXmlNode(); } }); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java index 23fe245..5a7f631 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/OutlinePage2.java @@ -431,7 +431,10 @@ public class OutlinePage2 extends ContentOutlinePage } }); - new DynamicContextMenu(mGraphicalEditorPart.getCanvasControl(), mMenuManager); + new DynamicContextMenu( + mGraphicalEditorPart.getLayoutEditor(), + mGraphicalEditorPart.getCanvasControl(), + mMenuManager); getControl().setMenu(mMenuManager.createContextMenu(getControl())); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java index 562c89e..40f77a4 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/NodeProxy.java @@ -165,26 +165,17 @@ public class NodeProxy implements INode { public void editXml(String undoName, final Closure c) { final AndroidXmlEditor editor = mNode.getEditor(); - if (editor.isEditXmlModelPending()) { - throw new RuntimeException("Error: calls to INode.editXml cannot be nested!"); - } - if (editor instanceof LayoutEditor) { - // Create an undo wrapper, which takes a runnable - ((LayoutEditor) editor).wrapUndoRecording( + // Create an undo edit XML wrapper, which takes a runnable + ((LayoutEditor) editor).wrapUndoEditXmlModel( undoName, new Runnable() { public void run() { - // Create an edit-XML wrapper, which takes a runnable - editor.editXmlModel(new Runnable() { - public void run() { - // Here editor.isEditXmlModelPending returns true and it - // is safe to edit the model using any method from INode. - - // Finally execute the closure that will act on the XML - c.call(NodeProxy.this); - } - }); + // Here editor.isEditXmlModelPending returns true and it + // is safe to edit the model using any method from INode. + + // Finally execute the closure that will act on the XML + c.call(NodeProxy.this); } }); } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java index cbfe763..5327d7c 100755 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gre/RulesEngine.java @@ -140,6 +140,13 @@ public class RulesEngine { } /** + * Returns the {@link IProject} on which the {@link RulesEngine} was created. + */ + public IProject getProject() { + return mProject; + } + + /** * Called by the owner of the {@link RulesEngine} when it is going to be disposed. * This frees some resources, such as the project's folder monitor. */ diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/DropFeedback.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/DropFeedback.java index 390dc76..ec928dd 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/DropFeedback.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/DropFeedback.java @@ -219,7 +219,7 @@ class DropFeedback { if (where == null) { return; } - uiNode.getEditor().editXmlModel(new Runnable() { + uiNode.getEditor().wrapEditXmlModel(new Runnable() { public void run() { uiNode.setAttributeValue( LayoutConstants.ATTR_LAYOUT_X, @@ -270,7 +270,7 @@ class DropFeedback { final UiElementEditPart anchorPart = info.targetParts[info.anchorIndex]; // can be null final int direction = info.direction; - uiNode.getEditor().editXmlModel(new Runnable() { + uiNode.getEditor().wrapEditXmlModel(new Runnable() { public void run() { HashMap<String, String> map = new HashMap<String, String>(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/ElementCreateCommand.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/ElementCreateCommand.java index 59f2169..128dfd7 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/ElementCreateCommand.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/parts/ElementCreateCommand.java @@ -72,7 +72,7 @@ public class ElementCreateCommand extends Command { if (uiParent != null) { final AndroidXmlEditor editor = uiParent.getEditor(); if (editor instanceof LayoutEditor) { - ((LayoutEditor) editor).wrapUndoRecording( + ((LayoutEditor) editor).wrapUndoEditXmlModel( String.format("Create %1$s", mDescriptor.getXmlLocalName()), new Runnable() { public void run() { diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java index 76a0442..8b20cd1 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/manifest/pages/ApplicationToggle.java @@ -43,8 +43,8 @@ import org.w3c.dom.Text; * Appllication Toogle section part for application page. */ final class ApplicationToggle extends UiElementPart { - - /** Checkbox indicating whether an application node is present */ + + /** Checkbox indicating whether an application node is present */ private Button mCheckbox; /** Listen to changes to the UI node for <application> and updates the checkbox */ private AppNodeUpdateListener mAppNodeUpdateListener; @@ -60,7 +60,7 @@ final class ApplicationToggle extends UiElementPart { null, /* description */ Section.TWISTIE | Section.EXPANDED); } - + @Override public void dispose() { super.dispose(); @@ -69,7 +69,7 @@ final class ApplicationToggle extends UiElementPart { mAppNodeUpdateListener = null; } } - + /** * Changes and refreshes the Application UI node handle by the this part. */ @@ -89,7 +89,7 @@ final class ApplicationToggle extends UiElementPart { * <p/> * This MUST not be called by the constructor. Instead it must be called from * <code>initialize</code> (i.e. right after the form part is added to the managed form.) - * + * * @param managedForm The owner managed form */ @Override @@ -114,14 +114,14 @@ final class ApplicationToggle extends UiElementPart { // Initialize the state of the checkbox mAppNodeUpdateListener.uiElementNodeUpdated(getUiElementNode(), UiUpdateState.CHILDREN_CHANGED); - + // Tell the section that the layout has changed. layoutChanged(); } /** * Updates the application tooltip in the form text. - * If there is no tooltip, the form text is hidden. + * If there is no tooltip, the form text is hidden. */ private void updateTooltip() { boolean isVisible = false; @@ -131,13 +131,13 @@ final class ApplicationToggle extends UiElementPart { tooltip = DescriptorsUtils.formatFormText(tooltip, getUiElementNode().getDescriptor(), Sdk.getCurrent().getDocumentationBaseUrl()); - + mTooltipFormText.setText(tooltip, true /* parseTags */, true /* expandURLs */); mTooltipFormText.setImage(DescriptorsUtils.IMAGE_KEY, AdtPlugin.getAndroidLogo()); mTooltipFormText.addHyperlinkListener(getEditor().createHyperlinkListener()); isVisible = true; } - + mTooltipFormText.setVisible(isVisible); } @@ -156,31 +156,27 @@ final class ApplicationToggle extends UiElementPart { public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); if (!mInternalModification && getUiElementNode() != null) { - getUiElementNode().getEditor().wrapUndoRecording( + getUiElementNode().getEditor().wrapUndoEditXmlModel( mCheckbox.getSelection() ? "Create or restore Application node" : "Remove Application node", new Runnable() { public void run() { - getUiElementNode().getEditor().editXmlModel(new Runnable() { - public void run() { - if (mCheckbox.getSelection()) { - // The user wants an <application> node. - // Either restore a previous one - // or create a full new one. - boolean create = true; - if (mUndoXmlNode != null) { - create = !restoreApplicationNode(); - } - if (create) { - getUiElementNode().createXmlNode(); - } - } else { - // Users no longer wants the <application> node. - removeApplicationNode(); - } + if (mCheckbox.getSelection()) { + // The user wants an <application> node. + // Either restore a previous one + // or create a full new one. + boolean create = true; + if (mUndoXmlNode != null) { + create = !restoreApplicationNode(); + } + if (create) { + getUiElementNode().createXmlNode(); } - }); + } else { + // Users no longer wants the <application> node. + removeApplicationNode(); + } } }); } @@ -188,7 +184,7 @@ final class ApplicationToggle extends UiElementPart { /** * Restore a previously "saved" application node. - * + * * @return True if the node could be restored, false otherwise. */ private boolean restoreApplicationNode() { @@ -226,7 +222,7 @@ final class ApplicationToggle extends UiElementPart { mUndoXmlParent.insertBefore(sep, null); // insert separator before end tag } success = true; - } + } // Remove internal references to avoid using them twice mUndoXmlParent = null; @@ -239,8 +235,8 @@ final class ApplicationToggle extends UiElementPart { /** * Validates that the given xml_node is still either the root node or one of its - * direct descendants. - * + * direct descendants. + * * @param root_node The root of the node hierarchy to examine. * @param xml_node The XML node to find. * @return Returns xml_node if it is, otherwise returns null. @@ -291,7 +287,7 @@ final class ApplicationToggle extends UiElementPart { * This listener synchronizes the UI (i.e. the checkbox) with the * actual presence of the application XML node. */ - private class AppNodeUpdateListener implements IUiUpdateListener { + private class AppNodeUpdateListener implements IUiUpdateListener { public void uiElementNodeUpdated(UiElementNode ui_node, UiUpdateState state) { // The UiElementNode for the application XML node always exists, even // if there is no corresponding XML node in the XML file. @@ -307,7 +303,7 @@ final class ApplicationToggle extends UiElementPart { } finally { mInternalModification = false; } - + } } } diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/UiElementPart.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/UiElementPart.java index 1ad9525..5e7ca30 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/UiElementPart.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/UiElementPart.java @@ -267,7 +267,7 @@ public class UiElementPart extends ManifestSectionPart { @Override public void commit(boolean onSave) { if (mUiElementNode != null) { - mEditor.editXmlModel(new Runnable() { + mEditor.wrapEditXmlModel(new Runnable() { public void run() { for (UiAttributeNode ui_attr : mUiElementNode.getUiAttributes()) { ui_attr.commit(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java index da6db1a..60ec130 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/ui/tree/UiActions.java @@ -155,7 +155,7 @@ public abstract class UiActions implements ICommitXml { : "Remove element from Android XML", String.format("Do you really want to remove %1$s?", sb.toString()))) { commitPendingXmlChanges(); - getRootNode().getEditor().editXmlModel(new Runnable() { + getRootNode().getEditor().wrapEditXmlModel(new Runnable() { public void run() { UiElementNode previous = null; UiElementNode parent = null; @@ -205,7 +205,7 @@ public abstract class UiActions implements ICommitXml { } commitPendingXmlChanges(); - getRootNode().getEditor().editXmlModel(new Runnable() { + getRootNode().getEditor().wrapEditXmlModel(new Runnable() { public void run() { Node xml_node = node.getXmlNode(); if (xml_node != null) { @@ -285,7 +285,7 @@ public abstract class UiActions implements ICommitXml { } commitPendingXmlChanges(); - getRootNode().getEditor().editXmlModel(new Runnable() { + getRootNode().getEditor().wrapEditXmlModel(new Runnable() { public void run() { Node xml_node = node.getXmlNode(); if (xml_node != null) { @@ -374,7 +374,7 @@ public abstract class UiActions implements ICommitXml { final UiElementNode uiNew = uiParent.insertNewUiChild(index, descriptor); UiElementNode rootNode = getRootNode(); - rootNode.getEditor().editXmlModel(new Runnable() { + rootNode.getEditor().wrapEditXmlModel(new Runnable() { public void run() { DescriptorsUtils.setDefaultLayoutAttributes(uiNew, updateLayout); Node xmlNode = uiNew.createXmlNode(); diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java index 9d0927a..dcb451c 100644 --- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java @@ -1256,7 +1256,7 @@ public class UiElementNode implements IPropertySource { * Note that the caller MUST ensure that modifying the underlying XML model is * safe and must take care of marking the model as dirty if necessary. * - * @see AndroidXmlEditor#editXmlModel(Runnable) + * @see AndroidXmlEditor#wrapEditXmlModel(Runnable) * * @param uiAttr The attribute node to commit. Must be a child of this UiElementNode. * @param newValue The new value to set. @@ -1302,7 +1302,7 @@ public class UiElementNode implements IPropertySource { * Note that the caller MUST ensure that modifying the underlying XML model is * safe and must take care of marking the model as dirty if necessary. * - * @see AndroidXmlEditor#editXmlModel(Runnable) + * @see AndroidXmlEditor#wrapEditXmlModel(Runnable) * * @return True if one or more values were actually modified or removed, * false if nothing changed. @@ -1662,7 +1662,7 @@ public class UiElementNode implements IPropertySource { final UiAttributeNode fAttribute = attribute; AndroidXmlEditor editor = getEditor(); - editor.editXmlModel(new Runnable() { + editor.wrapEditXmlModel(new Runnable() { public void run() { commitAttributeToXml(fAttribute, newValue); } |