/* * This file is part of LibrePlan * * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e * Desenvolvemento Tecnolóxico de Galicia * Copyright (C) 2010-2011 Igalia, S.L. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.zkoss.ganttz.util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.HtmlNativeComponent; import org.zkoss.zk.ui.Page; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.OpenEvent; import org.zkoss.zul.Menuitem; import org.zkoss.zul.Menupopup; import org.zkoss.zul.Menuseparator; import org.zkoss.zul.impl.XulElement; /** * Create context menu for right-click mouse. * * @param <T> */ public class MenuBuilder<T extends XulElement> { private final List<T> elements; private final List<Item> items = new ArrayList<>(); private Component root; private T referenced; private MenuBuilder(Page page, Collection<? extends T> elements) { this.elements = new ArrayList<>(elements); this.root = findVisibleOn(getRoots(page)); } public static <T extends XulElement> MenuBuilder<T> on(Page page, Collection<T> elements) { return new MenuBuilder<>(page, elements); } public static <T extends XulElement> MenuBuilder<T> on(Page page, T... elements) { return on(page, Arrays.asList(elements)); } public interface ItemAction<T> { void onEvent(T chosen, Event event); } private class Item { private final String name; private final String icon; private final ItemAction<T> action; Item(String name, String icon, ItemAction<T> action) { this.name = name; this.icon = icon; this.action = action; } Menuitem createMenuItem() { Menuitem result = new Menuitem(); result.setLabel(name); if ( icon != null ) { result.setImage(icon); } return result; } } private static List<Component> getRoots(Page page) { List<Component> result = new ArrayList<>(); Component current = page.getFirstRoot(); while (current != null) { result.add(current); current = current.getNextSibling(); } return result; } private static Component findVisibleOn(Collection<? extends Component> candidates) { for (Component each : candidates) { if ( each.isVisible() ) { return each; } } throw new RuntimeException("not found visible component on which to attach the menu"); } public MenuBuilder<T> item(String name, String icon, ItemAction<T> itemAction) { if ( name == null ) { throw new IllegalArgumentException("name cannot be null"); } if ( itemAction == null ) { throw new IllegalArgumentException("itemAction cannot be null"); } items.add(new Item(name, icon, itemAction)); return this; } public Menupopup createWithoutSettingContext() { return create(false); } public Menupopup create() { return create(true); } private Menupopup create(boolean setContext) { Menupopup result = new Menupopup(); result.addEventListener("onOpen", event -> { OpenEvent openEvent = (OpenEvent) event; referenced = (T) openEvent.getReference(); }); for (final Item item : items) { if ( !"separator".equals(item.name) ) { Menuitem menuItem = item.createMenuItem(); menuItem.addEventListener("onClick", event -> { ItemAction<T> action = item.action; action.onEvent(referenced, event); }); result.appendChild(menuItem); } else { Menuseparator separator = new Menuseparator(); result.appendChild(separator); } } insertInRootComponent(result); if ( setContext ) { for (T element : elements) { element.setContext(result); } } return result; } private void insertInRootComponent(Menupopup result) { ArrayList<Component> children = new ArrayList<>(root.getChildren()); Collections.reverse(children); // The Menupopup cannot be inserted after a HtmlNativeComponent, so we try to avoid it if ( children.isEmpty() ) { root.appendChild(result); } for (Component child : children) { if ( !(child instanceof HtmlNativeComponent) ) { root.insertBefore(result, child); return; } } throw new RuntimeException("all children of " + root + " are html native"); } }