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;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import com.google.appinventor.client.editor.simple.SimpleEditor;
......@@ -915,14 +916,25 @@ public final class MockForm extends MockContainer {
if (newSelectedComponent == null) {
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;
}
selectedComponent = newSelectedComponent;
Map<String, MockComponent> componentsMap = formEditor.getComponents();
if (oldSelectedComponent != null) { // Can be null initially
oldSelectedComponent.onSelectedChange(false);
if (oldSelectedComponent != null && !shouldSelectMultipleComponents) { // Can be null initially
for (MockComponent component : componentsMap.values()) {
if (component.getName() != selectedComponent.getName()) {
component.onSelectedChange(false);
}
}
}
newSelectedComponent.onSelectedChange(true);
}
......
......@@ -4,7 +4,6 @@
// http://www.apache.org/licenses/LICENSE-2.0
package com.google.appinventor.client.editor.simple.palette;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.TouchEndEvent;
......
......@@ -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.widgets.dnd.DropTarget;
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.PropertyChangeListener;
import com.google.appinventor.client.youngandroid.YoungAndroidFormUpgrader;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.shared.properties.json.JSONArray;
......@@ -51,9 +53,15 @@ import com.google.appinventor.shared.youngandroid.YoungAndroidSourceAnalyzer;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
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.Window;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.RootPanel;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -72,7 +80,7 @@ import java.util.Set;
* @author markf@google.com (Mark Friedman)
* @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 String content;
......@@ -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 boolean shouldSelectMultipleComponents = false;
private EditableProperties selectedProperties = null;
private List<MockComponent> selectedComponents = new ArrayList<MockComponent>();
/**
* Creates a new YaFormEditor.
*
......@@ -162,6 +174,24 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
componentsPanel.add(visibleComponentsPanel, DockPanel.NORTH);
componentsPanel.add(nonVisibleComponentsPanel, DockPanel.SOUTH);
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.
palettePanel = new YoungAndroidPalettePanel(this);
......@@ -187,6 +217,14 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
setSize("100%", "100%");
}
public List<MockComponent> getSelectedComponents() {
return selectedComponents;
}
public boolean getShouldSelectMultipleComponents() {
return shouldSelectMultipleComponents;
}
public boolean shouldDisplayHiddenComponents() {
return visibleComponentsPanel.isHiddenComponentsCheckboxChecked();
}
......@@ -319,6 +357,15 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
return formNode.isScreen1();
}
// PropertyChangeListener implementation
@Override
public void onPropertyChange(String propertyName, String propertyValue) {
for (MockComponent selectedComponent : selectedComponents) {
selectedComponent.changeProperty(propertyName, propertyValue);
}
}
// FormChangeListener implementation
@Override
......@@ -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
public void onComponentRemoved(MockComponent component, boolean permanentlyDeleted) {
if (loadComplete) {
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();
}
} else {
......@@ -349,6 +428,8 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
@Override
public void onComponentAdded(MockComponent component) {
if (loadComplete) {
selectedProperties = component.getProperties();
selectedComponents = new ArrayList<MockComponent>();
onFormStructureChange();
} else {
OdeLog.elog("onComponentAdded called when loadComplete is false");
......@@ -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
public void onComponentSelectionChange(MockComponent component, boolean selected) {
if (loadComplete) {
if (selected) {
// Select the item in the source structure explorer.
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.
updatePropertiesPanel(component);
} else {
// Unselect the item in the source structure explorer.
sourceStructureExplorer.unselectItem(component.getSourceStructureExplorerItem());
} else if (isSelectedComponent(component) && selectedComponents.size() > 1) {
selectedComponents.remove(component);
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 {
OdeLog.elog("onComponentSelectionChange called when loadComplete is false");
......@@ -701,10 +851,15 @@ public final class YaFormEditor extends SimpleEditor implements FormChangeListen
* Show the given component's properties in the properties panel.
*/
private void updatePropertiesPanel(MockComponent component) {
designProperties.setProperties(component.getProperties());
// need to update the caption after the setProperties call, since
// setProperties clears the caption!
designProperties.setPropertiesCaption(component.getName());
if (selectedComponents.size() > 1) {
designProperties.setProperties(selectedProperties);
designProperties.setPropertiesCaption(selectedComponents.size() + " components selected");
} else {
designProperties.setProperties(component.getProperties());
// need to update the caption after the setProperties call, since
// setProperties clears the caption!
designProperties.setPropertiesCaption(component.getName());
}
}
private void onFormStructureChange() {
......
......@@ -146,7 +146,7 @@ public final class EditableProperty extends Property {
*
* @return property caption
*/
String getCaption() {
public String getCaption() {
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