/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed 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.uberfire.client.views.pfly.listbar; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Instance; import javax.inject.Inject; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.event.logical.shared.BeforeSelectionEvent; import com.google.gwt.event.logical.shared.BeforeSelectionHandler; import com.google.gwt.event.logical.shared.SelectionEvent; import com.google.gwt.event.logical.shared.SelectionHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.ResizeComposite; import com.google.gwt.user.client.ui.Widget; import org.gwtbootstrap3.client.ui.AnchorListItem; import org.gwtbootstrap3.client.ui.Button; import org.gwtbootstrap3.client.ui.ButtonGroup; import org.gwtbootstrap3.client.ui.DropDownMenu; import org.gwtbootstrap3.client.ui.Panel; import org.gwtbootstrap3.client.ui.PanelBody; import org.gwtbootstrap3.client.ui.PanelHeader; import org.gwtbootstrap3.client.ui.constants.ButtonSize; import org.gwtbootstrap3.client.ui.constants.Pull; import org.gwtbootstrap3.client.ui.constants.Toggle; import org.jboss.errai.ioc.client.container.IOCResolutionException; import org.jboss.errai.security.shared.api.identity.User; import org.uberfire.client.menu.AuthFilterMenuVisitor; import org.uberfire.client.util.Layouts; import org.uberfire.client.views.pfly.maximize.MaximizeToggleButton; import org.uberfire.client.workbench.PanelManager; import org.uberfire.client.workbench.panels.MaximizeToggleButtonPresenter; import org.uberfire.client.workbench.panels.WorkbenchPanelPresenter; import org.uberfire.client.workbench.part.WorkbenchPartPresenter; import org.uberfire.client.workbench.widgets.dnd.WorkbenchDragAndDropManager; import org.uberfire.client.workbench.widgets.listbar.ListBarWidget; import org.uberfire.client.workbench.widgets.listbar.ListbarPreferences; import org.uberfire.client.workbench.widgets.listbar.ResizeFocusPanel; import org.uberfire.commons.data.Pair; import org.uberfire.mvp.Command; import org.uberfire.security.authz.AuthorizationManager; import org.uberfire.workbench.model.PartDefinition; import org.uberfire.workbench.model.menu.MenuCustom; import org.uberfire.workbench.model.menu.MenuGroup; import org.uberfire.workbench.model.menu.MenuItem; import org.uberfire.workbench.model.menu.MenuItemCommand; import org.uberfire.workbench.model.menu.impl.BaseMenuVisitor; import static com.google.gwt.dom.client.Style.Display.BLOCK; import static com.google.gwt.dom.client.Style.Display.NONE; import static org.uberfire.plugin.PluginUtil.ensureIterable; /** * Implementation of ListBarWidget based on PatternFly components. */ @Dependent public class ListBarWidgetImpl extends ResizeComposite implements ListBarWidget { private static ListBarWidgetBinder uiBinder = GWT.create(ListBarWidgetBinder.class); final Map<PartDefinition, FlowPanel> partContentView = new HashMap<PartDefinition, FlowPanel>(); /** * Preferences bean that applications can optionally provide. If this injection is unsatisfied, default settings are used. */ @Inject Instance<ListbarPreferences> optionalListBarPrefs; @Inject PanelManager panelManager; @UiField FocusPanel container; @UiField PartListDropdown titleDropDown; @UiField PanelHeader header; @UiField Panel panel; @UiField ButtonGroup contextMenu; @UiField Button closeButton; @UiField ButtonGroup toolBar; @UiField MaximizeToggleButton maximizeButton; /** * Wraps maximizeButton, which is the view. */ MaximizeToggleButtonPresenter maximizeButtonPresenter; @UiField PanelBody content; WorkbenchPanelPresenter presenter; LinkedHashSet<PartDefinition> parts = new LinkedHashSet<>(); Pair<PartDefinition, FlowPanel> currentPart; @Inject private AuthorizationManager authzManager; @Inject private User identity; @PostConstruct void postConstruct() { initWidget(uiBinder.createAndBindUi(this)); maximizeButtonPresenter = new MaximizeToggleButtonPresenter(maximizeButton); titleDropDown.setHideOnSingleElement(getListbarPreferences().isHideTitleDropDownOnSingleElement()); setupEventHandlers(); Layouts.setToFillParent(this); scheduleResize(); } void setupEventHandlers() { this.container.addMouseOutHandler(event -> titleDropDown.removeStyleName("open")); this.container.addFocusHandler(event -> { if (currentPart != null && currentPart.getK1() != null) { selectPart(currentPart.getK1()); } }); this.maximizeButton.addClickHandler(event -> { if (maximizeButton.isMaximized()) { panelManager.onPartMaximized(currentPart.getK1()); } else { panelManager.onPartMinimized(currentPart.getK1()); } }); closeButton.addClickHandler(event -> { if (currentPart != null) { if (maximizeButton.isMaximized()) { panelManager.onPartMinimized(currentPart.getK1()); } panelManager.closePart(currentPart.getK1()); } }); titleDropDown.addSelectionHandler(event -> selectPart(event.getSelectedItem())); titleDropDown.addCloseHandler(event -> panelManager.closePart(event.getTarget())); } ListbarPreferences getListbarPreferences() { try { return optionalListBarPrefs.isUnsatisfied() ? new ListbarPreferences() : optionalListBarPrefs.get(); } catch (IOCResolutionException e) { return new ListbarPreferences(); } } @Override public void enableDnd() { titleDropDown.enableDragAndDrop(); } @Override public void disableDnd() { titleDropDown.disableDragAndDrop(); } @Override public void setPresenter(final WorkbenchPanelPresenter presenter) { this.presenter = presenter; } @Override public void setDndManager(final WorkbenchDragAndDropManager dndManager) { this.titleDropDown.setDndManager(dndManager); } @Override public void clear() { contextMenu.clear(); header.setVisible(false); content.clear(); parts.clear(); partContentView.clear(); titleDropDown.clear(); currentPart = null; } @Override public void addPart(final WorkbenchPartPresenter.View view) { final PartDefinition partDefinition = view.getPresenter().getDefinition(); if (parts.contains(partDefinition)) { selectPart(partDefinition); return; } parts.add(partDefinition); final FlowPanel panel = new FlowPanel(); Layouts.setToFillParent(panel); panel.add(view); content.add(panel); // IMPORTANT! if you change what goes in this map, update the remove(PartDefinition) method partContentView.put(partDefinition, panel); if (partDefinition.isSelectable()) { titleDropDown.addPart(view); } header.setVisible(true); resizePanelBody(); scheduleResize(); } @Override public void changeTitle(final PartDefinition part, final String title, final IsWidget titleDecoration) { if (part.isSelectable()) { titleDropDown.changeTitle(part, title, titleDecoration); } } @Override public boolean selectPart(final PartDefinition part) { if (!parts.contains(part)) { //not necessary to check if current is part return false; } if (currentPart != null) { if (currentPart.getK1().equals(part)) { return true; } parts.add(currentPart.getK1()); panelManager.onPartHidden(currentPart.getK1()); currentPart.getK2().getElement().getStyle().setDisplay(NONE); } currentPart = Pair.newPair(part, partContentView.get(part)); currentPart.getK2().getElement().getStyle().setDisplay(BLOCK); parts.remove(currentPart.getK1()); if (part.isSelectable()) { titleDropDown.selectPart(part); setupContextMenu(); header.setVisible(true); } else { header.setVisible(false); } scheduleResize(); resizePanelBody(); SelectionEvent.fire(ListBarWidgetImpl.this, part); return true; } void setupContextMenu() { contextMenu.clear(); final WorkbenchPartPresenter.View part = (WorkbenchPartPresenter.View) currentPart.getK2().getWidget(0); if (part.getPresenter().getMenus() != null && part.getPresenter().getMenus().getItems().size() > 0) { for (final MenuItem menuItem : part.getPresenter().getMenus().getItems()) { final Widget result = makeItem(menuItem, true); if (result != null) { contextMenu.add(result); } } } contextMenu.setVisible(contextMenu.getWidgetCount() > 0); } @Override public boolean remove(final PartDefinition part) { if (part.isSelectable()) { titleDropDown.removePart(part); } if (currentPart != null && currentPart.getK1().asString().equals(part.asString())) { PartDefinition nextPart = getNextPart(part); if (nextPart != null) { presenter.selectPart(nextPart); } else { clear(); } } boolean removed = parts.remove(part); FlowPanel view = partContentView.remove(part); if (view != null) { // FIXME null check should not be necessary, but sometimes the entry in partContentView is missing! content.remove(view); } if (currentPart == null) { header.setVisible(false); } resizePanelBody(); scheduleResize(); return removed; } PartDefinition getNextPart(PartDefinition currentSelectedPart) { PartDefinition nextPart = null; for (PartDefinition p : getUnselectedParts()) { if (!currentSelectedPart.asString().equals(p.asString())) { if (nextPart == null || p.isSelectable()) { nextPart = p; } if (p.isSelectable()) { break; } } } return nextPart; } @Override public void setFocus(final boolean hasFocus) { } @Override public void addOnFocusHandler(final Command command) { } @Override public int getPartsSize() { if (currentPart == null) { return 0; } return parts.size() + 1; } @Override public Collection<PartDefinition> getParts() { List<PartDefinition> allParts = new ArrayList<>(); if (currentPart == null) { return parts; } allParts.add(currentPart.getK1()); allParts.addAll(parts); return Collections.unmodifiableList(allParts); } @Override public HandlerRegistration addBeforeSelectionHandler(final BeforeSelectionHandler<PartDefinition> handler) { return addHandler(handler, BeforeSelectionEvent.getType()); } @Override public HandlerRegistration addSelectionHandler(final SelectionHandler<PartDefinition> handler) { return addHandler(handler, SelectionEvent.getType()); } @Override public void onResize() { if (!isAttached()) { return; } super.onResize(); // FIXME only need to do this for the one visible part .. need to call onResize() when switching parts anyway for (int i = 0; i < content.getWidgetCount(); i++) { final FlowPanel container = (FlowPanel) content.getWidget(i); final Widget containedWidget = container.getWidget(0); if (containedWidget instanceof RequiresResize) { ((RequiresResize) containedWidget).onResize(); } } } protected Widget makeItem(final MenuItem item, boolean isRoot) { Widget[] menuWidget = new Widget[]{null}; item.accept(new AuthFilterMenuVisitor(authzManager, identity, new BaseMenuVisitor() { @Override public boolean visitEnter(MenuGroup menuGroup) { menuWidget[0] = makeMenuGroup(menuGroup, isRoot); return false; } @Override public void visit(MenuItemCommand menuItemCommand) { menuWidget[0] = makeMenuItemCommand(menuItemCommand, isRoot); } @Override public void visit(MenuCustom<?> menuCustom) { menuWidget[0] = makeMenuCustom(menuCustom); } })); return menuWidget[0]; } private Widget makeMenuItemCommand(final MenuItemCommand cmdItem, final boolean isRoot) { if (isRoot) { final Button button = GWT.create(Button.class); button.setText(cmdItem.getCaption()); button.setSize(ButtonSize.SMALL); button.setEnabled(cmdItem.isEnabled()); button.addClickHandler(event -> cmdItem.getCommand().execute()); cmdItem.addEnabledStateChangeListener(button::setEnabled); return button; } else { final AnchorListItem navbarLink = GWT.create(AnchorListItem.class); navbarLink.setText(cmdItem.getCaption()); if (!cmdItem.isEnabled()) { navbarLink.addStyleName("disabled"); } navbarLink.addClickHandler(event -> cmdItem.getCommand().execute()); cmdItem.addEnabledStateChangeListener(enabled -> { if (enabled) { navbarLink.removeStyleName("disabled"); } else { navbarLink.addStyleName("disabled"); } }); return navbarLink; } } private Widget makeMenuGroup(final MenuGroup groups, final boolean isRoot) { if (isRoot) { final List<Widget> widgetList = new ArrayList<>(); for (final MenuItem _item : ensureIterable(groups.getItems())) { final Widget widget = makeItem(_item, false); if (widget != null) { widgetList.add(widget); } } if (widgetList.isEmpty()) { return null; } return makeDropDownMenuButton(groups.getCaption(), widgetList); } else { final List<Widget> widgetList = new ArrayList<>(); for (final MenuItem _item : groups.getItems()) { final Widget result = makeItem(_item, false); if (result != null) { widgetList.add(result); } } if (widgetList.isEmpty()) { return null; } return makeDropDownMenuButton(groups.getCaption(), widgetList); } } private Widget makeMenuCustom(final MenuCustom item) { final Object result = item.build(); if (result instanceof Widget) { return (Widget) result; } return null; } private Widget makeDropDownMenuButton(final String caption, final List<Widget> widgetList) { final ButtonGroup buttonGroup = GWT.create(ButtonGroup.class); final Button dropdownButton = GWT.create(Button.class); dropdownButton.setText(caption); dropdownButton.setDataToggle(Toggle.DROPDOWN); dropdownButton.setSize(ButtonSize.SMALL); final DropDownMenu dropDownMenu = GWT.create(DropDownMenu.class); dropDownMenu.setPull(Pull.RIGHT); for (final Widget _item : widgetList) { dropDownMenu.add(_item); } buttonGroup.add(dropdownButton); buttonGroup.add(dropDownMenu); return buttonGroup; } private void scheduleResize() { Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() { @Override public void execute() { onResize(); } }); } void resizePanelBody() { //When an Item is added to the PanelHeader recalculate the PanelBody size. //This cannot be performed in either the @PostConstruct or onAttach() methods as at //these times the PanelHeader may not have any content and hence have no size. if (currentPart != null && !currentPart.getK1().isSelectable()) { content.getElement().getStyle().setProperty("height", "100%"); } else { content.getElement().getStyle().setProperty("height", "calc(100% - " + header.getOffsetHeight() + "px)"); } } /** * Returns the toggle button, that can be used to trigger maximizing and unmaximizing * of the panel containing this list bar. Make the button visible by calling {@link Widget#setVisible(boolean)} * and set its maximize and unmaximize actions with {@link MaximizeToggleButton#setMaximizeCommand(Command)} and * {@link MaximizeToggleButton#setUnmaximizeCommand(Command)}. */ @Override public MaximizeToggleButtonPresenter getMaximizeButton() { return maximizeButtonPresenter; } @Override public boolean isDndEnabled() { return this.titleDropDown.isDndEnabled(); } @Override public void enableClosePart() { closeButton.setVisible(true); } @Override public void disableClosePart() { closeButton.setVisible(false); } Collection<PartDefinition> getUnselectedParts() { return parts; } interface ListBarWidgetBinder extends UiBinder<ResizeFocusPanel, ListBarWidgetImpl> { } }