/*
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.ui;
import com.codename1.components.InteractionDialog;
import com.codename1.io.Log;
import com.codename1.ui.animations.BubbleTransition;
import com.codename1.ui.animations.CommonTransitions;
import com.codename1.ui.animations.Motion;
import com.codename1.ui.animations.Transition;
import com.codename1.ui.events.ActionEvent;
import com.codename1.ui.events.ActionListener;
import com.codename1.ui.events.ScrollListener;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.BoxLayout;
import com.codename1.ui.layouts.Layout;
import com.codename1.ui.list.DefaultListCellRenderer;
import com.codename1.ui.plaf.LookAndFeel;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
import java.util.ArrayList;
import java.util.Vector;
/**
* <p>Toolbar replaces the default title area with a powerful abstraction that allows functionality ranging
* from side menus (hamburger) to title animations and any arbitrary component type. Toolbar allows
* customizing the Form title with different commands on the title area, within the side menu or the overflow menu.</p>
*
* <p>
* The Toolbar allows placing components in one of 4 positions as illustrated by the sample below:
* </p>
* <script src="https://gist.github.com/codenameone/e72cfa6aedd7fcd1af72.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/components-toolbar.png" alt="Simple usage of Toolbar" />
*
* <p>
* {@code Toolbar} supports a search mode that implicitly replaces the title with a search field/magnifying glass
* effect. The code below demonstrates searching thru the contacts using this API:
* </p>
* <script src="https://gist.github.com/codenameone/cd227aaca486889f7c940e2e97985426.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/toolbar-search-mode.jpg" alt="Dynamic search mode in the Toolbar" />
*
* <p>
* The following code also demonstrates search with a more custom UX where the title
* area was replaced dynamically. This code predated the builtin search support above.
* Notice that the {@code TextField} and its hint are styled to look like the title.
* </p>
* <script src="https://gist.github.com/codenameone/dce6598a226aaf9a3157.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/components-toolbar-search.png" alt="Dynamic TextField search using the Toolbar" />
*
* <p>
* This sample code show off title animations that allow a title to change (and potentially shrink) as the user scrolls
* down the UI. The 3 frames below show a step by step process in the change.
* </p>
* <script src="https://gist.github.com/codenameone/085e3a8fa1c36829d812.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/components-toolbar-animation-1.png" alt="Toolbar animation stages" />
* <img src="https://www.codenameone.com/img/developer-guide/components-toolbar-animation-2.png" alt="Toolbar animation stages" />
* <img src="https://www.codenameone.com/img/developer-guide/components-toolbar-animation-3.png" alt="Toolbar animation stages" />
*
* @author Chen
*/
public class Toolbar extends Container {
/**
* Indicates whether the toolbar should be properly centered by default
* @return the centeredDefault
*/
public static boolean isCenteredDefault() {
return centeredDefault;
}
/**
* Indicates whether the toolbar should be properly centered by default
* @param aCenteredDefault the centeredDefault to set
*/
public static void setCenteredDefault(boolean aCenteredDefault) {
centeredDefault = aCenteredDefault;
}
/**
* Indicates if the side menu is in "on-top" mode
* @return the onTopSideMenu
*/
public static boolean isOnTopSideMenu() {
return onTopSideMenu;
}
/**
* Sets the side menu to "on-top" mode
* @param aOnTopSideMenu the onTopSideMenu to set
*/
public static void setOnTopSideMenu(boolean aOnTopSideMenu) {
onTopSideMenu = aOnTopSideMenu;
}
private Component titleComponent;
private ToolbarSideMenu sideMenu;
private Vector<Command> overflowCommands;
private Button menuButton;
private ScrollListener scrollListener;
private ActionListener releasedListener;
private boolean scrollOff = false;
private int initialY;
private int actualPaneInitialY;
private int actualPaneInitialH;
private Motion hideShowMotion;
private boolean showing;
private boolean layered = false;
private boolean initialized = false;
private static boolean permanentSideMenu;
/**
* Sets the side menu to "on-top" mode
*/
private static boolean onTopSideMenu;
private InteractionDialog sidemenuDialog;
private Container permanentSideMenuContainer;
private static boolean globalToolbar;
/**
* Indicates whether the toolbar should be properly centered by default
*/
private static boolean centeredDefault = true;
private Command searchCommand;
/**
* Empty Constructor
*/
public Toolbar() {
setLayout(new BorderLayout());
setUIID("Toolbar");
sideMenu = new ToolbarSideMenu();
if(centeredDefault && getUnselectedStyle().getAlignment() == CENTER) {
setTitleCentered(true);
}
}
/**
* Enables/disables the Toolbar for all the forms in the application. This flag can be flipped via the
* theme constant {@code globalToobarBool}. Notice that the name of this method might imply that
* one toolbar instance will be used for all forms which isn't the case, separate instances will be used for each form
*
* @param gt true to enable the toolbar globally
*/
public static void setGlobalToolbar(boolean gt) {
globalToolbar = gt;
}
/**
* Enables/disables the Toolbar for all the forms in the application. This flag can be flipped via the
* theme constant {@code globalToobarBool}. Notice that the name of this method might imply that
* one toolbar instance will be used for all forms which isn't the case, separate instances will be used for each form
*
* @return true if the toolbar API is turned on by default
*/
public static boolean isGlobalToolbar() {
return globalToolbar;
}
/**
* This constructor places the Toolbar on a different layer on top of the
* Content Pane.
*
* @param layered if true places the Toolbar on top of the Content Pane
*/
public Toolbar(boolean layered) {
this();
this.layered = layered;
}
/**
* Sets the title of the Toolbar.
*
* @param title the Toolbar title
*/
public void setTitle(String title) {
checkIfInitialized();
Component center = ((BorderLayout) getLayout()).getCenter();
if (center instanceof Label) {
((Label) center).setText(title);
} else {
titleComponent = new Label(title);
titleComponent.setUIID("Title");
if (center != null) {
replace(center, titleComponent, null);
} else {
addComponent(BorderLayout.CENTER, titleComponent);
}
}
}
/**
* Makes the title align to the center accurately by doing it at the layout level which also takes into
* account right/left commands
* @param cent whether the title should be centered
*/
public void setTitleCentered(boolean cent) {
if(cent) {
((BorderLayout)getLayout()).setCenterBehavior(BorderLayout.CENTER_BEHAVIOR_CENTER_ABSOLUTE);
} else {
((BorderLayout)getLayout()).setCenterBehavior(BorderLayout.CENTER_BEHAVIOR_SCALE);
}
}
/**
* Returns true if the title is centered via the layout
* @return true if the title is centered
*/
public boolean isTitleCentered() {
return ((BorderLayout)getLayout()).getCenterBehavior() == BorderLayout.CENTER_BEHAVIOR_CENTER_ABSOLUTE;
}
/**
* Creates a static side menu that doesn't fold instead of the standard sidemenu.
* This is common for tablet UI's where folding the side menu doesn't make as much sense.
*
* @param p true to have a permanent side menu
*/
public static void setPermanentSideMenu(boolean p) {
permanentSideMenu = p;
}
/**
* Creates a static side menu that doesn't fold instead of the standard sidemenu.
* This is common for tablet UI's where folding the side menu doesn't make as much sense.
*
* @return true if we will use a permanent sidemenu
*/
public static boolean isPermanentSideMenu() {
return permanentSideMenu;
}
/**
* This is a convenience method to open the side menu bar. It's useful for cases where we want to place the
* menu button in a "creative way" in which case we can bind the side menu to this
*/
public void openSideMenu() {
if(onTopSideMenu) {
showOnTopSidemenu(-1, false);
} else {
((SideMenuBar)getMenuBar()).openMenu(null);
}
}
/**
* Closes the current side menu
*/
public void closeSideMenu() {
if(onTopSideMenu) {
if(sidemenuDialog.isShowing()) {
sidemenuDialog.disposeToTheLeft();
Style s = getComponentForm().getLayeredPane(Toolbar.class, false).getUnselectedStyle();
s.setBgTransparency(0);
}
} else {
SideMenuBar.closeCurrentMenu();
}
}
/**
* Sets the Toolbar title component. This method allow placing any component
* in the Toolbar center instead of the regular Label. Can be used to place
* a TextField to preform search operations
*
* @param titleCmp Component to place in the Toolbar center.
*/
public void setTitleComponent(Component titleCmp) {
checkIfInitialized();
if(titleComponent != null) {
titleComponent.remove();
}
titleComponent = titleCmp;
addComponent(BorderLayout.CENTER, titleComponent);
}
/**
* Returns the Toolbar title Component.
*
* @return the Toolbar title component
*/
public Component getTitleComponent(){
return titleComponent;
}
/**
* Adds a Command to the overflow menu
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addCommandToOverflowMenu(String name, Image icon, final ActionListener ev) {
Command cmd = Command.create(name, icon, ev);
addCommandToOverflowMenu(cmd);
return cmd;
}
/**
* The behavior of the back command in the title
*/
public static enum BackCommandPolicy {
/**
* Show the back command always within the title bar on the left hand side
*/
ALWAYS,
/**
* Show the back command always but shows it with the UIID standard UIID
*/
AS_REGULAR_COMMAND,
/**
* Show the back command always as a back arrow image from the material design style
*/
AS_ARROW,
/**
* Shows the back command only if the {@code backUsesTitleBool} theme constant is defined to true which
* is the case for iOS themes
*/
ONLY_WHEN_USES_TITLE,
/**
* Shows the back command only if the {@code backUsesTitleBool} theme constant is defined to true
* on other platforms uses the left arrow material icon
*/
WHEN_USES_TITLE_OTHERWISE_ARROW,
/**
* Never show the command in the title area and only set the back command to the toolbar
*/
NEVER
}
/**
* Sets the back command in the title bar to an arrow type and maps the back command hardware key
* if applicable. This is functionally identical to {@code setBackCommand(title, Toolbar.BackCommandPolicy.AS_ARROW, listener); }
*
* @param title command title
* @param listener action event for the back command
* @return the created command
*/
public Command setBackCommand(String title, ActionListener<ActionEvent> listener) {
Command cmd = Command.create(title, null, listener);
setBackCommand(cmd, BackCommandPolicy.AS_ARROW);
return cmd;
}
/**
* Sets the back command in the title bar to an arrow type and maps the back command hardware key
* if applicable. This is functionally identical to {@code setBackCommand(cmd, Toolbar.BackCommandPolicy.AS_ARROW); }
*
* @param cmd the command
*/
public void setBackCommand(Command cmd) {
setBackCommand(cmd, BackCommandPolicy.AS_ARROW);
}
/**
* Sets the back command in the title bar and in the form, back command behaves based on the given
* policy type
*
* @param title command title
* @param policy the behavior of the back command in the title
* @param listener action event for the back command
* @return the created command
*/
public Command setBackCommand(String title, BackCommandPolicy policy, ActionListener<ActionEvent> listener) {
Command cmd = Command.create(title, null, listener);
setBackCommand(cmd, policy);
return cmd;
}
/**
* Sets the back command in the title bar and in the form, back command behaves based on the given
* policy type
*
* @param cmd the command
* @param policy the behavior of the back command in the title
* @param iconSize the size of the back command icon in millimeters
*/
public void setBackCommand(Command cmd, BackCommandPolicy policy, float iconSize) {
if(iconSize < 0) {
iconSize = 3;
}
getComponentForm().setBackCommand(cmd);
switch(policy) {
case ALWAYS:
cmd.putClientProperty("uiid", "BackCommand");
addCommandToLeftBar(cmd);
break;
case WHEN_USES_TITLE_OTHERWISE_ARROW:
cmd.putClientProperty("uiid", "BackCommand");
if(getUIManager().isThemeConstant("backUsesTitleBool", false)) {
addCommandToLeftBar(cmd);
break;
}
// we now internally fallback to as arrow...
case AS_ARROW:
cmd.setCommandName("");
cmd.setIcon(FontImage.createMaterial(FontImage.MATERIAL_ARROW_BACK, "TitleCommand", iconSize));
addCommandToLeftBar(cmd);
break;
case AS_REGULAR_COMMAND:
addCommandToLeftBar(cmd);
break;
case ONLY_WHEN_USES_TITLE:
if(getUIManager().isThemeConstant("backUsesTitleBool", false)) {
cmd.putClientProperty("uiid", "BackCommand");
addCommandToLeftBar(cmd);
}
break;
case NEVER:
break;
}
}
/**
* Sets the back command in the title bar and in the form, back command behaves based on the given
* policy type
*
* @param cmd the command
* @param policy the behavior of the back command in the title
*/
public void setBackCommand(Command cmd, BackCommandPolicy policy) {
setBackCommand(cmd, policy, -1);
}
/**
* <p>This method add a search Command on the right bar of the {@code Toolbar}.
* When the search Command is invoked the current {@code Toolbar} is replaced with
* a search {@code Toolbar} to perform a search on the Current Form.</p>
* <p>The callback ActionListener gets the search string and it's up to developer
* to do the actual filtering on the Form.</>
* <p>It is possible to customize the default look of the search {@code Toolbar} with the following
* uiid's: {@code ToolbarSearch}, {@code TextFieldSearch} & {@code TextHintSearch}.</>
* <script src="https://gist.github.com/codenameone/cd227aaca486889f7c940e2e97985426.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/toolbar-search-mode.jpg" alt="Dynamic search mode in the Toolbar" />
*
* @param callback gets the search string callbacks
* @param iconSize indicates the size of the icons used in the search/back in millimeters
*/
public void addSearchCommand(final ActionListener callback, final float iconSize){
searchCommand = new Command(""){
@Override
public void actionPerformed(ActionEvent evt) {
SearchBar s = new SearchBar(Toolbar.this, iconSize){
@Override
public void onSearch(String text) {
callback.actionPerformed(new ActionEvent(text));
}
};
Form f = (Form)Toolbar.this.getComponentForm();
setHidden(true);
f.removeComponentFromForm(Toolbar.this);
f.setToolbar(s);
s.initSearchBar();
f.animateLayout(100);
}
};
Image img;
if(iconSize > 0) {
img = FontImage.createMaterial(FontImage.MATERIAL_SEARCH, UIManager.getInstance().getComponentStyle("TitleCommand"), iconSize);
} else {
img = FontImage.createMaterial(FontImage.MATERIAL_SEARCH, UIManager.getInstance().getComponentStyle("TitleCommand"));
}
searchCommand.setIcon(img);
addCommandToRightBar(searchCommand);
}
/**
* Removes a previously installed search command
*/
public void removeSearchCommand() {
if(searchCommand != null) {
sideMenu.removeCommand(searchCommand);
searchCommand = null;
}
}
/**
* <p>This method add a search Command on the right bar of the {@code Toolbar}.
* When the search Command is invoked the current {@code Toolbar} is replaced with
* a search {@code Toolbar} to perform a search on the Current Form.</p>
* <p>The callback ActionListener gets the search string and it's up to developer
* to do the actual filtering on the Form.</>
* <p>It is possible to customize the default look of the search {@code Toolbar} with the following
* uiid's: {@code ToolbarSearch}, {@code TextFieldSearch} & {@code TextHintSearch}.</>
* <script src="https://gist.github.com/codenameone/cd227aaca486889f7c940e2e97985426.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/toolbar-search-mode.jpg" alt="Dynamic search mode in the Toolbar" />
*
* @param callback gets the search string callbacks
*/
public void addSearchCommand(final ActionListener callback){
addSearchCommand(callback, -1);
}
/**
* Adds a Command to the overflow menu
*
* @param cmd a Command
*/
public void addCommandToOverflowMenu(Command cmd) {
checkIfInitialized();
if (overflowCommands == null) {
overflowCommands = new Vector<Command>();
}
overflowCommands.add(cmd);
sideMenu.installRightCommands();
}
/**
* Returns the commands within the overflow menu which can be useful for things like unit testing. Notice
* that you should not mutate the commands or the iteratable set in any way!
* @return the commands in the overflow menu
*/
public Iterable<Command> getOverflowCommands() {
return overflowCommands;
}
/**
* Adds a Command to the side navigation menu
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addCommandToSideMenu(String name, Image icon, final ActionListener ev) {
Command cmd = Command.create(name, icon, ev);
addCommandToSideMenu(cmd);
return cmd;
}
/**
* Adds a Command to the side navigation menu with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToSideMenu(String name, char icon, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, "SideCommand");
addCommandToSideMenu(cmd);
return cmd;
}
/**
* Adds a Command to the side navigation menu with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param size size in millimeters for the icon
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToSideMenu(String name, char icon, float size, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, size, "SideCommand");
addCommandToSideMenu(cmd);
return cmd;
}
/**
* Adds a Command to the TitleArea on the right side with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToRightBar(String name, char icon, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, "TitleCommand");
addCommandToRightBar(cmd);
return cmd;
}
/**
* Adds a Command to the TitleArea on the right side with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param size size of the icon in millimeters
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToRightBar(String name, char icon, float size, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, size, "TitleCommand");
addCommandToRightBar(cmd);
return cmd;
}
private void setCommandMaterialIcon(Command cmd, char icon, String defaultUIID) {
String uiid = (String)cmd.getClientProperty("uiid");
if(uiid != null) {
FontImage.setMaterialIcon(cmd, icon, uiid);
} else {
FontImage.setMaterialIcon(cmd, icon, defaultUIID);
}
}
private void setCommandMaterialIcon(Command cmd, char icon, float size, String defaultUIID) {
String uiid = (String)cmd.getClientProperty("uiid");
if(uiid != null) {
FontImage.setMaterialIcon(cmd, icon, uiid, size);
} else {
FontImage.setMaterialIcon(cmd, icon, defaultUIID, size);
}
}
/**
* Adds a Command to the TitleArea on the left side with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToLeftBar(String name, char icon, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, "TitleCommand");
addCommandToLeftBar(cmd);
return cmd;
}
/**
* Adds a Command to the TitleArea on the left side with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param size size in millimeters for the icon
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToLeftBar(String name, char icon, float size, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, size, "TitleCommand");
addCommandToLeftBar(cmd);
return cmd;
}
/**
* Adds a Command to the overflow menu with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToOverflowMenu(String name, char icon, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, "Command");
addCommandToOverflowMenu(cmd);
return cmd;
}
/**
* Adds a Command to the overflow menu with a material design icon reference
* {@link com.codename1.ui.FontImage}.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param size size in millimeters for the icon
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addMaterialCommandToOverflowMenu(String name, char icon, float size, final ActionListener ev) {
Command cmd = Command.create(name, null, ev);
setCommandMaterialIcon(cmd, icon, size, "Command");
addCommandToOverflowMenu(cmd);
return cmd;
}
/**
* Adds a Command to the side navigation menu
*
* @param cmd a Command
*/
public void addCommandToSideMenu(Command cmd) {
checkIfInitialized();
if(permanentSideMenu) {
constructPermanentSideMenu();
Button b = new Button(cmd);
b.setEndsWith3Points(false);
Integer gap = (Integer)cmd.getClientProperty("iconGap");
if(gap != null) {
b.setGap(gap.intValue());
}
b.setTextPosition(Label.RIGHT);
String uiid = (String)cmd.getClientProperty("uiid");
if(uiid != null) {
b.setUIID(uiid);
} else {
b.setUIID("SideCommand");
}
addComponentToSideMenu(permanentSideMenuContainer, b);
} else {
if(onTopSideMenu) {
constructOnTopSideMenu();
Button b = new Button(sideMenu.wrapCommand(cmd));
b.setEndsWith3Points(false);
Integer gap = (Integer)cmd.getClientProperty("iconGap");
if(gap != null) {
b.setGap(gap.intValue());
}
b.setTextPosition(Label.RIGHT);
String uiid = (String)cmd.getClientProperty("uiid");
if(uiid != null) {
b.setUIID(uiid);
} else {
b.setUIID("SideCommand");
}
addComponentToSideMenu(permanentSideMenuContainer, b);
} else {
sideMenu.addCommand(cmd);
sideMenu.installMenuBar();
}
}
}
private void constructPermanentSideMenu() {
if(permanentSideMenuContainer == null) {
permanentSideMenuContainer = constructSideNavigationComponent();
Form parent = getComponentForm();
parent.addComponentToForm(BorderLayout.WEST, permanentSideMenuContainer);
}
}
private void constructOnTopSideMenu() {
if(sidemenuDialog == null) {
permanentSideMenuContainer = constructSideNavigationComponent();
final Form parent = getComponentForm();
parent.addPointerPressedListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if(sidemenuDialog.isShowing()) {
if(evt.getX() > sidemenuDialog.getWidth()) {
closeSideMenu();
parent.putClientProperty("cn1$ignorRelease", Boolean.TRUE);
evt.consume();
} else {
parent.putClientProperty("cn1$sidemenuCharged", Boolean.TRUE);
}
} else {
int displayWidth = Display.getInstance().getDisplayWidth();
final int sensitiveSection = displayWidth / getUIManager().getThemeConstant("sideSwipeSensitiveInt", 10);
if(evt.getX() < sensitiveSection) {
parent.putClientProperty("cn1$sidemenuCharged", Boolean.TRUE);
evt.consume();
} else {
parent.putClientProperty("cn1$sidemenuCharged", Boolean.FALSE);
}
}
}
});
parent.addPointerDraggedListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Boolean b = (Boolean)parent.getClientProperty("cn1$sidemenuCharged");
if(b != null && b.booleanValue()) {
parent.putClientProperty("cn1$sidemenuActivated", Boolean.TRUE);
showOnTopSidemenu(evt.getX(), false);
evt.consume();
}
}
});
parent.addPointerReleasedListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
Boolean ir = (Boolean)parent.getClientProperty("cn1$ignorRelease");
if(ir != null && ir.booleanValue()) {
parent.putClientProperty("cn1$ignorRelease", null);
evt.consume();
return;
}
Boolean b = (Boolean)parent.getClientProperty("cn1$sidemenuActivated");
if(b != null && b.booleanValue()) {
parent.putClientProperty("cn1$sidemenuActivated", null);
if(evt.getX() < parent.getWidth() / 4) {
closeSideMenu();
} else {
showOnTopSidemenu(-1, true);
}
evt.consume();
}
}
});
sidemenuDialog = new InteractionDialog(new BorderLayout());
// change this to true when stable
sidemenuDialog.setFormMode(false);
sidemenuDialog.setUIID("Container");
sidemenuDialog.setDialogUIID("Container");
sidemenuDialog.add(BorderLayout.CENTER, permanentSideMenuContainer);
float size = 4.5f;
try {
size = Float.parseFloat(getUIManager().getThemeConstant("menuImageSize", "4.5"));
} catch(Throwable t) {
Log.e(t);
}
addMaterialCommandToLeftBar("", FontImage.MATERIAL_MENU, size, new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if(sidemenuDialog.isShowing()) {
closeSideMenu();
return;
}
showOnTopSidemenu(-1, false);
}
});
}
}
void showOnTopSidemenu(int draggedX, boolean fromCurrent) {
int v = 0;
int dw = Display.getInstance().getDisplayWidth();
if (Display.getInstance().isPortrait()) {
if (Display.getInstance().isTablet()) {
v = getUIManager().getThemeConstant("sideMenuSizeTabPortraitInt", -1);
if(v < 0) {
v = dw * 2 / 3;
} else {
v = dw / 100 * v;
}
} else {
v = getUIManager().getThemeConstant("sideMenuSizePortraitInt", -1);
if(v < 0) {
v = dw - Display.getInstance().convertToPixels(10);
} else {
v = dw / 100 * v;
}
}
} else {
if (Display.getInstance().isTablet()) {
v = getUIManager().getThemeConstant("sideMenuSizeTabLandscapeInt", -1);
if(v < 0) {
v = dw * 3 / 4;
} else {
v = dw / 100 * v;
}
} else {
v = getUIManager().getThemeConstant("sideMenuSizeLandscapeInt", -1);
if(v < 0) {
v = dw * 4 / 10;
} else {
v = dw / 100 * v;
}
}
}
if(draggedX > 0) {
v = Math.min(v, draggedX);
sidemenuDialog.setAnimateShow(false);
sidemenuDialog.dispose();
} else {
sidemenuDialog.setAnimateShow(true);
}
// workaround for layout issue on first show
if(sidemenuDialog.getClientProperty("cn1$firstShow") == null) {
sidemenuDialog.setAnimateShow(false);
sidemenuDialog.setVisible(false);
sidemenuDialog.show(0, 0, 0, dw - v);
sidemenuDialog.disposeToTheLeft();
sidemenuDialog.setVisible(true);
sidemenuDialog.putClientProperty("cn1$firstShow", Boolean.TRUE);
sidemenuDialog.setAnimateShow(draggedX < 1);
}
sidemenuDialog.setHeight(Display.getInstance().getDisplayHeight());
sidemenuDialog.setWidth(v);
if(!fromCurrent) {
sidemenuDialog.setX(-v);
}
sidemenuDialog.setRepositionAnimation(false);
sidemenuDialog.layoutContainer();
float f = ((float)v) / ((float)dw) * 200.0f;
Style s = getComponentForm().getLayeredPane(Toolbar.class, false).getUnselectedStyle();
s.setBgTransparency((int)f);
s.setBgColor(0);
sidemenuDialog.show(0, 0, 0, dw - v);
}
/**
* Adds a Component to the side navigation menu. The Component is added to
* the navigation menu and the command gets the events once the Component is
* being pressed.
*
* @param cmp c Component to be added to the menu
* @param cmd a Command to handle the events
*/
public void addComponentToSideMenu(Component cmp, Command cmd) {
checkIfInitialized();
if(permanentSideMenu) {
constructPermanentSideMenu();
Container cnt = new Container(new BorderLayout());
cnt.addComponent(BorderLayout.CENTER, cmp);
Button btn = new Button(cmd);
btn.setParent(cnt);
cnt.setLeadComponent(btn);
addComponentToSideMenu(permanentSideMenuContainer, cnt);
} else {
if(onTopSideMenu) {
constructOnTopSideMenu();
Container cnt = new Container(new BorderLayout());
cnt.addComponent(BorderLayout.CENTER, cmp);
Button btn = new Button(cmd);
btn.setParent(cnt);
cnt.setLeadComponent(btn);
addComponentToSideMenu(permanentSideMenuContainer, cnt);
} else {
cmd.putClientProperty(SideMenuBar.COMMAND_SIDE_COMPONENT, cmp);
cmd.putClientProperty(SideMenuBar.COMMAND_ACTIONABLE, Boolean.TRUE);
sideMenu.addCommand(cmd);
sideMenu.installMenuBar();
}
}
}
/**
* Adds a Component to the side navigation menu.
*
* @param cmp c Component to be added to the menu
*/
public void addComponentToSideMenu(Component cmp) {
checkIfInitialized();
if(permanentSideMenu) {
constructPermanentSideMenu();
addComponentToSideMenu(permanentSideMenuContainer, cmp);
} else {
if(onTopSideMenu) {
constructOnTopSideMenu();
addComponentToSideMenu(permanentSideMenuContainer, cmp);
} else {
Command cmd = new Command("");
cmd.putClientProperty(SideMenuBar.COMMAND_SIDE_COMPONENT, cmp);
cmd.putClientProperty(SideMenuBar.COMMAND_ACTIONABLE, Boolean.FALSE);
sideMenu.addCommand(cmd);
sideMenu.installMenuBar();
}
}
}
/**
* Find the command component instance if such an instance exists
* @param c the command instance
* @return the button instance
*/
public Button findCommandComponent(Command c) {
if(permanentSideMenu || onTopSideMenu) {
Button b = findCommandComponent(c, permanentSideMenuContainer);
if(b != null) {
return b;
}
}
Button b = sideMenu.findCommandComponent(c);
if(b != null) {
return b;
}
return findCommandComponent(c, this);
}
private Button findCommandComponent(Command c, Container cnt) {
int count = cnt.getComponentCount();
for (int iter = 0; iter < count; iter++) {
Component current = cnt.getComponentAt(iter);
if (current instanceof Button) {
Button b = (Button) current;
if (b.getCommand() == c) {
return b;
}
} else {
if (current instanceof Container) {
Button b = findCommandComponent(c, (Container) current);
if(b != null) {
return b;
}
}
}
}
return null;
}
/**
* Adds a Command to the TitleArea on the right side.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addCommandToRightBar(String name, Image icon, final ActionListener ev) {
Command cmd = Command.create(name, icon, ev);
addCommandToRightBar(cmd);
return cmd;
}
/**
* Adds a Command to the TitleArea on the right side.
*
* @param cmd a Command
*/
public void addCommandToRightBar(Command cmd) {
checkIfInitialized();
cmd.putClientProperty("TitleCommand", Boolean.TRUE);
sideMenu.addCommand(cmd, 0);
}
/**
* Adds a Command to the TitleArea on the left side.
*
* @param name the name/title of the command
* @param icon the icon for the command
* @param ev the even handler
* @return a newly created Command instance
*/
public Command addCommandToLeftBar(String name, Image icon, final ActionListener ev) {
Command cmd = Command.create(name, icon, ev);
addCommandToLeftBar(cmd);
return cmd;
}
/**
* Adds a Command to the TitleArea on the left side.
*
* @param cmd a Command
*/
public void addCommandToLeftBar(Command cmd) {
checkIfInitialized();
cmd.putClientProperty("TitleCommand", Boolean.TRUE);
cmd.putClientProperty("Left", Boolean.TRUE);
sideMenu.addCommand(cmd, 0);
}
/**
* Returns the commands within the right bar section which can be useful for things like unit testing. Notice
* that you should not mutate the commands or the iteratable set in any way!
* @return the commands in the overflow menu
*/
public Iterable<Command> getRightBarCommands() {
return getBarCommands(null);
}
/**
* Returns the commands within the left bar section which can be useful for things like unit testing. Notice
* that you should not mutate the commands or the iteratable set in any way!
* @return the commands in the overflow menu
*/
public Iterable<Command> getLeftBarCommands() {
return getBarCommands(Boolean.TRUE);
}
private Iterable<Command> getBarCommands(Object leftValue) {
ArrayList<Command> cmds = new ArrayList<Command>();
findAllCommands(this, cmds);
int commandCount = cmds.size() - 1;
while(commandCount > 0) {
Command c = cmds.get(commandCount);
if(c.getClientProperty("Left") != leftValue) {
cmds.remove(commandCount);
}
commandCount--;
}
return cmds;
}
private void findAllCommands(Container cnt, ArrayList<Command> cmds) {
for(Component c : cnt) {
if(c instanceof Container) {
findAllCommands((Container)c, cmds);
continue;
}
if(c instanceof Button) {
cmds.add(((Button)c).getCommand());
}
}
}
/**
* Returns the associated SideMenuBar object of this Toolbar.
*
* @return the associated SideMenuBar object
*/
public MenuBar getMenuBar() {
return sideMenu;
}
/*
* A Overflow Menu is implemented as a dialog, this method allows you to
* override the dialog display in order to customize the dialog menu in
* various ways
*
* @param menu a dialog containing Overflow Menu options that can be
* customized
* @return the command selected by the user in the dialog
*/
protected Command showOverflowMenu(Dialog menu) {
Form parent = sideMenu.getParentForm();
int height;
int marginLeft;
int marginRight = 0;
Container dialogContentPane = menu.getDialogComponent();
marginLeft = parent.getWidth() - (dialogContentPane.getPreferredW()
+ menu.getStyle().getHorizontalPadding());
marginLeft = Math.max(0, marginLeft);
if (parent.getSoftButtonCount() > 1) {
height = parent.getHeight() - parent.getSoftButton(0).getParent().getPreferredH() - dialogContentPane.getPreferredH();
} else {
height = parent.getHeight() - dialogContentPane.getPreferredH();
}
height = Math.max(0, height);
int th = getHeight();
Transition transitionIn;
Transition transitionOut;
UIManager manager = parent.getUIManager();
LookAndFeel lf = manager.getLookAndFeel();
if (lf.getDefaultMenuTransitionIn() != null || lf.getDefaultMenuTransitionOut() != null) {
transitionIn = lf.getDefaultMenuTransitionIn();
if(transitionIn instanceof BubbleTransition){
((BubbleTransition)transitionIn).setComponentName("OverflowButton");
}
transitionOut = lf.getDefaultMenuTransitionOut();
} else {
transitionIn = CommonTransitions.createEmpty();
transitionOut = CommonTransitions.createEmpty();
}
menu.setTransitionInAnimator(transitionIn);
menu.setTransitionOutAnimator(transitionOut);
if(isRTL()){
marginRight = marginLeft;
marginLeft = 0;
}
int tint = parent.getTintColor();
parent.setTintColor(0x00FFFFFF);
parent.tint = false;
boolean showBelowTitle = manager.isThemeConstant("showMenuBelowTitleBool", true);
int topPadding = 0;
Component statusBar = ((BorderLayout) getLayout()).getNorth();
if (statusBar != null) {
topPadding = statusBar.getAbsoluteY() + statusBar.getHeight();
}
if(showBelowTitle){
topPadding = th;
}
Command r = menu.show(topPadding, Math.max(topPadding, height - topPadding), marginLeft, marginRight, true);
parent.setTintColor(tint);
return r;
}
/**
* Creates the list component containing the commands within the given
* vector used for showing the menu dialog
*
* @param commands list of command objects
* @return List object
*/
protected List createOverflowCommandList(Vector commands) {
List l = new List(commands);
l.setUIID("CommandList");
Component c = (Component) l.getRenderer();
c.setUIID("Command");
c = l.getRenderer().getListFocusComponent(l);
c.setUIID("CommandFocus");
l.setFixedSelection(List.FIXED_NONE_CYCLIC);
((DefaultListCellRenderer)l.getRenderer()).setShowNumbers(false);
return l;
}
/**
* Adds a status bar space to the north of the Component, subclasses can
* override this default behavior.
*/
protected void initTitleBarStatus() {
if (getUIManager().isThemeConstant("paintsTitleBarBool", false)) {
// check if its already added:
if (((BorderLayout) getLayout()).getNorth() == null) {
Container bar = new Container();
bar.setUIID("StatusBar");
addComponent(BorderLayout.NORTH, bar);
}
}
}
private void checkIfInitialized() {
if (!initialized) {
throw new IllegalStateException("Need to call "
+ "Form#setToolBar(Toolbar toolbar) before calling this method");
}
}
/**
* Sets the Toolbar to scroll off the screen upon content scroll. This
* feature can only work if the Form contentPane is scrollableY
*
* @param scrollOff if true the Toolbar needs to scroll off the screen when
* the Form ContentPane is scrolled
*/
public void setScrollOffUponContentPane(boolean scrollOff) {
if (initialized && !this.scrollOff && scrollOff) {
bindScrollListener(true);
}
this.scrollOff = scrollOff;
}
/**
* Hide the Toolbar if it is currently showing
*/
public void hideToolbar() {
showing = false;
if (actualPaneInitialH == 0) {
Form f = getComponentForm();
if(f != null){
initVars(f.getActualPane());
}
}
hideShowMotion = Motion.createSplineMotion(getY(), -getHeight(), 300);
getComponentForm().registerAnimated(this);
hideShowMotion.start();
}
/**
* Show the Toolbar if it is currently not showing
*/
public void showToolbar() {
showing = true;
hideShowMotion = Motion.createSplineMotion(getY(), initialY, 300);
getComponentForm().registerAnimated(this);
hideShowMotion.start();
}
public boolean animate() {
if (hideShowMotion != null) {
Form f = getComponentForm();
final Container actualPane = f.getActualPane();
int val = hideShowMotion.getValue();
setY(val);
if(!layered){
actualPane.setY(actualPaneInitialY + val);
if (showing) {
actualPane.setHeight(actualPaneInitialH + getHeight() - val);
} else {
actualPane.setHeight(actualPaneInitialH - val);
}
actualPane.doLayout();
}
f.repaint();
boolean finished = hideShowMotion.isFinished();
if (finished) {
f.deregisterAnimated(this);
hideShowMotion = null;
}
return !finished;
}
return false;
}
private void initVars(Container actualPane) {
initialY = getY();
actualPaneInitialY = actualPane.getY();
actualPaneInitialH = actualPane.getHeight();
}
private void bindScrollListener(boolean bind) {
final Form f = getComponentForm();
if (f != null) {
final Container actualPane = f.getActualPane();
final Container contentPane = f.getContentPane();
if (bind) {
initVars(actualPane);
scrollListener = new ScrollListener() {
public void scrollChanged(int scrollX, int scrollY, int oldscrollX, int oldscrollY) {
int diff = scrollY - oldscrollY;
int toolbarNewY = getY() - diff;
if (scrollY < 0 || Math.abs(toolbarNewY) < 2) {
return;
}
toolbarNewY = Math.max(toolbarNewY, -getHeight());
toolbarNewY = Math.min(toolbarNewY, initialY);
if (toolbarNewY != getY()) {
setY(toolbarNewY);
if(!layered){
actualPane.setY(actualPaneInitialY + toolbarNewY);
actualPane.setHeight(actualPaneInitialH + getHeight() - toolbarNewY);
actualPane.doLayout();
}
f.repaint();
}
}
};
contentPane.addScrollListener(scrollListener);
releasedListener = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (getY() + getHeight() / 2 > 0) {
showToolbar();
} else {
hideToolbar();
}
f.repaint();
}
};
contentPane.addPointerReleasedListener(releasedListener);
} else {
if (scrollListener != null) {
contentPane.removeScrollListener(scrollListener);
contentPane.removePointerReleasedListener(releasedListener);
}
}
}
}
/**
* Creates the side navigation component with the Commands.
*
* @param commands the Command objects
* @return the Component to display on the side navigation
*/
protected Container createSideNavigationComponent(Vector commands, String placement) {
return sideMenu.createSideNavigationPanel(commands, placement);
}
/**
* Creates an empty side navigation panel.
*/
protected Container constructSideNavigationComponent() {
return sideMenu.constructSideNavigationPanel();
}
/**
* This method responsible to add a Component to the side navigation panel.
*
* @param menu the Menu Container that was created in the
* constructSideNavigationComponent() method
*
* @param cmp the Component to add to the side menu
*/
protected void addComponentToSideMenu(Container menu, Component cmp) {
sideMenu.addComponentToSideMenuImpl(menu, cmp);
}
/**
* Returns the commands within the side menu which can be useful for things like unit testing. Notice
* that you should not mutate the commands or the iteratable set in any way!
* @return the commands in the overflow menu
*/
public Iterable<Command> getSideMenuCommands() {
ArrayList<Command> cmds = new ArrayList<Command>();
if(permanentSideMenu || onTopSideMenu) {
findAllCommands(permanentSideMenuContainer, cmds);
return cmds;
}
Form f = getComponentForm();
int commands = f.getCommandCount();
for(int iter = 0 ; iter < commands ; iter++) {
cmds.add(f.getCommand(iter));
}
return cmds;
}
/**
* Removes the given overflow menu command, notice that this has no effect on the menu that is currently
* showing (if it is currently showing) only on the upcoming iterations.
* @param cmd the command to remove from the overflow
*/
public void removeOverflowCommand(Command cmd) {
overflowCommands.remove(cmd);
}
class ToolbarSideMenu extends SideMenuBar {
@Override
protected Container createSideNavigationComponent(Vector commands, String placement) {
return Toolbar.this.createSideNavigationComponent(commands, placement);
}
@Override
protected Container constructSideNavigationComponent(){
return Toolbar.this.constructSideNavigationComponent();
}
@Override
protected void addComponentToSideMenu(Container menu, Component cmp) {
Toolbar.this.addComponentToSideMenu(menu, cmp);
}
@Override
protected Container getTitleAreaContainer() {
return Toolbar.this;
}
@Override
protected Component getTitleComponent() {
return Toolbar.this.getTitleComponent();
}
@Override
protected void initMenuBar(Form parent) {
Component ta = parent.getTitleArea();
parent.removeComponentFromForm(ta);
super.initMenuBar(parent);
if(layered){
Container layeredPane = parent.getLayeredPane();
Container p = layeredPane.getParent();
Container top = new Container(new BorderLayout());
top.addComponent(BorderLayout.NORTH, Toolbar.this);
p.addComponent(top);
}else{
parent.addComponentToForm(BorderLayout.NORTH, Toolbar.this);
}
initialized = true;
setTitle(parent.getTitle());
parent.revalidate();
initTitleBarStatus();
Display.getInstance().callSerially(new Runnable() {
public void run() {
if (scrollOff) {
bindScrollListener(true);
}
}
});
}
@Override
public boolean contains(int x, int y) {
return Toolbar.this.contains(x, y);
}
@Override
public Component getComponentAt(int x, int y) {
return Toolbar.this.getComponentAt(x, y);
}
@Override
void installRightCommands() {
super.installRightCommands();
if (overflowCommands != null && overflowCommands.size() > 0) {
UIManager uim = UIManager.getInstance();
Image i = (Image) uim.getThemeImageConstant("menuImage");
if (i == null) {
float size = 4.5f;
try {
size = Float.parseFloat(uim.getThemeConstant("overflowImageSize", "4.5"));
} catch(Throwable t) {
Log.e(t);
}
i = FontImage.createMaterial(FontImage.MATERIAL_MORE_VERT, UIManager.getInstance().getComponentStyle("TitleCommand"), size);
}
menuButton = sideMenu.createTouchCommandButton(new Command("", i) {
public void actionPerformed(ActionEvent ev) {
sideMenu.showMenu();
}
});
menuButton.putClientProperty("overflow", Boolean.TRUE);
menuButton.setUIID("TitleCommand");
menuButton.setName("OverflowButton");
Layout l = getTitleAreaContainer().getLayout();
if (l instanceof BorderLayout) {
BorderLayout bl = (BorderLayout) l;
Component east = bl.getEast();
if (east == null) {
getTitleAreaContainer().addComponent(BorderLayout.EAST, menuButton);
} else {
if (east instanceof Container) {
Container cnt = (Container) east;
for (int j = 0; j < cnt.getComponentCount(); j++) {
Component c = cnt.getComponentAt(j);
if (c instanceof Button) {
//remove the menu button and add it last
if (c.getClientProperty("overflow") != null) {
cnt.removeComponent(c);
}
}
}
cnt.addComponent(cnt.getComponentCount(), menuButton);
} else {
if (east instanceof Button) {
if (east.getClientProperty("overflow") != null) {
return;
}
}
east.getParent().removeComponent(east);
Container buttons = new Container(new BoxLayout(BoxLayout.X_AXIS));
buttons.addComponent(east);
buttons.addComponent(menuButton);
getTitleAreaContainer().addComponent(BorderLayout.EAST, buttons);
}
}
}
}
}
@Override
protected Component createCommandComponent(Vector commands) {
return createOverflowCommandList(overflowCommands);
}
@Override
protected Button createBackCommandButton() {
Button back = new Button(getBackCommand());
return back;
}
@Override
protected Command showMenuDialog(Dialog menu) {
return showOverflowMenu(menu);
}
@Override
public int getCommandBehavior() {
return Display.COMMAND_BEHAVIOR_ICS;
}
@Override
void synchronizeCommandsWithButtonsInBackbutton() {
boolean hasSideCommands = false;
Vector commands = getCommands();
for (int iter = commands.size() - 1; iter > -1; iter--) {
Command c = (Command) commands.elementAt(iter);
if (c.getClientProperty("TitleCommand") == null) {
hasSideCommands = true;
break;
}
}
boolean hideBack = UIManager.getInstance().isThemeConstant("hideBackCommandBool", false);
boolean showBackOnTitle = UIManager.getInstance().isThemeConstant("showBackCommandOnTitleBool", false);
//need to put the back command
if (getBackCommand() != null) {
if (hasSideCommands && !hideBack) {
getCommands().remove(getBackCommand());
getCommands().add(getCommands().size(), getBackCommand());
} else {
if (!hideBack || showBackOnTitle) {
//put the back command on the title
Layout l = getTitleAreaContainer().getLayout();
if (l instanceof BorderLayout) {
BorderLayout bl = (BorderLayout) l;
Component west = bl.getWest();
Button back = createBackCommandButton();
if (!back.getUIID().equals("BackCommand")) {
back.setUIID("BackCommand");
}
hideEmptyCommand(back);
verifyBackCommandRTL(back);
if (west instanceof Container) {
((Container) west).addComponent(0, back);
} else {
Container left = new Container(new BoxLayout(BoxLayout.X_AXIS));
left.addComponent(back);
if (west != null) {
west.getParent().removeComponent(west);
left.addComponent(west);
}
getTitleAreaContainer().addComponent(BorderLayout.WEST, left);
}
}
}
}
}
}
@Override
void initTitleBarStatus() {
Toolbar.this.initTitleBarStatus();
}
}
}