Commit 9615b7a7 authored by Matthew Coufal's avatar Matthew Coufal Committed by Susan Rati Lane

Enable multiple component selection using shift key presses (#1891)

* Enable multiple component selection using shift key presses
* Add highlight to select components and show number of components selected
* Make selected component properties default when there is no full intersection
* Allow unselection of already selected components
parent bfdb5ac2
...@@ -10,6 +10,7 @@ import static com.google.appinventor.client.Ode.MESSAGES; ...@@ -10,6 +10,7 @@ import static com.google.appinventor.client.Ode.MESSAGES;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import com.google.appinventor.client.editor.simple.SimpleEditor; import com.google.appinventor.client.editor.simple.SimpleEditor;
...@@ -915,14 +916,25 @@ public final class MockForm extends MockContainer { ...@@ -915,14 +916,25 @@ public final class MockForm extends MockContainer {
if (newSelectedComponent == null) { if (newSelectedComponent == null) {
throw new IllegalArgumentException("at least one component must always be selected"); throw new IllegalArgumentException("at least one component must always be selected");
} }
if (newSelectedComponent == oldSelectedComponent) { YaFormEditor formEditor = (YaFormEditor) editor;
boolean shouldSelectMultipleComponents = formEditor.getShouldSelectMultipleComponents();
List<MockComponent> selectedComponents = formEditor.getSelectedComponents();
if (shouldSelectMultipleComponents && selectedComponents.size() > 1 && formEditor.isSelectedComponent(newSelectedComponent)) {
int index = selectedComponents.indexOf(newSelectedComponent);
selectedComponent = selectedComponents.get((index == 0) ? 1 : index - 1);
newSelectedComponent.onSelectedChange(false);
return; return;
} }
selectedComponent = newSelectedComponent; selectedComponent = newSelectedComponent;
Map<String, MockComponent> componentsMap = formEditor.getComponents();
if (oldSelectedComponent != null) { // Can be null initially if (oldSelectedComponent != null && !shouldSelectMultipleComponents) { // Can be null initially
oldSelectedComponent.onSelectedChange(false); for (MockComponent component : componentsMap.values()) {
if (component.getName() != selectedComponent.getName()) {
component.onSelectedChange(false);
}
}
} }
newSelectedComponent.onSelectedChange(true); newSelectedComponent.onSelectedChange(true);
} }
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
// http://www.apache.org/licenses/LICENSE-2.0 // http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.client.editor.simple.palette; package com.google.appinventor.client.editor.simple.palette;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.TouchEndEvent; import com.google.gwt.event.dom.client.TouchEndEvent;
......
...@@ -36,7 +36,9 @@ import com.google.appinventor.client.properties.json.ClientJsonParser; ...@@ -36,7 +36,9 @@ import com.google.appinventor.client.properties.json.ClientJsonParser;
import com.google.appinventor.client.properties.json.ClientJsonString; import com.google.appinventor.client.properties.json.ClientJsonString;
import com.google.appinventor.client.widgets.dnd.DropTarget; import com.google.appinventor.client.widgets.dnd.DropTarget;
import com.google.appinventor.client.widgets.properties.EditableProperties; import com.google.appinventor.client.widgets.properties.EditableProperties;
import com.google.appinventor.client.widgets.properties.EditableProperty;
import com.google.appinventor.client.widgets.properties.PropertiesPanel; import com.google.appinventor.client.widgets.properties.PropertiesPanel;
import com.google.appinventor.client.widgets.properties.PropertyChangeListener;
import com.google.appinventor.client.youngandroid.YoungAndroidFormUpgrader; import com.google.appinventor.client.youngandroid.YoungAndroidFormUpgrader;
import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.shared.properties.json.JSONArray; import com.google.appinventor.shared.properties.json.JSONArray;
...@@ -51,9 +53,15 @@ import com.google.appinventor.shared.youngandroid.YoungAndroidSourceAnalyzer; ...@@ -51,9 +53,15 @@ import com.google.appinventor.shared.youngandroid.YoungAndroidSourceAnalyzer;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
import com.google.gwt.core.client.Callback; import com.google.gwt.core.client.Callback;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DockPanel; import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.RootPanel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -72,7 +80,7 @@ import java.util.Set; ...@@ -72,7 +80,7 @@ import java.util.Set;
* @author markf@google.com (Mark Friedman) * @author markf@google.com (Mark Friedman)
* @author lizlooney@google.com (Liz Looney) * @author lizlooney@google.com (Liz Looney)
*/ */
public final class YaFormEditor extends SimpleEditor implements FormChangeListener, ComponentDatabaseChangeListener { public final class YaFormEditor extends SimpleEditor implements FormChangeListener, ComponentDatabaseChangeListener, PropertyChangeListener {
private static class FileContentHolder { private static class FileContentHolder {
private String content; private String content;
...@@ -136,6 +144,10 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -136,6 +144,10 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
private static final int OLD_PROJECT_YAV = 150; // Projects older then this have no authURL private static final int OLD_PROJECT_YAV = 150; // Projects older then this have no authURL
private boolean shouldSelectMultipleComponents = false;
private EditableProperties selectedProperties = null;
private List<MockComponent> selectedComponents = new ArrayList<MockComponent>();
/** /**
* Creates a new YaFormEditor. * Creates a new YaFormEditor.
* *
...@@ -162,6 +174,24 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -162,6 +174,24 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
componentsPanel.add(visibleComponentsPanel, DockPanel.NORTH); componentsPanel.add(visibleComponentsPanel, DockPanel.NORTH);
componentsPanel.add(nonVisibleComponentsPanel, DockPanel.SOUTH); componentsPanel.add(nonVisibleComponentsPanel, DockPanel.SOUTH);
componentsPanel.setSize("100%", "100%"); componentsPanel.setSize("100%", "100%");
RootPanel.get().addDomHandler(new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent event) {
int keyCode = event.getNativeKeyCode();
if (keyCode == KeyCodes.KEY_SHIFT) {
shouldSelectMultipleComponents = true;
}
}
}, KeyDownEvent.getType());
RootPanel.get().addDomHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
int keyCode = event.getNativeKeyCode();
if (keyCode == KeyCodes.KEY_SHIFT) {
shouldSelectMultipleComponents = false;
}
}
}, KeyUpEvent.getType());
// Create palettePanel, which will be used as the content of the PaletteBox. // Create palettePanel, which will be used as the content of the PaletteBox.
palettePanel = new YoungAndroidPalettePanel(this); palettePanel = new YoungAndroidPalettePanel(this);
...@@ -187,6 +217,14 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -187,6 +217,14 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
setSize("100%", "100%"); setSize("100%", "100%");
} }
public List<MockComponent> getSelectedComponents() {
return selectedComponents;
}
public boolean getShouldSelectMultipleComponents() {
return shouldSelectMultipleComponents;
}
public boolean shouldDisplayHiddenComponents() { public boolean shouldDisplayHiddenComponents() {
return visibleComponentsPanel.isHiddenComponentsCheckboxChecked(); return visibleComponentsPanel.isHiddenComponentsCheckboxChecked();
} }
...@@ -319,6 +357,15 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -319,6 +357,15 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
return formNode.isScreen1(); return formNode.isScreen1();
} }
// PropertyChangeListener implementation
@Override
public void onPropertyChange(String propertyName, String propertyValue) {
for (MockComponent selectedComponent : selectedComponents) {
selectedComponent.changeProperty(propertyName, propertyValue);
}
}
// FormChangeListener implementation // FormChangeListener implementation
@Override @Override
...@@ -335,10 +382,42 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -335,10 +382,42 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
} }
} }
private boolean isIntersectedProperty(String propertyName) {
for (MockComponent selectedComponent : selectedComponents) {
if (!selectedComponent.hasProperty(propertyName)) {
return false;
}
}
return true;
}
@Override @Override
public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) { public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) {
if (loadComplete) { if (loadComplete) {
if (permanentlyDeleted) { if (permanentlyDeleted) {
if (isSelectedComponent(component)) {
selectedComponents.remove(component);
EditableProperties propertyIntersection = new EditableProperties(true);
for (MockComponent selectedComponent : selectedComponents) {
for (EditableProperty property : selectedComponent.getProperties()) {
String propertyName = property.getName();
if (propertyName == "Uuid") {
continue;
}
if (isIntersectedProperty(propertyName)) {
propertyIntersection.addProperty(
propertyName,
property.getDefaultValue(),
property.getCaption(),
property.getEditor(),
property.getType()
);
}
}
}
selectedProperties = propertyIntersection;
selectedProperties.addPropertyChangeListener(this);
}
onFormStructureChange(); onFormStructureChange();
} }
} else { } else {
...@@ -349,6 +428,8 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -349,6 +428,8 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
@Override @Override
public void onComponentAdded(MockComponent component) { public void onComponentAdded(MockComponent component) {
if (loadComplete) { if (loadComplete) {
selectedProperties = component.getProperties();
selectedComponents = new ArrayList<MockComponent>();
onFormStructureChange(); onFormStructureChange();
} else { } else {
OdeLog.elog("onComponentAdded called when loadComplete is false"); OdeLog.elog("onComponentAdded called when loadComplete is false");
...@@ -365,18 +446,87 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -365,18 +446,87 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
} }
} }
public boolean isSelectedComponent(MockComponent component) {
String name = component.getName();
for (MockComponent selectedComponent : selectedComponents) {
if (selectedComponent.getName() == name) {
return true;
}
}
return false;
}
private boolean isIntersectedValue(String propertyName) {
String value = null;
for (MockComponent selectedComponent : selectedComponents) {
String propertyValue = selectedComponent.getPropertyValue(propertyName);
if (value == null) {
value = propertyValue;
} else if (!value.equals(propertyValue)) {
return false;
}
}
return true;
}
@Override @Override
public void onComponentSelectionChange(MockComponent component, boolean selected) { public void onComponentSelectionChange(MockComponent component, boolean selected) {
if (loadComplete) { if (loadComplete) {
if (selected) { if (selected) {
// Select the item in the source structure explorer. // Select the item in the source structure explorer.
sourceStructureExplorer.selectItem(component.getSourceStructureExplorerItem()); sourceStructureExplorer.selectItem(component.getSourceStructureExplorerItem());
EditableProperties componentProperties = component.getProperties();
if (selectedProperties != null && shouldSelectMultipleComponents) {
if (!isSelectedComponent(component)) {
selectedComponents.add(component);
}
EditableProperties propertyIntersection = new EditableProperties(true);
for (EditableProperty property : componentProperties) {
String propertyName = property.getName();
if (propertyName == "Uuid") {
continue;
}
if (selectedProperties.hasProperty(propertyName)) {
propertyIntersection.addProperty(
propertyName,
(isIntersectedValue(propertyName)) ? property.getValue() : property.getDefaultValue(),
property.getCaption(),
property.getEditor(),
property.getType()
);
}
}
selectedProperties = propertyIntersection;
selectedProperties.addPropertyChangeListener(this);
} else {
selectedComponents = new ArrayList<MockComponent>();
selectedComponents.add(component);
selectedProperties = componentProperties;
}
// Show the component properties in the properties panel. // Show the component properties in the properties panel.
updatePropertiesPanel(component); updatePropertiesPanel(component);
} else { } else if (isSelectedComponent(component) && selectedComponents.size() > 1) {
// Unselect the item in the source structure explorer. selectedComponents.remove(component);
sourceStructureExplorer.unselectItem(component.getSourceStructureExplorerItem()); EditableProperties propertyIntersection = new EditableProperties(true);
for (EditableProperty property : selectedComponents.get(0).getProperties()) {
String propertyName = property.getName();
if (propertyName == "Uuid") {
continue;
}
if (isIntersectedProperty(propertyName)) {
propertyIntersection.addProperty(
propertyName,
(isIntersectedValue(propertyName)) ? property.getValue() : property.getDefaultValue(),
property.getCaption(),
property.getEditor(),
property.getType()
);
}
}
selectedProperties = propertyIntersection;
selectedProperties.addPropertyChangeListener(this);
updatePropertiesPanel(selectedComponents.size() == 1 ? selectedComponents.get(0) : null);
onFormStructureChange();
} }
} else { } else {
OdeLog.elog("onComponentSelectionChange called when loadComplete is false"); OdeLog.elog("onComponentSelectionChange called when loadComplete is false");
...@@ -701,11 +851,16 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen ...@@ -701,11 +851,16 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
* Show the given component's properties in the properties panel. * Show the given component's properties in the properties panel.
*/ */
private void updatePropertiesPanel(MockComponent component) { private void updatePropertiesPanel(MockComponent component) {
if (selectedComponents.size() > 1) {
designProperties.setProperties(selectedProperties);
designProperties.setPropertiesCaption(selectedComponents.size() + " components selected");
} else {
designProperties.setProperties(component.getProperties()); designProperties.setProperties(component.getProperties());
// need to update the caption after the setProperties call, since // need to update the caption after the setProperties call, since
// setProperties clears the caption! // setProperties clears the caption!
designProperties.setPropertiesCaption(component.getName()); designProperties.setPropertiesCaption(component.getName());
} }
}
private void onFormStructureChange() { private void onFormStructureChange() {
Ode.getInstance().getEditorManager().scheduleAutoSave(this); Ode.getInstance().getEditorManager().scheduleAutoSave(this);
......
...@@ -146,7 +146,7 @@ public final class EditableProperty extends Property { ...@@ -146,7 +146,7 @@ public final class EditableProperty extends Property {
* *
* @return property caption * @return property caption
*/ */
String getCaption() { public String getCaption() {
return caption; return caption;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment