/* * 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.list; import com.codename1.ui.Container; import com.codename1.ui.Graphics; import com.codename1.ui.Component; import com.codename1.ui.Display; import com.codename1.ui.Form; import com.codename1.ui.events.ActionEvent; import com.codename1.ui.events.ActionListener; import com.codename1.ui.events.DataChangedListener; import com.codename1.ui.events.SelectionListener; import com.codename1.ui.geom.Dimension; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.layouts.Layout; import com.codename1.ui.plaf.Style; import com.codename1.ui.util.EventDispatcher; import java.util.Collection; import java.util.Vector; /** * This is a "list component" implemented as a container with a layout manager * which provides <b>some</b> of the ui advantages of a Container and some of a * list while pulling out some of the drawbacks of both. * This container uses the model/renderer approach for populating itself, adding/removing * entries will probably break it. It still provides most of the large size advantages * a list offers since the components within it are very simple and don't contain any * actual state other than layout information. The big advantage with this class is * the ability to leverage elaborate CodenameOne layouts such as Grid, Table & flow layout * to provide other ways of rendering the content of a list model. * * @author Shai Almog * @deprecated the performance of ContainerList is worse than the performance of List or Container. The API/behaviors * are problematic and we don't think its the right choice for any project. It is our recommendation that you use * Container, InfiniteContainer etc. */ public class ContainerList extends Container { private CellRenderer renderer = new DefaultListCellRenderer(); private ListModel model; private Listeners listener; private EventDispatcher dispatcher = new EventDispatcher(); /** * Default constructor */ public ContainerList() { this(new DefaultListModel()); } /** * Constructs a container list with the given model * * @param m the model */ public ContainerList(ListModel m) { init(m); } private void init(ListModel m) { setModel(m); setUIID("ContainerList"); setScrollableY(true); } /** * Constructs a container list with the given model and layout * * @param l layout manager * @param m the model */ public ContainerList(Layout l, ListModel m) { super(l); init(m); } /** * The renderer used to draw the container list elements * * @param r renderer instance */ public void setRenderer(CellRenderer r) { renderer = r; for(int iter = 0 ; iter < getComponentCount() ; iter++) { getComponentAt(iter).setShouldCalcPreferredSize(true); } setScrollSize(null); if(isInitialized()) { revalidate(); } } /** * The renderer used to draw the container list elements */ public CellRenderer getRenderer() { return renderer; } private void updateComponentCount() { int cc = getComponentCount(); int modelCount = model.getSize(); if(cc != modelCount) { if(cc < modelCount) { for(int iter = cc ; iter < modelCount ; iter++) { addComponent(new Entry(iter)); } } else { while(getComponentCount() > modelCount) { removeComponent(getComponentAt(getComponentCount() - 1)); } } Form f = getComponentForm(); if(f != null) { f.revalidate(); } } } /** * Returns the list model * * @return the list model */ public ListModel getModel() { return model; } /** * Allows binding a listener to user selection actions * * @param l the action listener to be added */ public void addActionListener(ActionListener l) { dispatcher.addListener(l); } /** * This method allows extracting the action listeners from the current list * * @return vector containing the action listeners on the list * @deprecated use getListeners instead */ public Vector getActionListeners() { return dispatcher.getListenerVector(); } /** * This method allows extracting the action listeners from the current list * * @return Collection containing the action listeners on the list */ public Collection getListeners() { return dispatcher.getListenerCollection(); } /** * Allows binding a listener to user selection actions * * @param l the action listener to be removed */ public void removeActionListener(ActionListener l) { dispatcher.removeListener(l); } /** * {@inheritDoc} */ protected void initComponent() { if(model != null) { int i = model.getSelectedIndex(); if(i > 0) { getComponentAt(i).requestFocus(); } bindListeners(); } } /** * {@inheritDoc} */ protected void deinitialize() { if (this.model != null && listener != null) { this.model.removeDataChangedListener(listener); this.model.removeSelectionListener(listener); listener = null; } } /** * Set the model for the container list * * @param model a model class that is mapped into the internal components */ public void setModel(ListModel model) { if (this.model != null && listener != null) { this.model.removeDataChangedListener(listener); this.model.removeSelectionListener(listener); listener = null; } this.model = model; updateComponentCount(); if(model.getSelectedIndex() > 0) { getComponentAt(model.getSelectedIndex()).requestFocus(); } if (isInitialized()) { bindListeners(); } repaint(); } private void bindListeners() { if (listener == null) { listener = new Listeners(); model.addDataChangedListener(listener); model.addSelectionListener(listener); } } /** * Returns the current/last selected item * @return selected item or null */ public Object getSelectedItem() { int i = model.getSelectedIndex(); if(i > -1) { return model.getItemAt(i); } return null; } /** * Returns the current selected offset in the list * * @return the current selected offset in the list */ public int getSelectedIndex() { return model.getSelectedIndex(); } /** * Sets the current selected offset in the list, by default this implementation * will scroll the list to the selection if the selection is outside of the screen * * @param index the current selected offset in the list */ public void setSelectedIndex(int index) { if (index < 0) { throw new IllegalArgumentException("Selection index is negative:" + index); } getComponentAt(index).requestFocus(); model.setSelectedIndex(index); } /** * Triggers the event to the listeners * @param evt the event to fire */ protected void fireActionEvent(ActionEvent evt) { if(isEnabled() && !Display.getInstance().hasDragOccured()){ dispatcher.fireActionEvent(evt); } } /** * {@inheritDoc} */ public String[] getPropertyNames() { return new String[] {"ListItems", "Renderer"}; } /** * {@inheritDoc} */ public Class[] getPropertyTypes() { return new Class[] {com.codename1.impl.CodenameOneImplementation.getObjectArrayClass(), CellRenderer.class}; } /** * {@inheritDoc} */ public String[] getPropertyTypeNames() { return new String[] {"Object[]", "CellRenderer"}; } /** * {@inheritDoc} */ public Object getPropertyValue(String name) { if(name.equals("ListItems")) { Object[] obj = new Object[model.getSize()]; int olen = obj.length; for(int iter = 0 ; iter < olen ; iter++) { obj[iter] = model.getItemAt(iter); } return obj; } if(name.equals("Renderer")) { return getRenderer(); } return null; } /** * {@inheritDoc} */ public String setPropertyValue(String name, Object value) { if(name.equals("ListItems")) { setModel(new DefaultListModel((Object[])value)); return null; } if(name.equals("Renderer")) { setRenderer((CellRenderer)value); return null; } return super.setPropertyValue(name, value); } @Override public Rectangle getSelectedRect() { Component cmp = getComponentAt(getSelectedIndex()); return cmp.getSelectedRect(); } /** * {@inheritDoc} */ protected int getDragRegionStatus(int x, int y) { if(!isScrollable()) { return DRAG_REGION_NOT_DRAGGABLE; } if(isScrollableX()) { if(isScrollableY()) { return DRAG_REGION_POSSIBLE_DRAG_XY; } return DRAG_REGION_POSSIBLE_DRAG_X; } return DRAG_REGION_POSSIBLE_DRAG_Y; } /** * This class is an internal implementation detail */ class Entry extends Component { private int offset; Entry(int off) { setUIID("Container"); setFocusable(true); offset = off; } public void initComponent() { offset = getParent().getComponentIndex(this); } protected void focusGained() { model.setSelectedIndex(offset); } public void paintBackground(Graphics g) { } public void paintBorder(Graphics g) { } public void paint(Graphics g) { Component cmp = renderer.getCellRendererComponent(ContainerList.this, model, model.getItemAt(offset), offset, hasFocus()); cmp.setX(getX()); cmp.setY(getY()); cmp.setWidth(getWidth()); cmp.setHeight(getHeight()); if(cmp instanceof Container) { ((Container)cmp).revalidate(); } cmp.setFocus(hasFocus()); cmp.paintComponent(g); } public void longPointerPress(int x, int y) { super.longPointerPress(x, y); pointerReleasedImpl(x, y, true); } public void pointerReleased(int x, int y) { super.pointerReleased(x, y); pointerReleasedImpl(x, y, false); } /** * {@inheritDoc} */ public void pointerReleasedImpl(int x, int y, boolean longPress) { if (!isDragActivated()) { // fire the action event into the selected component Component cmp = renderer.getCellRendererComponent(ContainerList.this, model, model.getItemAt(offset), offset, hasFocus()); if(cmp instanceof Container) { int absX = getAbsoluteX(); int absY = getAbsoluteY(); int newX = x - absX + cmp.getX(); int newY = y - absY + cmp.getY(); Component selectionCmp = ((Container) cmp).getComponentAt(newX, newY); if(selectionCmp != null) { selectionCmp.setX(0); selectionCmp.setY(0); if(longPress){ selectionCmp.longPointerPress(newX, newY); fireActionEvent(new ActionEvent(ContainerList.this, x, y, true)); return; }else{ selectionCmp.pointerPressed(newX, newY); selectionCmp.pointerReleased(newX, newY); } } } fireActionEvent(new ActionEvent(ContainerList.this, x, y, longPress)); } } /** * {@inheritDoc} */ public void keyReleased(int keyCode) { super.keyReleased(keyCode); if(Display.getInstance().getGameAction(keyCode) == Display.GAME_FIRE) { fireActionEvent(new ActionEvent(ContainerList.this, keyCode)); } } @Override public Dimension getPreferredSize() { ContainerList.this.setScrollSize(null); return calcPreferredSize(); } public Dimension calcPreferredSize() { Component c = renderer.getCellRendererComponent(ContainerList.this, model, model.getItemAt(offset), offset, hasFocus()); if(getWidth() <= 0) { c.setWidth(Display.getInstance().getDisplayWidth()); c.setHeight(Display.getInstance().getDisplayHeight()); } else { c.setWidth(getWidth()); c.setHeight(getHeight()); } if(c instanceof Container) { ((Container)c).revalidate(); } Dimension d = c.getPreferredSize(); return d; } } private class Listeners implements DataChangedListener, SelectionListener { public void dataChanged(int status, int index) { updateComponentCount(); } public void selectionChanged(int oldSelected, int newSelected) { getComponentAt(newSelected).requestFocus(); } } }