com.codename1.ui.ComboBox Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2010, Oracle 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. Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui;
import com.codename1.ui.animations.CommonTransitions;
import com.codename1.ui.events.ActionSource;
import com.codename1.ui.geom.Dimension;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.list.DefaultListCellRenderer;
import com.codename1.ui.list.DefaultListModel;
import com.codename1.ui.list.ListCellRenderer;
import com.codename1.ui.list.ListModel;
import com.codename1.ui.plaf.UIManager;
import java.util.Vector;
/**
* A {@code ComboBox} is a list that allows only one selection at a time, when a user clicks
* the {@code ComboBox} a popup button with the full list of elements allows the selection of
* a single element. The {@code ComboBox} is driven by the list model and allows all the renderer
* features of the List as well.
* The {@code ComboBox} is notoriously hard to style properly as it relies on a complex dynamic of
* popup renderer and instantly visible renderer. The UIID for the {@code ComboBox} is "{@code ComboBox}"
* however if you set it to something else all the other UIID's will also change their prefix. E.g. the "{@code ComboBoxPopup}"
* UIID will become "{@code MyNewUIIDPopup}".
* The combo box defines the following UIID's by default:
*
*
* - {@code ComboBox}
* - {@code ComboBoxItem}
* - {@code ComboBoxFocus}
* - {@code PopupContentPane}
* - {@code PopupItem}
* - {@code PopupFocus}
*
*
* This class also defines theme constants that allow some native themes to manipulate its behavior e.g.:
*
*
* - popupTitleBool - shows the "label for" value as the title of the popup dialog
* - popupCancelBodyBool - Adds a cancel button into the popup dialog
* - centeredPopupBool - shows the popup dialog in the center of the screen instead of under the popup
* - otherPopupRendererBool - Uses a different list cell render for the popup than the one used for the {@code ComboBox}
* itself. When this is false {@code PopupItem} & {@code PopupFocus} become irrelevant. Notice that the
* Android native theme defines this to true.
*
*
* iOS doesn't use combo boxes as part of its UI paradigm. Its available there mostly in web applications and feels
* unnatural in iOS which is why we recommend using the {@link com.codename1.ui.spinner.Picker} class.
*
*
* The sample code below uses the {@link com.codename1.ui.list.GenericListCellRenderer} to create a richer
* {@code ComboBox} UI.
*
*
*
*
* @see List
* @author Chen Fishbein
*/
public class ComboBox extends List implements ActionSource {
private static boolean defaultActAsSpinnerDialog;
private Image comboBoxImage;
/**
* When this flag is active the combo box acts as a button that opens a dialog that looks like a spinner
* this allows creating user interfaces for touch devices where a spinner UI approach is more common than
* a combo box paradigm.
* @return the defaultActAsSpinnerDialog
*/
public static boolean isDefaultActAsSpinnerDialog() {
return defaultActAsSpinnerDialog;
}
/**
* When this flag is active the combo box acts as a button that opens a dialog that looks like a spinner
* this allows creating user interfaces for touch devices where a spinner UI approach is more common than
* a combo box paradigm.
* @param aDefaultActAsSpinnerDialog the defaultActAsSpinnerDialog to set
*/
public static void setDefaultActAsSpinnerDialog(boolean aDefaultActAsSpinnerDialog) {
defaultActAsSpinnerDialog = aDefaultActAsSpinnerDialog;
}
private boolean actAsSpinnerDialog = defaultActAsSpinnerDialog;
/**
* Indicates whethe the soft buttons for select/cancel should appear for the combo box by default
*/
private static boolean defaultIncludeSelectCancel = true;
/**
* Indicates whethe the soft buttons for select/cancel should appear for the combo box
*/
private boolean includeSelectCancel = defaultIncludeSelectCancel;
/**
* Creates a new instance of ComboBox
*
* @param items set of items placed into the combo box model
*/
public ComboBox(Vector items) {
this(new DefaultListModel(items));
}
/**
* Creates a new instance of ComboBox
*
* @param items set of items placed into the combo box model
*/
public ComboBox(Object... items) {
this(new DefaultListModel(items));
}
/**
* Constructs an empty combo box
*/
public ComboBox() {
this(new DefaultListModel());
}
/**
* Creates a new instance of ComboBox
*
* @param model the model for the combo box elements and selection
*/
public ComboBox(ListModel model) {
super(model);
super.setUIID("ComboBox");
((DefaultListCellRenderer) super.getRenderer()).setShowNumbers(false);
setInputOnFocus(false);
setIsScrollVisible(false);
setFixedSelection(FIXED_NONE_CYCLIC);
ListCellRenderer r = getRenderer();
if(r instanceof Component) {
Component c = (Component) getRenderer();
c.setUIID("ComboBoxItem");
}
Component c = getRenderer().getListFocusComponent(this);
if(c != null){
c.setUIID("ComboBoxFocus");
}
if (UIManager.getInstance().isThemeConstant("comboBoxUseMaterialArrowDropDownBool", false)) {
Style comboImageStyle = new Style(getStyle());
comboImageStyle.setBgTransparency(0);
setComboBoxImage(FontImage.createMaterial(FontImage.MATERIAL_ARROW_DROP_DOWN, comboImageStyle));
}
}
/**
* Sets the ComboBox drop down icon
*
* @param comboBoxImage the drop down icon
*/
public void setComboBoxImage(Image comboBoxImage) {
this.comboBoxImage = comboBoxImage;
}
/**
* Gets the ComboBox drop down icon
*
* @return the drop down icon
*/
public Image getComboBoxImage() {
return comboBoxImage;
}
/**
* {@inheritDoc}
*/
public void setUIID(String uiid) {
super.setUIID(uiid);
ListCellRenderer r = getRenderer();
if(r instanceof Component) {
Component c = (Component) getRenderer();
c.setUIID(uiid + "Item");
}
Component c = getRenderer().getListFocusComponent(this);
if(c != null){
c.setUIID(uiid + "Focus");
}
if (UIManager.getInstance().isThemeConstant("comboBoxUseMaterialArrowDropDownBool", false)) {
Style comboImageStyle = new Style(getStyle());
comboImageStyle.setBgTransparency(0);
setComboBoxImage(FontImage.createMaterial(FontImage.MATERIAL_ARROW_DROP_DOWN, comboImageStyle));
}
}
/**
* {@inheritDoc}
*/
public int getBaseline(int width, int height) {
Component selected;
if (getRenderingPrototype() != null) {
selected = getRenderer().getListCellRendererComponent(this, getRenderingPrototype(), 0, true);
}
if (getModel().getSize() > 0) {
selected = getRenderer().getListCellRendererComponent(this, getModel().getItemAt(0), 0, true);
} else {
selected = getRenderer().getListCellRendererComponent(this, "XXXXXXXXXXX", 0, true);
}
return getHeight() - getStyle().getPaddingBottom() - selected.getStyle().getPaddingBottom();
}
/**
* {@inheritDoc}
*/
protected void laidOut() {
}
/**
* {@inheritDoc}
*/
public Rectangle getSelectedRect() {
// the implemenation from list doesn't make sense here, restore the component implementation
return new Rectangle(getAbsoluteX(), getAbsoluteY(), getBounds().getSize());
}
/**
* {@inheritDoc}
*/
protected Rectangle getVisibleBounds() {
return getBounds();
}
/**
* {@inheritDoc}
*/
public void setSelectedIndex(int selection) {
super.setSelectedIndex(selection, false);
}
/**
* {@inheritDoc}
*/
public void setSelectedIndex(int selection, boolean scroll) {
super.setSelectedIndex(selection, false);
}
/**
* {@inheritDoc}
*/
public void pointerHover(int[] x, int[] y) {
}
/**
* {@inheritDoc}
*/
public void pointerHoverReleased(int[] x, int[] y) {
}
/**
* Subclasses can override this method to change the creation of the dialog
*
* @param l the list of the popup
* @return a dialog instance
*/
protected Dialog createPopupDialog(List l) {
Dialog popupDialog = new Dialog(getUIID() + "Popup", getUIID() + "PopupTitle"){
void sizeChangedInternal(int w, int h) {
//if only height changed it's the virtual keyboard, no need to
//resize the popup just resize the parent form
if(getWidth() == w && getHeight() != h){
Form frm = getPreviousForm();
if(frm != null){
frm.sizeChangedInternal(w, h);
}
setSize(new Dimension(w, h));
repaint();
}else{
dispose();
}
}
};
popupDialog.setScrollable(false);
popupDialog.getContentPane().setAlwaysTensile(false);
popupDialog.setAlwaysTensile(false);
popupDialog.getContentPane().setUIID("PopupContentPane");
popupDialog.setDisposeWhenPointerOutOfBounds(true);
popupDialog.setTransitionInAnimator(CommonTransitions.createEmpty());
popupDialog.setTransitionOutAnimator(CommonTransitions.createEmpty());
popupDialog.setLayout(new BorderLayout());
popupDialog.addComponent(BorderLayout.CENTER, l);
return popupDialog;
}
private boolean showingPopupDialog;
/**
* Returns true if the popup dialog is currently showing for this combobox.
* @return
* @since 8.0
*/
public boolean isShowingPopupDialog() {
return showingPopupDialog;
}
/**
* Shows the popup dialog for the combo box and returns the resulting command.
* This method can be overriden by subclasses to modify the behavior of the class.
*
* @param popupDialog the popup dialog
* @param l the list within
* @return the selected command
*/
protected Command showPopupDialog(Dialog popupDialog, List l) {
if(getUIManager().isThemeConstant("popupTitleBool", false)) {
if(getLabelForComponent() != null) {
popupDialog.setTitle(getLabelForComponent().getText());
}
}
if(includeSelectCancel) {
popupDialog.setBackCommand(popupDialog.getMenuBar().getCancelMenuItem());
if(Display.getInstance().isTouchScreenDevice()) {
if(getUIManager().isThemeConstant("popupCancelBodyBool", false)) {
popupDialog.placeButtonCommands(new Command[] {popupDialog.getMenuBar().getCancelMenuItem()});
}
} else {
if (Display.getInstance().isThirdSoftButton()) {
popupDialog.addCommand(popupDialog.getMenuBar().getSelectMenuItem());
popupDialog.addCommand(popupDialog.getMenuBar().getCancelMenuItem());
} else {
popupDialog.addCommand(popupDialog.getMenuBar().getCancelMenuItem());
popupDialog.addCommand(popupDialog.getMenuBar().getSelectMenuItem());
}
}
}
if(actAsSpinnerDialog) {
l.setFixedSelection(List.FIXED_CENTER);
l.setUIID("Spinner");
l.spinnerOverlay = getUIManager().getComponentStyle("SpinnerOverlay");
l.spinnerOverlay.setMargin(0, 0, 0, 0);
l.setAlwaysTensile(false);
l.installDefaultPainter(l.spinnerOverlay);
popupDialog.setDialogUIID("Container");
popupDialog.setUIID("Container");
popupDialog.getTitleComponent().setUIID("Container");
popupDialog.setTransitionInAnimator(CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, true, 200));
popupDialog.setTransitionOutAnimator(CommonTransitions.createSlide(CommonTransitions.SLIDE_VERTICAL, false, 200));
showingPopupDialog = true;
Command out = popupDialog.show(Display.getInstance().getDisplayHeight() - popupDialog.getDialogComponent().getPreferredH(), 0, 0, 0, true, true);
showingPopupDialog = false;
return out;
}
if(getUIManager().isThemeConstant("centeredPopupBool", false)) {
showingPopupDialog = true;
Command out = popupDialog.showPacked(BorderLayout.CENTER, true);
showingPopupDialog = false;
return out;
} else {
int top, bottom, left, right;
Form parentForm = getComponentForm();
int listW = Math.max(getWidth() , l.getPreferredW());
listW = Math.min(listW + l.getSideGap(), parentForm.getContentPane().getWidth());
Component content = popupDialog.getDialogComponent();
Style contentStyle = content.getStyle();
int listH = content.getPreferredH()
+ contentStyle.getVerticalMargins();
Component title = popupDialog.getTitleArea();
listH += title.getPreferredH()
+ title.getStyle().getVerticalMargins();
bottom = 0;
top = getAbsoluteY();
int formHeight = parentForm.getHeight();
if(parentForm.getSoftButtonCount() > 1) {
Component c = parentForm.getSoftButton(0).getParent();
formHeight -= c.getHeight();
Style s = c.getStyle();
formHeight -= (s.getVerticalMargins());
}
if(listH < formHeight) {
// pop up or down?
if(top > formHeight / 2) {
bottom = formHeight - top;
top = top - listH;
} else {
top += getHeight();
bottom = formHeight - top - listH;
}
} else {
top = 0;
}
left = getAbsoluteX();
right = parentForm.getWidth() - left - listW;
if(right < 0) {
left += right;
right = 0;
}
popupDialog.setBackCommand(popupDialog.getMenuBar().getCancelMenuItem());
showingPopupDialog = true;
Command out = popupDialog.show(Math.max(top, 0),
Math.max(bottom, 0),
Math.max(left, 0),
Math.max(right, 0), false, true);
showingPopupDialog = false;
return out;
}
}
/**
* {@inheritDoc}
*/
protected void fireClicked() {
List l = createPopupList();
l.dispatcher = dispatcher;
l.eventSource = this;
l.disposeDialogOnSelection = true;
Form parentForm = getComponentForm();
//l.getSelectedStyle().setBorder(null);
int tint = parentForm.getTintColor();
parentForm.setTintColor(0);
Dialog popupDialog = createPopupDialog(l);
int originalSel = getSelectedIndex();
Form.comboLock = includeSelectCancel;
float rr = Dialog.getDefaultBlurBackgroundRadius();
Dialog.setDefaultBlurBackgroundRadius(-1);
Command result = showPopupDialog(popupDialog, l);
Dialog.setDefaultBlurBackgroundRadius(rr);
Form.comboLock = false;
parentForm.setTintColor(tint);
if(result == popupDialog.getMenuBar().getCancelMenuItem() || popupDialog.wasDisposedDueToOutOfBoundsTouch() ||
popupDialog.wasDisposedDueToRotation()) {
setSelectedIndex(originalSel);
}
}
/**
* Creates the list object used within the popup dialog. This method allows subclasses
* to customize the list creation for the popup dialog shown when the combo box is pressed.
*
* @return a newly created list object used when the user presses the combo box.
*/
protected List createPopupList() {
List l = new List(getModel());
l.setCommandList(isCommandList());
l.setSmoothScrolling(isSmoothScrolling());
l.setFixedSelection(getFixedSelection());
l.setListCellRenderer(getRenderer());
l.setItemGap(getItemGap());
l.setUIID("ComboBoxList");
if(getUIManager().isThemeConstant("otherPopupRendererBool", false)) {
DefaultListCellRenderer renderer = new DefaultListCellRenderer();
renderer.setUIID("PopupItem");
renderer.getListFocusComponent(l).setUIID("PopupFocus");
l.setListCellRenderer(renderer);
}
return l;
}
/**
* {@inheritDoc}
*/
public void keyReleased(int keyCode) {
// other events are in keyReleased to prevent the next event from reaching the next form
int gameAction = Display.getInstance().getGameAction(keyCode);
if (gameAction == Display.GAME_FIRE) {
fireClicked();
return;
}
super.keyPressed(keyCode);
}
/**
* Prevent the combo box from losing selection in some use cases
*/
void selectElement(int selectedIndex) {
}
/**
* {@inheritDoc}
*/
public void pointerPressed(int x, int y) {
}
/**
* {@inheritDoc}
*/
public void pointerDragged(int x, int y) {
}
/**
* {@inheritDoc}
*/
public void pointerReleased(int x, int y) {
if(isEnabled() && !Display.impl.isScrollWheeling()) {
fireClicked();
}
}
/**
* {@inheritDoc}
*/
public void paint(Graphics g) {
getUIManager().getLookAndFeel().drawComboBox(g, this);
}
/**
* {@inheritDoc}
*/
protected Dimension calcPreferredSize() {
return getUIManager().getLookAndFeel().getComboBoxPreferredSize(this);
}
/**
* {@inheritDoc}
*/
public int getOrientation() {
return COMBO;
}
/**
* Indicates whethe the soft buttons for select/cancel should appear for the combo box
*
* @return true if the soft buttons for select/cancel should appear for the combo box
*/
public boolean isIncludeSelectCancel() {
return includeSelectCancel;
}
/**
* Indicates whethe the soft buttons for select/cancel should appear for the combo box
*
* @param includeSelectCancel the new value
*/
public void setIncludeSelectCancel(boolean includeSelectCancel) {
this.includeSelectCancel = includeSelectCancel;
}
/**
* Indicates whethe the soft buttons for select/cancel should appear for the combo box by default
*
* @return true if the soft buttons for select/cancel should appear for the combo box
*/
public static boolean isDefaultIncludeSelectCancel() {
return defaultIncludeSelectCancel;
}
/**
* Indicates whethe the soft buttons for select/cancel should appear for the combo box by default
*
* @param aDefaultIncludeSelectCancel the new value
*/
public static void setDefaultIncludeSelectCancel(boolean aDefaultIncludeSelectCancel) {
defaultIncludeSelectCancel = aDefaultIncludeSelectCancel;
}
/**
* When this flag is active the combo box acts as a button that opens a dialog that looks like a spinner
* this allows creating user interfaces for touch devices where a spinner UI approach is more common than
* a combo box paradigm.
* @return the actAsSpinnerDialog
*/
public boolean isActAsSpinnerDialog() {
return actAsSpinnerDialog;
}
/**
* When this flag is active the combo box acts as a button that opens a dialog that looks like a spinner
* this allows creating user interfaces for touch devices where a spinner UI approach is more common than
* a combo box paradigm.
* @param actAsSpinnerDialog the actAsSpinnerDialog to set
*/
public void setActAsSpinnerDialog(boolean actAsSpinnerDialog) {
this.actAsSpinnerDialog = actAsSpinnerDialog;
}
}