diff --git a/appinventor/appengine/src/com/google/appinventor/client/Ode.java b/appinventor/appengine/src/com/google/appinventor/client/Ode.java index 430db9ad261..63975143d07 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/Ode.java +++ b/appinventor/appengine/src/com/google/appinventor/client/Ode.java @@ -20,6 +20,7 @@ import com.google.appinventor.client.editor.EditorManager; import com.google.appinventor.client.editor.FileEditor; import com.google.appinventor.client.editor.ProjectEditor; +import com.google.appinventor.client.editor.simple.components.MockComponent; import com.google.appinventor.client.editor.simple.palette.DropTargetProvider; import com.google.appinventor.client.editor.youngandroid.BlocklyPanel; import com.google.appinventor.client.editor.youngandroid.DesignToolbar; @@ -82,7 +83,11 @@ import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.RunAsyncCallback; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.MouseWheelEvent; import com.google.gwt.event.dom.client.MouseWheelHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; @@ -94,6 +99,7 @@ import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Event.NativePreviewEvent; import com.google.gwt.user.client.History; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; @@ -117,6 +123,8 @@ import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.widgetideas.client.event.KeyDownHandler; + import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; @@ -1609,6 +1617,15 @@ public DialogBox createEmptyTrashDialog(boolean showDialog) { dialogBox.show(); } + Event.addNativePreviewHandler(new Event.NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(Event.NativePreviewEvent event) { + if (event.getTypeInt() == Event.ONKEYDOWN && dialogBox.isShowing()) { + dialogBox.hide(); + } + } + }); + return dialogBox; } diff --git a/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java b/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java index 9d9f9341ef3..f0b5bb5cff9 100755 --- a/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java +++ b/appinventor/appengine/src/com/google/appinventor/client/OdeMessages.java @@ -655,7 +655,7 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages { @Description("Message providing details about starting a USB connection.") String usbMenuItem(); - @DefaultMessage("Reset Connection") + @DefaultMessage("Reset Connection (Alt + Shift + R)") @Description("Reset all connections.") String resetConnectionsMenuItem(); @@ -663,7 +663,7 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages { @Description("Hard Reset the Emulator.") String hardResetConnectionsMenuItem(); - @DefaultMessage("Refresh Companion Screen") + @DefaultMessage("Refresh Companion Screen (Alt + R)") @Description("Refresh the companion screen.") String refreshCompanionMenuItem(); @@ -725,6 +725,10 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages { @Description("Redisplay the Splash Screen") String showSplashMenuItem(); + @DefaultMessage("Show Keyboard Shortcuts (Alt + ?)") + @Description("Display the Shortcuts dialog") + String showShortcuts(); + @DefaultMessage("Library") @Description("Name of Library link") String libraryMenuItem(); @@ -1254,7 +1258,7 @@ public interface OdeMessages extends Messages, AutogeneratedOdeMessages { String blocksLoadFailure(String formName); // Used in editor/youngandroid/palette/YoungAndroidPalettePanel.java - @DefaultMessage("Search Components...") + @DefaultMessage("Type / to search components") @Description("Text shown in the component palette search box") String searchComponents(); @@ -5119,6 +5123,39 @@ String newerVersionComponentException(String componentType, int srcCompVersion, @Description("") String MaximumRangeMethods(); + @DefaultMessage( + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "
ActionKey Combination
Focus Component search box/
Focus Components treeT
Focus ViewerV
Focus Properties PanelP
Switch between Designer and Block editorCtrl + Alt
Rename ComponentAlt + N
Reset ConnectionAlt + Shift + R
Refresh Companion ScreenAlt + R
Open/Close BackpackAlt + B
Switch between Inline and External InputsAlt + I
Add comment to a blockAlt + K
Expand/Collapse blockAlt + O
Navigate up the blocksAlt + S
Navigate down the blocksAlt + W
Zoom inAlt + +
Zoom outAlt + -
Re-centerAlt + G
Navigate Components↑/↓
Open this dialogAlt + ?
") + @Description("") + String KeyBoardShortcuts(); + // =========== ListPicker @DefaultMessage("ItemTextColor") @Description("") diff --git a/appinventor/appengine/src/com/google/appinventor/client/TopToolbar.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/TopToolbar.ui.xml index 8e053e17657..2c4f5951f33 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/TopToolbar.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/TopToolbar.ui.xml @@ -173,6 +173,9 @@ + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/actions/ShowShortcutsAction.java b/appinventor/appengine/src/com/google/appinventor/client/actions/ShowShortcutsAction.java new file mode 100644 index 00000000000..898239808d4 --- /dev/null +++ b/appinventor/appengine/src/com/google/appinventor/client/actions/ShowShortcutsAction.java @@ -0,0 +1,60 @@ +// -*- mode: java; c-basic-offset: 2; -*- +// Copyright 2009-2011 Google, All Rights reserved +// Copyright 2011-2023 MIT, All rights reserved +// Released under the Apache License, Version 2.0 +// http://www.apache.org/licenses/LICENSE-2.0 + +package com.google.appinventor.client.actions; + +import static com.google.appinventor.client.Ode.MESSAGES; + +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.VerticalPanel; + +public class ShowShortcutsAction implements Command { + + private DialogBox db; + + public ShowShortcutsAction() { + db = new DialogBox(true, false); + db.setText("Keyboard Shortcuts"); + db.setStyleName("ode-DialogBox"); + db.setHeight("200px"); + db.setWidth("400px"); + db.setGlassEnabled(true); + db.setAnimationEnabled(true); + + shortcutKeyHandler(); + } + + @Override + public void execute() { + VerticalPanel DialogBoxContents = new VerticalPanel(); + HTML message = new HTML(MESSAGES.KeyBoardShortcuts()); + DialogBoxContents.add(message); + db.setWidget(DialogBoxContents); + db.center(); + db.show(); + } + + private void shortcutKeyHandler() { + Event.addNativePreviewHandler(new Event.NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(Event.NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (event.getTypeInt() == Event.ONKEYDOWN && nativeEvent.getAltKey() && nativeEvent.getKeyCode() == 191 && !db.isShowing()) { + nativeEvent.preventDefault(); + execute(); + } else if (event.getTypeInt() == Event.ONKEYDOWN && db.isShowing()) { + nativeEvent.preventDefault(); + db.hide(); + } + } + }); + } +} diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/SimpleVisibleComponentsPanel.java b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/SimpleVisibleComponentsPanel.java index f7353ba1131..5498d24317d 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/SimpleVisibleComponentsPanel.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/SimpleVisibleComponentsPanel.java @@ -20,6 +20,7 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; @@ -42,6 +43,7 @@ interface SimpleVisibleComponentsPanelUiBinder extends UiBinder - + diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockComponent.java b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockComponent.java index d16bdc76166..3f81102eebf 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockComponent.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockComponent.java @@ -39,10 +39,12 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.DomEvent; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.HasAllTouchHandlers; import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.event.dom.client.TouchCancelHandler; import com.google.gwt.event.dom.client.TouchEndHandler; import com.google.gwt.event.dom.client.TouchMoveHandler; @@ -110,12 +112,15 @@ private class RenameDialog extends DialogBox { private final LabeledTextBox newNameTextBox; RenameDialog(String oldName) { - super(false, true); - + super(false, false); + setGlassEnabled(true); setStylePrimaryName("ode-DialogBox"); setText(MESSAGES.renameTitle()); VerticalPanel contentPanel = new VerticalPanel(); + Button topInvisible = new Button(); + contentPanel.add(topInvisible); + LabeledTextBox oldNameTextBox = new LabeledTextBox(MESSAGES.oldNameLabel()); oldNameTextBox.setText(getName()); oldNameTextBox.setEnabled(false); @@ -123,9 +128,9 @@ private class RenameDialog extends DialogBox { newNameTextBox = new LabeledTextBox(MESSAGES.newNameLabel()); newNameTextBox.setText(oldName); - newNameTextBox.getTextBox().addKeyUpHandler(new KeyUpHandler() { + newNameTextBox.getTextBox().addKeyDownHandler(new KeyDownHandler() { @Override - public void onKeyUp(KeyUpEvent event) { + public void onKeyDown(KeyDownEvent event) { int keyCode = event.getNativeKeyCode(); if (keyCode == KeyCodes.KEY_ENTER) { handleOkClick(); @@ -150,9 +155,26 @@ public void onClick(ClickEvent event) { handleOkClick(); } }); + + Button bottomInvisible = new Button(); + bottomInvisible.setStyleName("FocusTrap"); + topInvisible.setStyleName("FocusTrap"); + topInvisible.addFocusHandler(new FocusHandler() { + @Override + public void onFocus(FocusEvent event) { + okButton.setFocus(true); + } + }); + bottomInvisible.addFocusHandler(new FocusHandler() { + public void onFocus(FocusEvent event) { + newNameTextBox.setFocus(true); + } + }); + HorizontalPanel buttonPanel = new HorizontalPanel(); buttonPanel.add(cancelButton); buttonPanel.add(okButton); + buttonPanel.add(bottomInvisible); buttonPanel.setSize("100%", "24px"); contentPanel.add(buttonPanel); contentPanel.setSize("320px", "100%"); diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockVisibleComponent.java b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockVisibleComponent.java index 01da3fc8ddc..48614ac6655 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockVisibleComponent.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/components/MockVisibleComponent.java @@ -10,6 +10,7 @@ import com.google.appinventor.client.ComponentsTranslation; import com.google.appinventor.client.editor.simple.SimpleEditor; +import com.google.appinventor.client.editor.simple.SimpleNonVisibleComponentsPanel; import com.google.appinventor.client.editor.youngandroid.properties.YoungAndroidLengthPropertyEditor; import com.google.appinventor.client.widgets.properties.TextPropertyEditor; import com.google.appinventor.components.common.ComponentConstants; @@ -158,4 +159,9 @@ public void onPropertyChange(String propertyName, String newValue) { refreshForm(); } } -} + + public SimpleNonVisibleComponentsPanel getNonVisibleComponentsPanel() { + return editor.getNonVisibleComponentsPanel(); + } + +} \ No newline at end of file diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/palette/SimplePaletteItem.java b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/palette/SimplePaletteItem.java index 1e18fa2dbce..329cefabbe4 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/simple/palette/SimplePaletteItem.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/simple/palette/SimplePaletteItem.java @@ -6,12 +6,24 @@ package com.google.appinventor.client.editor.simple.palette; +import java.util.List; + import com.google.appinventor.client.ComponentsTranslation; import com.google.appinventor.client.editor.simple.components.MockComponent; import com.google.appinventor.client.editor.simple.components.MockComponentsUtil; +import com.google.appinventor.client.editor.simple.components.MockContainer; +import com.google.appinventor.client.editor.simple.components.MockForm; +import com.google.appinventor.client.editor.simple.components.MockVisibleComponent; import com.google.appinventor.client.widgets.dnd.DragSourcePanel; import com.google.appinventor.client.widgets.dnd.DragSourceSupport; import com.google.appinventor.client.widgets.dnd.DropTarget; +import com.google.gwt.event.dom.client.DoubleClickEvent; +import com.google.gwt.event.dom.client.DoubleClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +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.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.dom.client.TouchStartEvent; @@ -114,6 +126,40 @@ public void onTouchStart(TouchStartEvent event) { select(getWidget()); } }); + addFocusHandler(new FocusHandler() { + @Override + public void onFocus(FocusEvent event) { + select(getWidget()); + } + }); + addKeyDownHandler(new KeyDownHandler() { + @Override + public void onKeyDown (KeyDownEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + addComponent(); + } + } + }); + addDoubleClickHandler(new DoubleClickHandler() { + public void onDoubleClick (DoubleClickEvent event) { + addComponent(); + } + }); + } + + private void addComponent() { + MockComponent component = createMockComponent(); + MockVisibleComponent mockVisibleComponent = (MockVisibleComponent) dropTargetProvider.getDropTargets()[0]; + MockForm form = mockVisibleComponent.getForm(); + MockComponent selectedComponent = form.getLastSelectedComponent(); + if (selectedComponent instanceof MockContainer && ((MockContainer) selectedComponent).willAcceptComponentType(component.getType()) && component.isVisibleComponent()) { + ((MockContainer) selectedComponent).addComponent(component); + } else if (form.willAcceptComponentType(component.getType()) && component.isVisibleComponent()) { + form.addComponent(component); + } else if (form.willAcceptComponentType(component.getType()) && !component.isVisibleComponent()) { + form.addComponent(component); + form.getNonVisibleComponentsPanel().addComponent(component); + } } /** diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java index 7cb27c05169..bea8504187d 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/DesignToolbar.java @@ -11,6 +11,8 @@ import com.google.appinventor.client.editor.FileEditor; import com.google.appinventor.client.editor.ProjectEditor; import com.google.appinventor.client.editor.youngandroid.actions.SwitchScreenAction; +import com.google.appinventor.client.editor.youngandroid.actions.SwitchToBlocksEditorAction; +import com.google.appinventor.client.editor.youngandroid.actions.SwitchToFormEditorAction; import com.google.appinventor.client.widgets.DropDownButton; import com.google.appinventor.client.widgets.DropDownItem; import com.google.appinventor.client.widgets.Toolbar; @@ -21,9 +23,12 @@ import com.google.common.collect.Maps; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.RootPanel; import java.util.LinkedList; import java.util.Map; @@ -160,6 +165,7 @@ public DesignToolbar() { // Gray out the Designer button and enable the blocks button toggleEditor(false); Ode.getInstance().getTopToolbar().updateFileMenuButtons(0); + toggleView(); } public void bindUI() { @@ -369,6 +375,22 @@ public void toggleEditor(boolean blocks) { setButtonEnabled(WIDGET_NAME_REMOVEFORM, notOnScreen1); } + public void toggleView() { + SwitchToBlocksEditorAction blockView = new SwitchToBlocksEditorAction(); + SwitchToFormEditorAction designView = new SwitchToFormEditorAction(); + RootPanel.get().addDomHandler(new KeyDownHandler() { + public void onKeyDown(KeyDownEvent event) { + if (event.isControlKeyDown() && event.isAltKeyDown()) { + if (currentView == DesignToolbar.View.FORM) { + blockView.execute(); + } else if (currentView == DesignToolbar.View.BLOCKS) { + designView.execute(); + } + } + } + }, KeyDownEvent.getType()); + } + public DesignProject getCurrentProject() { return currentProject; } diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java index 78189f9a2e7..ff4bde2dea5 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/YaFormEditor.java @@ -56,12 +56,17 @@ import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; +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.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; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -199,6 +204,7 @@ public DropTarget[] getDropTargets() { initWidget(componentsPanel); setSize("100%", "100%"); registerNativeListeners(); + registerKeyDownListeners(); } public boolean shouldDisplayHiddenComponents() { @@ -939,6 +945,88 @@ public void onResetDatabase() { } } + + private void registerKeyDownListeners () { + RootPanel.get().addDomHandler(new KeyDownHandler() { + @Override + public void onKeyDown(KeyDownEvent event) { + if (event.isAltKeyDown() && isActiveEditor()) { + List allComponents = new ArrayList<>(getComponents().values()); + MockComponent selectedComponent = form.getLastSelectedComponent(); + int index = form.getChildren().indexOf(selectedComponent); + + if (selectedComponent.isVisibleComponent()) { + switch (event.getNativeKeyCode()) { + case KeyCodes.KEY_DOWN: + if (index < 0) { + MockContainer container = selectedComponent.getContainer(); + List containerComponents = container.getChildren(); + int indexC = containerComponents.indexOf(selectedComponent); + int indexOfContainer = allComponents.indexOf(container); + selectedComponent.getContainer().removeComponent(selectedComponent, false); + + if (indexC == containerComponents.size() && container.getContainer().willAcceptComponentType(selectedComponent.getType())) { + container.getContainer().addVisibleComponent(selectedComponent, indexOfContainer); + } else { + container.addVisibleComponent(selectedComponent, indexC + 1); + } + } else { + index++; + form.removeComponent(selectedComponent, false); + MockComponent nextComponent = allComponents.get(index + 1); + int nextComponentindex = form.getChildren().indexOf(nextComponent); + if(nextComponent instanceof MockContainer && ((MockContainer) nextComponent).willAcceptComponentType(selectedComponent.getType())) { + ((MockContainer)nextComponent).addVisibleComponent(selectedComponent, 0); + } else if (nextComponentindex < 0 && ((MockContainer) nextComponent.getContainer()).willAcceptComponentType(selectedComponent.getType())) { + nextComponent.getContainer().addVisibleComponent(selectedComponent, 0); + } else { + form.addVisibleComponent(selectedComponent, index); + } + } + break; + + case KeyCodes.KEY_UP: + if (index < 0) { + MockContainer container = selectedComponent.getContainer(); + List containerComponents = container.getChildren(); + int indexC = containerComponents.indexOf(selectedComponent); + int indexOfContainer = allComponents.indexOf(container); + selectedComponent.getContainer().removeComponent(selectedComponent, false); + if (indexC == 0 && container.getContainer().willAcceptComponentType(selectedComponent.getType())) { + container.getContainer().addVisibleComponent(selectedComponent, indexOfContainer - 1); + } else { + container.addVisibleComponent(selectedComponent, indexC - 1); + } + } else { + index++; + form.removeComponent(selectedComponent, false); + MockComponent prevComponent = allComponents.get(index - 1); + int prevComponentIndex = form.getChildren().indexOf(prevComponent); + if(prevComponent instanceof MockContainer && ((MockContainer) prevComponent).willAcceptComponentType(selectedComponent.getType())) { + ((MockContainer)prevComponent).addVisibleComponent(selectedComponent, -1); + } else if (prevComponentIndex < 0 && ((MockContainer) prevComponent).willAcceptComponentType(selectedComponent.getType())) { + prevComponent.getContainer().addVisibleComponent(selectedComponent, -1); + } else { + form.addVisibleComponent(selectedComponent, index - 2); + } + } + break; + + default: + break; + } + } + } else if (event.getNativeKeyCode() == KeyCodes.KEY_T && !palettePanel.isTextboxFocused() && isActiveEditor()) { + SourceStructureBox.getSourceStructureBox().getSourceStructureExplorer().getTree().setFocus(true); + } else if (event.getNativeKeyCode() == KeyCodes.KEY_V && !palettePanel.isTextboxFocused() && isActiveEditor()) { + getVisibleComponentsPanel().focusCheckbox(); + } else if (event.getNativeKeyCode() == KeyCodes.KEY_P && !palettePanel.isTextboxFocused() && isActiveEditor()) { + PropertiesBox.getPropertiesBox().getElement().getElementsByTagName("a").getItem(0).focus(); + } + } + }, KeyDownEvent.getType()); + } + @SuppressWarnings("checkstyle:LineLength") private native void registerNativeListeners()/*-{ var editor = this; diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/RefreshCompanionAction.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/RefreshCompanionAction.java index 36869878112..4aab313fe5a 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/RefreshCompanionAction.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/RefreshCompanionAction.java @@ -6,13 +6,35 @@ package com.google.appinventor.client.editor.youngandroid.actions; import com.google.appinventor.client.Ode; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; public class RefreshCompanionAction implements Command { + + public RefreshCompanionAction() { + shortcutKeyHandler(); + } + @Override public void execute() { if (Ode.getInstance().okToConnect()) { Ode.getInstance().getTopToolbar().replUpdate(); } } + + private void shortcutKeyHandler() { + Event.addNativePreviewHandler(new Event.NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(Event.NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (event.getTypeInt() == Event.ONKEYDOWN && nativeEvent.getKeyCode() == KeyCodes.KEY_R + && nativeEvent.getAltKey()) { + nativeEvent.preventDefault(); + execute(); + } + } + }); + } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/ResetAction.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/ResetAction.java index 239ed04e50e..d97da5a5d89 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/ResetAction.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/actions/ResetAction.java @@ -7,13 +7,35 @@ package com.google.appinventor.client.editor.youngandroid.actions; import com.google.appinventor.client.Ode; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; public class ResetAction implements Command { + + public ResetAction() { + shortcutKeyHandler(); + } + @Override public void execute() { if (Ode.getInstance().okToConnect()) { Ode.getInstance().getTopToolbar().startRepl(false, false, false, false); // We are really stopping the repl here } } + + private void shortcutKeyHandler() { + Event.addNativePreviewHandler(new Event.NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(Event.NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (event.getTypeInt() == Event.ONKEYDOWN && nativeEvent.getKeyCode() == KeyCodes.KEY_R + && nativeEvent.getAltKey() && nativeEvent.getShiftKey()) { + execute(); + nativeEvent.preventDefault(); + } + } + }); + } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/palette/YoungAndroidPalettePanel.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/palette/YoungAndroidPalettePanel.java index 614cf065659..0503b1167d3 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/palette/YoungAndroidPalettePanel.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/palette/YoungAndroidPalettePanel.java @@ -7,6 +7,7 @@ package com.google.appinventor.client.editor.youngandroid.palette; import com.google.appinventor.client.ComponentsTranslation; +import com.google.appinventor.client.Ode; import com.google.appinventor.client.editor.simple.SimpleComponentDatabase; import com.google.appinventor.client.editor.simple.components.MockComponent; import com.google.appinventor.client.editor.simple.components.utils.PropertiesUtil; @@ -15,6 +16,7 @@ import com.google.appinventor.client.editor.simple.palette.SimpleComponentDescriptor; import com.google.appinventor.client.editor.simple.palette.SimplePaletteItem; import com.google.appinventor.client.editor.simple.palette.SimplePalettePanel; +import com.google.appinventor.client.editor.youngandroid.DesignToolbar; import com.google.appinventor.client.editor.youngandroid.YaFormEditor; import com.google.appinventor.client.explorer.project.ComponentDatabaseChangeListener; import com.google.appinventor.client.wizards.ComponentImportWizard; @@ -31,6 +33,7 @@ import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasHorizontalAlignment; +import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.event.dom.client.KeyPressEvent; @@ -44,12 +47,12 @@ import jsinterop.annotations.JsOverlay; import jsinterop.annotations.JsType; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.*; +import java.util.Iterator; +import java.util.Set; import static com.google.appinventor.client.Ode.MESSAGES; @@ -192,6 +195,20 @@ public void onChange(ChangeEvent event) { } }); + /* User presses the slash key, the search text box is focused */ + RootPanel.get().addDomHandler(new KeyDownHandler() { + @Override + public void onKeyDown(KeyDownEvent event) { + DesignToolbar designToolbar = Ode.getInstance().getDesignToolbar(); + if (designToolbar.currentView == DesignToolbar.View.FORM && event.getNativeKeyCode() == 191 && !isTextboxFocused()) { + { + event.preventDefault(); + searchText.setFocus(true); + } + } + } + }, KeyDownEvent.getType()); + panel.setSpacing(3); panel.add(searchText); panel.setWidth("100%"); @@ -504,4 +521,8 @@ public void reloadComponents() { loadComponents(); } + public native boolean isTextboxFocused()/*-{ + var element = $doc.activeElement; + return element.tagName === 'INPUT' && element.type === 'text' || element.tagName === 'TEXTAREA'; + }-*/; } diff --git a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/properties/YoungAndroidLengthPropertyEditor.java b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/properties/YoungAndroidLengthPropertyEditor.java index 1745fb951b8..65d98a312db 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/properties/YoungAndroidLengthPropertyEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/editor/youngandroid/properties/YoungAndroidLengthPropertyEditor.java @@ -140,6 +140,12 @@ public void onClick(ClickEvent event) { initAdditionalChoicePanel(panel); } + @Override + protected void openAdditionalChoiceDialog() { + super.openAdditionalChoiceDialog(); + automaticRadioButton.setFocus(true); + } + @Override protected void updateValue() { super.updateValue(); diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java index 586fbdcc7fd..aaffe74ea8b 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/SourceStructureExplorer.java @@ -9,8 +9,14 @@ import com.google.appinventor.client.Ode; import com.google.appinventor.client.widgets.TextButton; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; +import com.google.gwt.event.dom.client.MouseUpEvent; +import com.google.gwt.event.dom.client.MouseUpHandler; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.logical.shared.CloseHandler; @@ -104,7 +110,7 @@ public void onOpen(OpenEvent event) { }); tree.addSelectionHandler(new SelectionHandler() { @Override - public void onSelection(SelectionEvent event) { + public void onSelection(SelectionEvent event) { TreeItem treeItem = event.getSelectedItem(); if (treeItem != null) { Object userObject = treeItem.getUserObject(); @@ -130,9 +136,30 @@ public void onKeyDown(KeyDownEvent event) { if (keyCode == KeyCodes.KEY_DELETE || keyCode == KeyCodes.KEY_BACKSPACE) { event.preventDefault(); deleteItemFromTree(); + } else if (event.isAltKeyDown() && keyCode == KeyCodes.KEY_N) { + event.preventDefault(); + renameItem(); } } }); + tree.addFocusHandler(new FocusHandler() { + @Override + public void onFocus(FocusEvent event) { + tree.getParent().setStyleName("gwt-Tree-focused"); + } + }); + tree.addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + tree.getParent().removeStyleName("gwt-Tree-focused"); + } + }); + tree.addMouseUpHandler(new MouseUpHandler() { + @Override + public void onMouseUp(MouseUpEvent event) { + tree.setFocus(true); + } + }); // Put a ScrollPanel around the tree. ScrollPanel scrollPanel = new ScrollPanel(tree); @@ -148,14 +175,7 @@ public void onKeyDown(KeyDownEvent event) { renameButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - TreeItem treeItem = tree.getSelectedItem(); - if (treeItem != null) { - Object userObject = treeItem.getUserObject(); - if (userObject instanceof SourceStructureExplorerItem) { - SourceStructureExplorerItem item = (SourceStructureExplorerItem) userObject; - item.rename(); - } - } + renameItem(); } }); buttonPanel.add(renameButton); @@ -195,6 +215,17 @@ private void deleteItemFromTree() { } } + private void renameItem() { + TreeItem treeItem = tree.getSelectedItem(); + if (treeItem != null) { + Object userObject = treeItem.getUserObject(); + if (userObject instanceof SourceStructureExplorerItem) { + SourceStructureExplorerItem item = (SourceStructureExplorerItem) userObject; + item.rename(); + } + } + } + private void enableButtons(SourceStructureExplorerItem item) { renameButton.setEnabled(item.canRename()); deleteButton.setEnabled(item.canDelete()); @@ -315,4 +346,8 @@ public void selectItem(SourceStructureExplorerItem item) { public void unselectItem(SourceStructureExplorerItem item) { selectItem(item, false); } + + public Tree getTree() { + return tree; + } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/AddFormCommand.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/AddFormCommand.java index 15aff037a04..386156e85b9 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/AddFormCommand.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/AddFormCommand.java @@ -25,8 +25,8 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DialogBox; @@ -109,9 +109,9 @@ private class NewFormDialog extends DialogBox { newNameTextBox = new LabeledTextBox(MESSAGES.formNameLabel()); newNameTextBox.setText(defaultFormName); - newNameTextBox.getTextBox().addKeyUpHandler(new KeyUpHandler() { + newNameTextBox.getTextBox().addKeyDownHandler(new KeyDownHandler() { @Override - public void onKeyUp(KeyUpEvent event) { + public void onKeyDown(KeyDownEvent event) { int keyCode = event.getNativeKeyCode(); if (keyCode == KeyCodes.KEY_ENTER) { handleOkClick(projectRootNode); diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/CopyYoungAndroidProjectCommand.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/CopyYoungAndroidProjectCommand.java index d865352e557..444bdd0626b 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/CopyYoungAndroidProjectCommand.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/CopyYoungAndroidProjectCommand.java @@ -20,8 +20,8 @@ import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.dom.client.KeyCodes; -import com.google.gwt.event.dom.client.KeyUpEvent; -import com.google.gwt.event.dom.client.KeyUpHandler; +import com.google.gwt.event.dom.client.KeyDownEvent; +import com.google.gwt.event.dom.client.KeyDownHandler; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.DeferredCommand; @@ -154,9 +154,9 @@ public String getErrorMessage() { } }); newNameTextBox.setText(defaultNewName); - newNameTextBox.getTextBox().addKeyUpHandler(new KeyUpHandler() { + newNameTextBox.getTextBox().addKeyDownHandler(new KeyDownHandler() { @Override - public void onKeyUp(KeyUpEvent event) { + public void onKeyDown(KeyDownEvent event) { int keyCode = event.getNativeKeyCode(); if (keyCode == KeyCodes.KEY_ENTER) { handleOkClick(oldProjectNode); diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/DeleteFileCommand.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/DeleteFileCommand.java index 9dc7d2518ef..190057aa4f5 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/DeleteFileCommand.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/commands/DeleteFileCommand.java @@ -22,6 +22,9 @@ import com.google.gwt.core.client.Scheduler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; +import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; import com.google.gwt.user.client.Window; @@ -100,7 +103,8 @@ private class DeleteFormDialog extends DialogBox { private final String formName; DeleteFormDialog(final YoungAndroidSourceNode node) { - super(false, true); + super(false, false); + setGlassEnabled(true); this.node = node; formName = node.getFormName(); @@ -108,6 +112,11 @@ private class DeleteFormDialog extends DialogBox { setStylePrimaryName("ode-DialogBox"); setText(MESSAGES.removeFormButton()); + Button topInvisible = new Button(); + Button bottomInvisible = new Button(); + topInvisible.setStyleName("FocusTrap"); + bottomInvisible.setStyleName("FocusTrap"); + Button cancelButton = new Button(MESSAGES.cancelButton()); cancelButton.addClickHandler(new ClickHandler() { @Override @@ -133,11 +142,13 @@ public void onClick(ClickEvent event) { labelPanel.add(warnmsg); labelPanel.setSize("100%", "22px"); contentPanel.add(labelPanel); + contentPanel.add(topInvisible); contentPanel.add(nameTextBox); HorizontalPanel buttonPanel = new HorizontalPanel(); HorizontalPanel checkboxPanel = new HorizontalPanel(); buttonPanel.add(cancelButton); buttonPanel.add(deleteButton); + buttonPanel.add(bottomInvisible); checkboxPanel.add(cb); checkboxPanel.setSize("100%", "20px"); contentPanel.add(checkboxPanel); @@ -148,15 +159,32 @@ public void onClick(ClickEvent event) { nameTextBox.getTextBox().addKeyUpHandler(new KeyUpHandler() { @Override public void onKeyUp(KeyUpEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { + hide(); + executionFailedOrCanceled(); + } if (nameTextBox.getText().equals(formName)) { deleteButton.setEnabled(true); nameTextBox.setColor("#00c8ff"); + if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + handleOkClick(); + } } else { deleteButton.setEnabled(false); nameTextBox.setColor("red"); } } }); + topInvisible.addFocusHandler(new FocusHandler() { + public void onFocus(FocusEvent event) { + cancelButton.setFocus(true); + } + }); + bottomInvisible.addFocusHandler(new FocusHandler() { + public void onFocus(FocusEvent event) { + nameTextBox.setFocus(true); + } + }); add(contentPanel); } diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.java index 3c03a337531..89d80708a2b 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.java @@ -12,6 +12,7 @@ import com.google.appinventor.client.wizards.youngandroid.NewYoungAndroidProjectWizard; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; @@ -54,6 +55,10 @@ public void execute(Project project) { Button goToYR; @UiField Button noDialogNewProject; + @UiField + Button topInvisible; + @UiField + Button bottomInvisible; /** * Creates a new dialog box when the user has no current projects in their @@ -66,6 +71,8 @@ public NoProjectDialogBox() { this.center(); this.setAnimationEnabled(true); this.setAutoHideEnabled(true); + this.setModal(false); + noDialogNewProject.setFocus(true); lastDialog = this; } @@ -109,4 +116,14 @@ public static void closeIfOpen() { lastDialog = null; } } + + @UiHandler("topInvisible") + protected void FocusLast(FocusEvent event) { + closeDialogBox.setFocus(true); + } + + @UiHandler("bottomInvisible") + protected void FocusFirst(FocusEvent event) { + goToPurr.setFocus(true); + } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.ui.xml index 6b414d3744e..862747c0faf 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/NoProjectDialogBox.ui.xml @@ -13,6 +13,7 @@ +

@@ -127,7 +128,7 @@

- + diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/ProjectPropertiesDialogBox.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/ProjectPropertiesDialogBox.java index be50ee6b1bf..e0355cfe0ce 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/ProjectPropertiesDialogBox.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/dialogs/ProjectPropertiesDialogBox.java @@ -20,6 +20,7 @@ import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; @@ -58,6 +59,12 @@ interface ProjectPropertiesDialogBoxUiBinder extends UiBinder + @@ -27,6 +28,7 @@ >Close + diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.java index 85aed8fc0b5..e98bbd2f7e4 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.java @@ -16,6 +16,8 @@ import com.google.appinventor.client.explorer.youngandroid.ProjectListItem; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.json.client.JSONArray; import com.google.gwt.json.client.JSONObject; @@ -27,6 +29,7 @@ import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Label; import java.util.ArrayList; @@ -64,6 +67,7 @@ interface ProjectFolderUiBinder extends UiBinder { } @UiField protected Label dateCreatedLabel; @UiField protected CheckBox checkBox; @UiField protected Icon expandButton; + @UiField protected FocusPanel expandbuttonFocusPanel; public ProjectFolder(String name, long dateCreated, long dateModified, ProjectFolder parent) { bindUI(); @@ -132,9 +136,19 @@ protected void toggleFolderSelection(ClickEvent e) { fireSelectionChangeEvent(); } - @SuppressWarnings("unused") - @UiHandler("expandButton") + @UiHandler("expandbuttonFocusPanel") protected void toggleExpandedState(ClickEvent e) { + toggleState(); + } + + @UiHandler("expandbuttonFocusPanel") + protected void toggleExpandedState(KeyDownEvent e) { + if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + toggleState(); + } + } + + private void toggleState() { setSelected(false); isExpanded = !isExpanded; if (isExpanded) { @@ -153,9 +167,9 @@ protected void toggleExpandedState(ClickEvent e) { public void setSelected(boolean selected) { checkBox.setValue(selected); if (selected) { - container.addStyleName("ode-ProjectRowHighlighted"); + container.addStyleName("ode-ProjectRow-Highlighted"); } else { - container.removeStyleName("ode-ProjectRowHighlighted"); + container.removeStyleName("ode-ProjectRow-Highlighted"); } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.ui.xml index 3a01f3d0ee8..9039fe4bfc9 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/folder/ProjectFolder.ui.xml @@ -12,11 +12,13 @@ - - - - - + + + + + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.java index 186234a8cbc..c969ef521d4 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.java @@ -15,13 +15,15 @@ import com.google.appinventor.client.explorer.project.ProjectSelectionChangeHandler; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; - +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.InlineLabel; import java.util.Collections; @@ -70,6 +72,9 @@ private enum SortOrder { @UiField protected InlineLabel createDateSortAsc; @UiField protected InlineLabel modDateSortDec; @UiField protected InlineLabel modDateSortAsc; + @UiField protected FocusPanel nameFocusPanel; + @UiField protected FocusPanel createdateFocusPanel; + @UiField protected FocusPanel modDateFocusPanel; /** * Creates a new ProjectList @@ -98,21 +103,43 @@ public void bindIU() { } @SuppressWarnings("unused") - @UiHandler("projectName") + @UiHandler("nameFocusPanel") public void sortByNameField(ClickEvent e) { changeSortOrder(SortField.NAME); } - @UiHandler("createDate") + @SuppressWarnings("unused") + @UiHandler("nameFocusPanel") + public void sortByNameField(KeyDownEvent e) { + if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + changeSortOrder(SortField.NAME); + } + } + + @UiHandler("createdateFocusPanel") public void sortByCreateDate(ClickEvent e) { changeSortOrder(SortField.DATE_CREATED); } - @UiHandler("modDate") + @UiHandler("createdateFocusPanel") + public void sortByCreateDate(KeyDownEvent e) { + if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + changeSortOrder(SortField.DATE_CREATED); + } + } + + @UiHandler("modDateFocusPanel") public void sortByModDate(ClickEvent e) { changeSortOrder(SortField.DATE_MODIFIED); } + @UiHandler("modDateFocusPanel") + public void sortByModDate(KeyDownEvent e) { + if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + changeSortOrder(SortField.DATE_MODIFIED); + } + } + private void changeSortOrder(SortField clickedSortField) { if (sortField != clickedSortField) { sortField = clickedSortField; diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.ui.xml index af4d6c3cd16..3bf211ffc24 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectList.ui.xml @@ -11,30 +11,36 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.java b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.java index 68e25fb87da..a8f712268d0 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.java +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.java @@ -17,6 +17,8 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.KeyCodes; +import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiFactory; @@ -25,6 +27,7 @@ import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Label; import java.util.Date; @@ -37,6 +40,7 @@ interface ProjectListItemUiBinder extends UiBinder { @UiField protected Label dateModifiedLabel; @UiField protected Label dateCreatedLabel; @UiField protected CheckBox checkBox; + @UiField protected FocusPanel projectnameFocusPanel; private final Project project; private ProjectSelectionChangeHandler changeHandler; @@ -74,9 +78,9 @@ public boolean isSelected() { public void setSelected(boolean selected) { checkBox.setValue(selected); if (selected) { - container.addStyleName("ode-ProjectRowHighlighted"); + container.addStyleName("ode-ProjectRow-Highlighted"); } else { - container.removeStyleName("ode-ProjectRowHighlighted"); + container.removeStyleName("ode-ProjectRow-Highlighted"); } } @@ -96,9 +100,15 @@ protected void toggleItemSelection(ClickEvent e) { changeHandler.onSelectionChange(checkBox.getValue()); } + @UiHandler("projectnameFocusPanel") + protected void openProject(KeyDownEvent e) { + if (e.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + Ode.getInstance().openYoungAndroidProjectInDesigner(project); + } + } @SuppressWarnings("unused") - @UiHandler("nameLabel") + @UiHandler("projectnameFocusPanel") protected void itemClicked(ClickEvent e) { Ode.getInstance().openYoungAndroidProjectInDesigner(project); } diff --git a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.ui.xml index b27eaf6e685..c0e2aa0971f 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/explorer/youngandroid/ProjectListItem.ui.xml @@ -10,7 +10,9 @@ - + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.java b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.java index cedc20e2b2d..52806a74ef3 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.java +++ b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.java @@ -12,11 +12,13 @@ import com.google.appinventor.client.explorer.youngandroid.ProjectListItem; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.json.client.JSONObject; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Label; public class ProjectFolderNeo extends ProjectFolder { @@ -29,6 +31,7 @@ interface ProjectFolderUiBinderNeo extends UiBinder @UiField protected Label dateCreatedLabel; @UiField protected CheckBox checkBox; @UiField protected Icon expandButton; + @UiField protected FocusPanel expandbuttonFocusPanel; public ProjectFolderNeo(String name, long dateCreated, long dateModified, ProjectFolder parent) { super(name, dateCreated, dateModified, parent); @@ -53,6 +56,7 @@ public void bindUI() { super.dateCreatedLabel = dateCreatedLabel; super.checkBox = checkBox; super.expandButton = expandButton; + super.expandbuttonFocusPanel = expandbuttonFocusPanel; } @Override @@ -66,6 +70,12 @@ protected void toggleFolderSelection(ClickEvent e) { super.toggleFolderSelection(e); } + @SuppressWarnings("unused") + @Override + protected void toggleExpandedState(KeyDownEvent e) { + super.toggleExpandedState(e); + } + @SuppressWarnings("unused") @Override protected void toggleExpandedState(ClickEvent e) { diff --git a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.ui.xml index d353dddfdc6..e34a6494010 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectFolderNeo.ui.xml @@ -11,11 +11,13 @@ - - - - - + + + + + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListItemNeo.java b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListItemNeo.java index 7f19aced78d..dcdcc3c70db 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListItemNeo.java +++ b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListItemNeo.java @@ -9,11 +9,13 @@ import com.google.appinventor.client.explorer.youngandroid.ProjectListItem; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Label; public class ProjectListItemNeo extends ProjectListItem { @@ -28,6 +30,7 @@ interface ProjectListItemUiBinderNeo extends UiBinder - + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.java b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.java index a9d3db11e0c..6af3a9dcb54 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.java +++ b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.java @@ -15,6 +15,7 @@ import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.InlineLabel; public class ProjectListNeo extends ProjectList { @@ -29,6 +30,9 @@ interface ProjectListUiBinderNeo extends UiBinder {} @UiField InlineLabel createDateSortAsc; @UiField InlineLabel modDateSortDec; @UiField InlineLabel modDateSortAsc; + @UiField FocusPanel nameFocusPanel; + @UiField FocusPanel createdateFocusPanel; + @UiField FocusPanel modDateFocusPanel; @Override public void bindIU() { @@ -42,6 +46,9 @@ public void bindIU() { super.createDateSortDec = createDateSortDec; super.modDateSortAsc = modDateSortAsc; super.modDateSortDec = modDateSortDec; + super.nameFocusPanel = nameFocusPanel; + super.createdateFocusPanel = createdateFocusPanel; + super.modDateFocusPanel = modDateFocusPanel; Ode.getInstance().getFolderManager().addFolderManagerEventListener(this); // It is important to listen to project manager events as soon as possible. diff --git a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.ui.xml index 22926ab5803..c8dc07b320f 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/style/neo/ProjectListNeo.ui.xml @@ -11,30 +11,36 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/style/neo/SimpleVisibleComponentsPanelNeo.java b/appinventor/appengine/src/com/google/appinventor/client/style/neo/SimpleVisibleComponentsPanelNeo.java index d986d519975..f1a7663ac2e 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/style/neo/SimpleVisibleComponentsPanelNeo.java +++ b/appinventor/appengine/src/com/google/appinventor/client/style/neo/SimpleVisibleComponentsPanelNeo.java @@ -13,6 +13,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.CheckBox; import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.VerticalPanel; @@ -24,6 +25,7 @@ interface SimpleVisibleComponentsPanelUiBinderNeo extends UiBinder - + diff --git a/appinventor/appengine/src/com/google/appinventor/client/style/neo/TopPanelNeo.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/style/neo/TopPanelNeo.ui.xml index 6292eba6f16..9ffe84a883e 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/style/neo/TopPanelNeo.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/style/neo/TopPanelNeo.ui.xml @@ -54,6 +54,9 @@ + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/widgets/ContextMenu.java b/appinventor/appengine/src/com/google/appinventor/client/widgets/ContextMenu.java index 9e48c0ac313..9621f9d9b89 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/widgets/ContextMenu.java +++ b/appinventor/appengine/src/com/google/appinventor/client/widgets/ContextMenu.java @@ -142,4 +142,27 @@ public void setPopupPosition(int left, int top) { public void hide() { popupPanel.hide(); } + + /* Returns if the context menu is showing */ + public boolean isShowing() { + return popupPanel.isShowing(); + } + + /* Gives the menubar focus */ + public void focus() { + menuBar.focus(); + } + + public void moveSelectionDown() { + menuBar.moveSelectionDown(); + } + + public void moveSelectionUp() { + menuBar.moveSelectionUp(); + } + + public void resetSelection() { + menuBar.selectItem(null); + } + } diff --git a/appinventor/appengine/src/com/google/appinventor/client/widgets/DropDownButton.java b/appinventor/appengine/src/com/google/appinventor/client/widgets/DropDownButton.java index e3949c610a3..ba168779c75 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/widgets/DropDownButton.java +++ b/appinventor/appengine/src/com/google/appinventor/client/widgets/DropDownButton.java @@ -8,10 +8,15 @@ import com.google.appinventor.client.components.Icon; import com.google.appinventor.client.utils.PZAwarePositionCallback; import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +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.uibinder.client.ElementParserToUse; import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Event; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.MenuItem; import com.google.gwt.user.client.ui.MenuItemSeparator; @@ -111,7 +116,39 @@ public DropDownButton() { addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - menu.setPopupPositionAndShow(new DropDownPositionCallback(getElement())); + if (menu.isShowing()) { + menu.hide(); + } else { + menu.resetSelection(); + menu.setPopupPositionAndShow(new DropDownPositionCallback(getElement())); + } + } + }); + + addKeyDownHandler(new KeyDownHandler() { + @Override + public void onKeyDown(KeyDownEvent event) { + if (event.getNativeKeyCode() == KeyCodes.KEY_DOWN && menu.isShowing()) { + event.preventDefault(); + menu.moveSelectionDown(); + menu.focus(); + } else if (event.getNativeKeyCode() == KeyCodes.KEY_UP && menu.isShowing()) { + event.preventDefault(); + menu.moveSelectionUp(); + menu.focus(); + } + } + }); + + Event.addNativePreviewHandler(new Event.NativePreviewHandler() { + @Override + public void onPreviewNativeEvent(Event.NativePreviewEvent event) { + NativeEvent nativeEvent = event.getNativeEvent(); + if (event.getTypeInt() == Event.ONKEYDOWN && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB && menu.isShowing()) { + nativeEvent.preventDefault(); + menu.hide(); + setFocus(true); + } } }); } diff --git a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/AdditionalChoicePropertyEditor.java b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/AdditionalChoicePropertyEditor.java index 495467f2bdd..aa95b36c429 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/AdditionalChoicePropertyEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/AdditionalChoicePropertyEditor.java @@ -9,6 +9,9 @@ import static com.google.appinventor.client.Ode.MESSAGES; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +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.user.client.Window; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.HorizontalPanel; @@ -45,6 +48,14 @@ public void onClick(ClickEvent event) { openAdditionalChoiceDialog(); } }); + summary.addKeyDownHandler(new KeyDownHandler() { + @Override + public void onKeyDown(KeyDownEvent event) { + if(event.getNativeKeyCode() == KeyCodes.KEY_ENTER) { + openAdditionalChoiceDialog(); + } + } + }); initWidget(summary); } @@ -154,6 +165,7 @@ protected final void closeAdditionalChoiceDialog(boolean ok) { updateValue(); // Restore previous property value } popup.hide(); + summary.setFocus(true); } /** diff --git a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ChoicePropertyEditor.java b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ChoicePropertyEditor.java index a9fbbb22079..fd428b0befe 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ChoicePropertyEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ChoicePropertyEditor.java @@ -108,6 +108,7 @@ public void execute() { if (autoupdate) { updateValue(); } + dropDownButton.setFocus(true); } })); if (Objects.equals(choice.value, defaultValue)) { @@ -139,6 +140,7 @@ public void execute() { boolean multiple = isMultipleValues(); setMultipleValues(false); property.setValue(choice.value, multiple); + dropDownButton.setFocus(true); } })); } diff --git a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ColorChoicePropertyEditor.java b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ColorChoicePropertyEditor.java index 74663b00687..e63841b0a59 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ColorChoicePropertyEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/ColorChoicePropertyEditor.java @@ -189,6 +189,7 @@ public void execute() { color.alphaString + color.rgbString; selectedColorMenu.replaceLastItem(new DropDownItem(WIDGET_NAME, makeCustomHTML(customColor), showCustomPicker)); } + selectedColorMenu.setFocus(true); } })); } diff --git a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/SubsetJSONPropertyEditor.java b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/SubsetJSONPropertyEditor.java index 2c7a261f419..5a65ca8a662 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/SubsetJSONPropertyEditor.java +++ b/appinventor/appengine/src/com/google/appinventor/client/widgets/properties/SubsetJSONPropertyEditor.java @@ -120,6 +120,7 @@ public void onChange(ChangeEvent changeEvent) { public void execute() { property.setValue(BeginnerToolkit.INSTANCE.getToolkit().getText()); updateValue(); + dropDownButton.setFocus(true); }})); items.add(new DropDownItem("Subset Property Editor", MESSAGES.intermediateToolkitButton(), new Command() { @@ -127,6 +128,7 @@ public void execute() { public void execute() { property.setValue(IntermediateToolkit.INSTANCE.getToolkit().getText()); updateValue(); + dropDownButton.setFocus(true); }})); items.add(new DropDownItem("Subset Property Editor", MESSAGES.expertToolkitButton(), new Command() { @@ -134,6 +136,7 @@ public void execute() { public void execute() { property.setValue(""); updateValue(); + dropDownButton.setFocus(true); }})); if (!newProject) { items.add(new DropDownItem("Subset Property Editor", MESSAGES.matchProjectButton(), new Command() { @@ -142,6 +145,7 @@ public void execute() { matchProject(); property.setValue(createJSONString()); updateValue(); + dropDownButton.setFocus(true); } })); } @@ -158,6 +162,7 @@ public void execute() { @Override public void execute() { showCustomSubsetPanel(); + dropDownButton.setFocus(true); }})); dropDownButton = new DropDownButton("Subset Property Editor", "", items, false); diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/Dialog.java b/appinventor/appengine/src/com/google/appinventor/client/wizards/Dialog.java index ac75cd3667a..209c0d20be6 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/Dialog.java +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/Dialog.java @@ -19,6 +19,7 @@ public Dialog() { super(false, true, new CaptionImpl()); caption = getCaption(); setGlassEnabled(true); + setModal(false); } public void setCaption(String text) { diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.java b/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.java index a9695dad0b4..b8bdbee6f38 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.java +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.java @@ -23,6 +23,7 @@ import com.google.appinventor.shared.rpc.project.youngandroid.YoungAndroidAssetsFolder; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; @@ -56,6 +57,8 @@ interface FileUploadWizardUiBinder extends UiBinder {} @UiField FileUpload upload; @UiField Button okButton; @UiField Button cancelButton; + @UiField Button topInvisible; + @UiField Button bottomInvisible; private final FolderNode folderNode; @@ -119,6 +122,7 @@ public FileUploadWizard(final FolderNode folderNode, public void show() { uploadDialog.center(); + upload.setFocus(true); } @UiHandler("cancelButton") @@ -264,5 +268,15 @@ private void finishUpload(FolderNode folderNode, String filename, } } + @UiHandler("topInvisible") + protected void FocusLast(FocusEvent event) { + okButton.setFocus(true); + } + + @UiHandler("bottomInvisible") + protected void FocusFirst(FocusEvent event) { + upload.setFocus(true); + } + } diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.ui.xml index 478751dc401..2503c21c0ab 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/FileUploadWizard.ui.xml @@ -12,11 +12,13 @@ + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/MoveProjectsWizard.java b/appinventor/appengine/src/com/google/appinventor/client/wizards/MoveProjectsWizard.java index fec69b4b033..b4f739d9c0a 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/MoveProjectsWizard.java +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/MoveProjectsWizard.java @@ -13,7 +13,11 @@ import com.google.appinventor.client.explorer.project.Project; import com.google.appinventor.client.explorer.youngandroid.ProjectList; import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; @@ -36,7 +40,8 @@ interface MoveProjectsWizardUiBinder extends UiBinder + - + + + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.java b/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.java index 0ecb46868ea..d8222aa15fb 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.java +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.java @@ -8,7 +8,11 @@ import com.google.appinventor.client.widgets.LabeledTextBox; import com.google.appinventor.client.widgets.Validator; import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.BlurEvent; +import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; +import com.google.gwt.event.dom.client.FocusHandler; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; @@ -41,6 +45,8 @@ interface NewFolderWizardUiBinder extends UiBinder {} @UiField Button cancelButton; @UiField LabeledTextBox input; @UiField Tree tree; + @UiField Button topInvisible; + @UiField Button bottomInvisible; /** * Creates a new command for renaming projects @@ -90,6 +96,20 @@ public void onKeyUp(KeyUpEvent event) { //Validate the text each time a key is l input.validate(); } }); + + tree.addFocusHandler(new FocusHandler() { + @Override + public void onFocus(FocusEvent event) { + tree.getParent().setStyleName("gwt-Tree-focused"); + } + }); + + tree.addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + tree.getParent().removeStyleName("gwt-Tree-focused"); + } + }); } private FolderTreeItem renderFolder(ProjectFolder folder) { @@ -117,4 +137,14 @@ void addFolder(ClickEvent e) { } addDialog.hide(); } + + @UiHandler("topInvisible") + protected void FocusLast(FocusEvent event) { + addButton.setFocus(true); + } + + @UiHandler("bottomInvisible") + protected void FocusFirst(FocusEvent event) { + input.setFocus(true); + } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.ui.xml index 88f81c9e685..b50671945bd 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/NewFolderWizard.ui.xml @@ -13,16 +13,18 @@ + - + + diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.java b/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.java index e774d21debd..725015e3494 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.java +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.java @@ -8,6 +8,7 @@ import com.google.appinventor.client.Ode; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; @@ -32,6 +33,8 @@ interface UISettingsWizardUiBinder extends UiBinder {} // @UiField protected RadioButton darkModeRadioButton; @UiField protected RadioButton classicRadioButton; @UiField protected RadioButton modernRadioButton; + @UiField protected Button topInvisible; + @UiField protected Button bottomInvisible; Boolean userThemePreference; Boolean userLayoutPreference; @@ -62,6 +65,7 @@ public void bindUI() { public void show() { UIDialog.center(); + classicRadioButton.setFocus(true); } // @UiHandler("darkModeButton") @@ -115,4 +119,14 @@ protected void applySettings(ClickEvent e) { Ode.saveUserDesignSettings(); UIDialog.hide(); } + + @UiHandler("topInvisible") + protected void FocusLast(FocusEvent event) { + applyButton.setFocus(true); + } + + @UiHandler("bottomInvisible") + protected void FocusFirst(FocusEvent event) { + classicRadioButton.setFocus(true); + } } diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.ui.xml b/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.ui.xml index 400ba87373f..9cd59ad6769 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.ui.xml +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/UISettingsWizard.ui.xml @@ -11,6 +11,7 @@ + @@ -42,6 +43,7 @@ + diff --git a/appinventor/appengine/src/com/google/appinventor/client/wizards/youngandroid/NewYoungAndroidProjectWizard.java b/appinventor/appengine/src/com/google/appinventor/client/wizards/youngandroid/NewYoungAndroidProjectWizard.java index 3f44309c8f2..49d92821b82 100644 --- a/appinventor/appengine/src/com/google/appinventor/client/wizards/youngandroid/NewYoungAndroidProjectWizard.java +++ b/appinventor/appengine/src/com/google/appinventor/client/wizards/youngandroid/NewYoungAndroidProjectWizard.java @@ -17,6 +17,7 @@ import com.google.gwt.core.client.GWT; import com.google.appinventor.client.widgets.Validator; import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.FocusEvent; import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyDownEvent; import com.google.gwt.event.dom.client.KeyDownHandler; @@ -67,6 +68,8 @@ interface NewYoungAndroidProjectWizardUiBinder extends UiBinder + @@ -33,6 +34,7 @@ + diff --git a/appinventor/appengine/war/static/css/Ya.css b/appinventor/appengine/war/static/css/Ya.css index 5d679f7605c..19cbbc9fa64 100755 --- a/appinventor/appengine/war/static/css/Ya.css +++ b/appinventor/appengine/war/static/css/Ya.css @@ -328,13 +328,18 @@ div.StatusPanel { align-items: center; } +.ode-ProjectNameFocusPanel, +.ode-ExpandButtonFocusPanel, +.ode-NameFocusPanel { + flex-grow: 1; +} + .ode-ProjectNameLabel { font-weight: bold; font-size: small; color: #555; cursor: pointer; vertical-align: top; - padding-right: 10px; text-align: left; flex-grow: 1; min-width: 100px; @@ -614,9 +619,13 @@ div.StatusPanel { .ode-TopPanelButton:hover, .ActionDropDown-Default:hover, -.ActionDropDown-Options:hover { +.ActionDropDown-Options:hover, +.ode-TopPanelButton:focus, +.ActionDropDown-Default:focus, +.ActionDropDown-Options:focus { background: #E6E6E6; color: #5B5B5B; + outline: none; } .ode-TopPanelButton:active, @@ -834,6 +843,10 @@ div.StatusPanel { background-color: #d2e0a6; } +.gwt-Tree-focused { + outline: solid 2px #000000; +} + /* Young Android Toolbar */ .ya-Toolbar { @@ -1199,7 +1212,7 @@ div.StatusPanel { } .ode-ChoicePropertyEditor:focus { - border-color: #D3D3D3; + border-color: rgba(82, 168, 236, 0.8); outline: 0; outline: thin dotted \9; /* IE6-9 */ @@ -1230,13 +1243,14 @@ select.ode-PropertyEditor[disabled] { } .ode-ColorChoicePropertyEditor:focus { + border-color: rgba(82, 168, 236, 0.8); outline: 0; outline: thin dotted \9; /* IE6-9 */ - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0); - box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0); + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.8); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.8); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.07), 0 0 8px rgba(82, 168, 236, 0.8); } .ode-SimpleFormDesigner { @@ -1247,6 +1261,10 @@ select.ode-PropertyEditor[disabled] { font-size: 12px; } +.gwt-CheckBox input[type="checkbox"]:focus { + outline: 2px solid #333; +} + .ode-SimpleMockForm { height: auto !important; background-color: #d2e0a6; @@ -1618,6 +1636,19 @@ path.ode-SimpleMockMapFeature-selected { /* All dialog boxes */ +.FocusTrap { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + opacity: 0; + overflow: hidden; + border: none; + background: none; + padding: 0; + margin: 0; +} + /* Start of NoProjectsDialogBox. */ .ode-noDialogDiv { box-shadow: 0 0 10px gray; @@ -2161,6 +2192,7 @@ input[type="email"]:focus, input[type="url"]:focus, input[type="search"]:focus, input[type="tel"]:focus, +input[type="checkbox"]:focus, input[type="color"]:focus, .uneditable-input:focus { border-color: rgba(82, 168, 236, 0.8); diff --git a/appinventor/blocklyeditor/src/backpack.js b/appinventor/blocklyeditor/src/backpack.js index 938f9229a80..f96b804d00e 100644 --- a/appinventor/blocklyeditor/src/backpack.js +++ b/appinventor/blocklyeditor/src/backpack.js @@ -209,6 +209,9 @@ Blockly.Backpack.prototype.createDom = function(opt_workspace) { this.svgBody_.setAttribute('class', 'blocklybackpackImage'); + this.svgTooltip_ = Blockly.utils.createSvgElement('title', null , this.svgBody_); + this.svgTooltip_.textContent = 'Alt + B to Open/Close'; + return this.svgGroup_; }; diff --git a/appinventor/blocklyeditor/src/blocklyeditor.js b/appinventor/blocklyeditor/src/blocklyeditor.js index 71ebce09301..289870df0b3 100644 --- a/appinventor/blocklyeditor/src/blocklyeditor.js +++ b/appinventor/blocklyeditor/src/blocklyeditor.js @@ -589,6 +589,26 @@ Blockly.ai_inject = function(container, workspace) { return workspace; }; +function isBlockEditorActive(e) { + var target = null; + + if (e.target.tagName.toLowerCase() == 'div' && e.target.className == 'injectionDiv') { + target = e.target; + } else { + var parent = e.target; + while (parent.parentNode && !(parent.tagName.toLowerCase() == 'div' && parent.className == 'injectionDiv')) { + parent = parent.parentNode; + } + target = parent; + } + + if (Blockly.mainWorkspace.getParentSvg().parentNode == target) { + return true; + } + + return false; +} + // Preserve Blockly during Closure and GWT optimizations window['Blockly'] = Blockly; top['Blockly'] = Blockly; @@ -615,3 +635,46 @@ top.document.addEventListener('mousedown', function(e) { Blockly.hideChaff(); } }, false); + +top.document.addEventListener('keydown', function(e) { + var selectedBlock = Blockly.selected; + var allBlocks = Blockly.mainWorkspace.getAllBlocks(); + + if (e.altKey) { + e.preventDefault(); + if (e.key.toLowerCase() === 'o') { + selectedBlock.setCollapsed(!selectedBlock.isCollapsed()); + } else if (e.key.toLowerCase() === 'i') { + selectedBlock.setInputsInline(!selectedBlock.getInputsInline()); + } else if (e.key.toLowerCase() === 'w') { + if (allBlocks.length > 0) { + i = (i + 1) % allBlocks.length; + allBlocks[i].select(); + } + } else if (e.key.toLowerCase() === 's') { + if (allBlocks.length > 0) { + i = (i - 1 + allBlocks.length) % allBlocks.length; + allBlocks[i].select(); + } + } else if (e.key.toLowerCase() === 'k') { + if (!selectedBlock.isCollapsed()) { + if (selectedBlock.comment && selectedBlock.comment.isVisible()) { + selectedBlock.comment.setVisible(false); + } else { + selectedBlock.setCommentText(''); + selectedBlock.comment.setVisible(true); + } + } + } else if (e.key === '+') { + Blockly.getMainWorkspace().zoomCenter(1); + } else if (e.key === '-') { + Blockly.getMainWorkspace().zoomCenter(-1); + } else if (e.key.toLowerCase() === 'g') { + var mainWorkspace = Blockly.getMainWorkspace(); + mainWorkspace.setScale(mainWorkspace.options.zoomOptions.startScale); + mainWorkspace.scrollCenter(); + } else if (e.key.toLowerCase() === 'b' && isBlockEditorActive(e)) { + Blockly.getMainWorkspace().getBackpack().openBackpack(e); + } + } +}, false); \ No newline at end of file diff --git a/appinventor/blocklyeditor/src/msg/ai_blockly/messages.json b/appinventor/blocklyeditor/src/msg/ai_blockly/messages.json index 659900ce539..06ae6360665 100644 --- a/appinventor/blocklyeditor/src/msg/ai_blockly/messages.json +++ b/appinventor/blocklyeditor/src/msg/ai_blockly/messages.json @@ -11,10 +11,10 @@ "Blockly.Msg.HIDE": "Hide Workspace Controls", "Blockly.Msg.SHOW": "Show Workspace Controls", "Blockly.Msg.DUPLICATE_BLOCK": "Duplicate", - "Blockly.Msg.REMOVE_COMMENT": "Remove Comment", - "Blockly.Msg.ADD_COMMENT": "Add Comment", - "Blockly.Msg.EXTERNAL_INPUTS": "External Inputs", - "Blockly.Msg.INLINE_INPUTS": "Inline Inputs", + "Blockly.Msg.REMOVE_COMMENT": "Remove Comment (Alt + K)", + "Blockly.Msg.ADD_COMMENT": "Add Comment (Alt + K)", + "Blockly.Msg.EXTERNAL_INPUTS": "External Inputs (Alt + I)", + "Blockly.Msg.INLINE_INPUTS": "Inline Inputs (Alt + I)", "Blockly.Msg.HORIZONTAL_PARAMETERS": "Arrange Parameters Horizontally", "Blockly.Msg.VERTICAL_PARAMETERS": "Arrange Parameters Vertically", "Blockly.Msg.CONFIRM_DELETE": "Confirm deletion", @@ -22,8 +22,8 @@ "Blockly.Msg.DELETE_BLOCK": "Delete Block", "Blockly.Msg.DELETE_X_BLOCKS": "Delete %1 Blocks", "Blockly.Msg.COLLAPSE_BLOCK": "Collapse Block", - "Blockly.Msg.EXPAND_BLOCK": "Expand Block", - "Blockly.Msg.DISABLE_BLOCK": "Disable Block", + "Blockly.Msg.EXPAND_BLOCK": "Expand Block (Alt + O)", + "Blockly.Msg.DISABLE_BLOCK": "Disable Block (Alt + O)", "Blockly.Msg.ENABLE_BLOCK": "Enable Block", "Blockly.Msg.HELP": "Help", "Blockly.Msg.EXPORT_IMAGE": "Download Blocks as Image",