blob: 4099ef5ff5ee09daf5744184e079affebd882049 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.wtk;
import java.util.Iterator;
import org.apache.pivot.beans.DefaultProperty;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Sequence;
import org.apache.pivot.util.ImmutableIterator;
import org.apache.pivot.util.ListenerList;
import org.apache.pivot.wtk.content.MenuItemDataRenderer;
/**
* Component that presents a cascading menu.
*/
@DefaultProperty("sections")
public class Menu extends Container {
/**
* Component representing a menu item.
*/
@DefaultProperty("menu")
public static class Item extends Button {
private static class ItemListenerList extends WTKListenerList<ItemListener> implements
ItemListener {
@Override
public void menuChanged(Item item, Menu previousMenu) {
for (ItemListener listener : this) {
listener.menuChanged(item, previousMenu);
}
}
@Override
public void activeChanged(Item item) {
for (ItemListener listener : this) {
listener.activeChanged(item);
}
}
}
private Section section = null;
private Menu menu = null;
private boolean active = false;
private ItemListenerList itemListeners = new ItemListenerList();
private static final Button.DataRenderer DEFAULT_DATA_RENDERER = new MenuItemDataRenderer();
public Item() {
this(null);
}
public Item(Object buttonData) {
super(buttonData);
setDataRenderer(DEFAULT_DATA_RENDERER);
installSkin(Item.class);
}
@Override
protected void setParent(Container parent) {
if (parent != null && !(parent instanceof Menu)) {
throw new IllegalArgumentException("Parent must be an instance of "
+ Menu.class.getName());
}
setActive(false);
super.setParent(parent);
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
if (!enabled) {
setActive(false);
}
}
public Section getSection() {
return section;
}
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
if (menu != null && menu.getItem() != null) {
throw new IllegalArgumentException("menu already belongs to an item.");
}
Menu previousMenu = this.menu;
if (previousMenu != menu) {
if (previousMenu != null) {
previousMenu.item = null;
}
if (menu != null) {
menu.item = this;
}
this.menu = menu;
itemListeners.menuChanged(this, previousMenu);
}
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
if (active && (getParent() == null || !isEnabled())) {
throw new IllegalStateException();
}
if (this.active != active) {
this.active = active;
// Update the active item
Menu menuLocal = (Menu) getParent();
Item activeItem = menuLocal.getActiveItem();
if (active) {
// Set this as the new active item (do this before
// de-selecting any currently active item so the
// menu bar's change event isn't fired twice)
menuLocal.setActiveItem(this);
// Deactivate any previously active item
if (activeItem != null) {
activeItem.setActive(false);
}
} else {
// If this item is currently active, clear the
// selection
if (activeItem == this) {
menuLocal.setActiveItem(null);
}
}
itemListeners.activeChanged(this);
}
}
@Override
public void setTriState(boolean triState) {
throw new UnsupportedOperationException("Menu items can't be tri-state.");
}
@Override
public void press() {
if (isToggleButton()) {
setSelected(getButtonGroup() == null ? !isSelected() : true);
}
super.press();
if (menu == null) {
Item item = this;
while (item != null) {
Menu menuLocal = (Menu) item.getParent();
if (menuLocal == null) {
item = null;
} else {
menuLocal.menuItemSelectionListeners.itemSelected(this);
item = menuLocal.item;
}
}
}
}
public ListenerList<ItemListener> getItemListeners() {
return itemListeners;
}
}
/**
* Item listener interface.
*/
public interface ItemListener {
/**
* Called when an item's menu has changed.
*
* @param item The item that has been moved.
* @param previousMenu The menu where the item used to live.
*/
public void menuChanged(Item item, Menu previousMenu);
/**
* Called when an item's active state has changed.
*
* @param item The item that is changing.
*/
public void activeChanged(Item item);
}
/**
* Class representing a menu section. A section is a grouping of menu items
* within a menu.
*/
public static class Section implements Sequence<Item>, Iterable<Item> {
private static class SectionListenerList extends WTKListenerList<SectionListener> implements
SectionListener {
@Override
public void itemInserted(Menu.Section section, int index) {
for (SectionListener listener : this) {
listener.itemInserted(section, index);
}
}
@Override
public void itemsRemoved(Menu.Section section, int index, Sequence<Item> removed) {
for (SectionListener listener : this) {
listener.itemsRemoved(section, index, removed);
}
}
@Override
public void nameChanged(Menu.Section section, String previousName) {
for (SectionListener listener : this) {
listener.nameChanged(section, previousName);
}
}
}
private Menu menu = null;
private String name = null;
private ArrayList<Item> items = new ArrayList<>();
private SectionListenerList sectionListeners = new SectionListenerList();
public Menu getMenu() {
return menu;
}
public String getName() {
return name;
}
public void setName(String name) {
String previousName = this.name;
if (name != previousName) {
this.name = name;
sectionListeners.nameChanged(this, previousName);
}
}
@Override
public int add(Item item) {
int index = getLength();
insert(item, index);
return index;
}
@Override
public void insert(Item item, int index) {
if (item.getSection() != null) {
throw new IllegalArgumentException("item already has a section.");
}
items.insert(item, index);
item.section = this;
if (menu != null) {
menu.add(item);
sectionListeners.itemInserted(this, index);
}
}
@Override
public Item update(int index, Item item) {
throw new UnsupportedOperationException();
}
@Override
public int remove(Item item) {
int index = items.indexOf(item);
if (index != -1) {
remove(index, 1);
}
return index;
}
@Override
public Sequence<Item> remove(int index, int count) {
Sequence<Item> removed = items.remove(index, count);
for (int i = 0, n = removed.getLength(); i < n; i++) {
Item item = removed.get(i);
item.section = null;
if (menu != null) {
menu.remove(item);
}
}
sectionListeners.itemsRemoved(this, index, removed);
return removed;
}
@Override
public Item get(int index) {
return items.get(index);
}
@Override
public int indexOf(Item item) {
return items.indexOf(item);
}
@Override
public int getLength() {
return items.getLength();
}
@Override
public Iterator<Item> iterator() {
return new ImmutableIterator<>(items.iterator());
}
public ListenerList<SectionListener> getSectionListeners() {
return sectionListeners;
}
}
/**
* Section listener interface.
*/
public interface SectionListener {
/**
* Called when a menu item has been inserted.
*
* @param section The section that is changing.
* @param index The index where the item was inserted.
*/
public void itemInserted(Section section, int index);
/**
* Called when menu items have been removed.
*
* @param section The section that has changed.
* @param index The starting index of the removed items.
* @param removed The sequence of the items that were removed.
*/
public void itemsRemoved(Section section, int index, Sequence<Item> removed);
/**
* Called when a section's name has changed.
*
* @param section The section that changed.
* @param previousName The previous name for this section.
*/
public void nameChanged(Section section, String previousName);
}
/**
* Section sequence implementation.
*/
public final class SectionSequence implements Sequence<Section>, Iterable<Section> {
private SectionSequence() {
}
@Override
public int add(Section section) {
int index = getLength();
insert(section, index);
return index;
}
@Override
public void insert(Section section, int index) {
if (section.menu != null) {
throw new IllegalArgumentException("section already has a menu.");
}
sections.insert(section, index);
section.menu = Menu.this;
for (int i = 0, n = section.getLength(); i < n; i++) {
Menu.this.add(section.get(i));
}
menuListeners.sectionInserted(Menu.this, index);
}
@Override
public Section update(int index, Section section) {
throw new UnsupportedOperationException();
}
@Override
public int remove(Section section) {
int index = sections.indexOf(section);
if (index != -1) {
remove(index, 1);
}
return index;
}
@Override
public Sequence<Section> remove(int index, int count) {
Sequence<Section> removed = sections.remove(index, count);
for (int i = 0, n = removed.getLength(); i < n; i++) {
Section section = removed.get(i);
section.menu = null;
for (Item itemLocal : section) {
Menu.this.remove(itemLocal);
}
}
menuListeners.sectionsRemoved(Menu.this, index, removed);
return removed;
}
public void clear() {
remove(0, sections.getLength());
}
@Override
public Section get(int index) {
return sections.get(index);
}
@Override
public int indexOf(Section itemArgument) {
return sections.indexOf(itemArgument);
}
@Override
public int getLength() {
return sections.getLength();
}
@Override
public Iterator<Section> iterator() {
return new ImmutableIterator<>(sections.iterator());
}
}
private static class MenuListenerList extends WTKListenerList<MenuListener> implements
MenuListener {
@Override
public void sectionInserted(Menu menu, int index) {
for (MenuListener listener : this) {
listener.sectionInserted(menu, index);
}
}
@Override
public void sectionsRemoved(Menu menu, int index, Sequence<Section> removed) {
for (MenuListener listener : this) {
listener.sectionsRemoved(menu, index, removed);
}
}
@Override
public void activeItemChanged(Menu menu, Menu.Item previousActiveItem) {
for (MenuListener listener : this) {
listener.activeItemChanged(menu, previousActiveItem);
}
}
}
private static class MenuItemSelectionListenerList extends
WTKListenerList<MenuItemSelectionListener> implements MenuItemSelectionListener {
@Override
public void itemSelected(Menu.Item menuItem) {
for (MenuItemSelectionListener listener : this) {
listener.itemSelected(menuItem);
}
}
}
private Item item = null;
private ArrayList<Section> sections = new ArrayList<>();
private SectionSequence sectionSequence = new SectionSequence();
private Item activeItem = null;
private MenuListenerList menuListeners = new MenuListenerList();
private MenuItemSelectionListenerList menuItemSelectionListeners = new MenuItemSelectionListenerList();
public Menu() {
installSkin(Menu.class);
}
/**
* @return The parent item of this menu.
*/
public Item getItem() {
return item;
}
public SectionSequence getSections() {
return sectionSequence;
}
public Item getActiveItem() {
return activeItem;
}
private void setActiveItem(Item activeItem) {
Item previousActiveItem = this.activeItem;
if (previousActiveItem != activeItem) {
this.activeItem = activeItem;
menuListeners.activeItemChanged(this, previousActiveItem);
}
}
@Override
public Sequence<Component> remove(int index, int count) {
for (int i = index, n = index + count; i < n; i++) {
Component component = get(i);
for (Section section : sections) {
if (section.indexOf((Menu.Item) component) >= 0) {
throw new UnsupportedOperationException();
}
}
}
// Call the base method to remove the components
return super.remove(index, count);
}
public ListenerList<MenuListener> getMenuListeners() {
return menuListeners;
}
public ListenerList<MenuItemSelectionListener> getMenuItemSelectionListeners() {
return menuItemSelectionListeners;
}
}