/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.core.gui.control.guistack; import java.util.List; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.panel.LayeredPanel; import org.olat.core.gui.components.panel.Panel; import org.olat.core.gui.components.panel.SimpleStackedPanel; import org.olat.core.gui.components.panel.StackedPanel; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.WindowBackOffice; import org.olat.core.gui.control.generic.closablewrapper.CalloutSettings; import org.olat.core.gui.control.generic.closablewrapper.CalloutSettings.CalloutOrientation; import org.olat.core.gui.control.util.ZIndexWrapper; import org.olat.core.gui.render.ValidationResult; import org.olat.core.util.Util; /** * Description: <br> * * @author Felix Jost */ public class GuiStackNiceImpl implements GuiStack { private static final String VELOCITY_ROOT = Util.getPackageVelocityRoot(GuiStackNiceImpl.class); private StackedPanel panel; private StackedPanel modalPanel; private int modalLayers; private WindowBackOffice wbo; private GuiStackNiceImpl() { panel = new SimpleStackedPanel("guistackpanel"); // Use a layered panel instead of a standard panel to support multiple modal layers modalPanel = new LayeredPanel("guistackmodalpanel", 900, 100); modalLayers = 0; } /** * @param initialBaseComponent */ public GuiStackNiceImpl(WindowBackOffice wbo, Component initialBaseComponent) { this(); this.wbo = wbo; setContent(initialBaseComponent); } /** * @see org.olat.core.gui.control.GuiStackHandle#setContent(org.olat.core.gui.components.Component) */ private void setContent(Component newContent) { panel.setContent(newContent); } /** * * @param title the title of the modal dialog, can be null * @param content the component to push as modal dialog */ public void pushModalDialog(Component content) { // wrap the component into a modal foreground dialog with alpha-blended-background final Panel guiMsgPlace = new Panel("guimsgplace_for_modaldialog"); VelocityContainer inset = new VelocityContainer("inset", VELOCITY_ROOT + "/modalDialog.html", null, null) { public void validate(UserRequest ureq, ValidationResult vr) { super.validate(ureq, vr); // just before rendering, we need to tell the windowbackoffice that we are a favorite for accepting gui-messages. // the windowbackoffice doesn't know about guimessages, it is only a container that keeps them for one render cycle List<ZIndexWrapper> zindexed = wbo.getGuiMessages(); zindexed.add(new ZIndexWrapper(guiMsgPlace, 10)); } }; inset.put("cont", content); inset.put("guimsgplace", guiMsgPlace); int zindex = 900 + (modalLayers * 100) + 5; inset.contextPut("zindexoverlay", zindex+1); inset.contextPut("zindexshim", zindex); inset.contextPut("zindexarea", zindex+5); inset.contextPut("zindexextwindows", zindex+50); modalPanel.pushContent(inset); // the links in the panel cannot be clicked because of the alpha-blended background over it, but if user chooses own css style -> // FIXME:fj:b panel.setEnabled(false) causes effects if there is an image component in the panel -> the component is not dispatched // and thus renders inline and wastes the timestamp. // Needed:solution (a) a flag (a bit of the mode indicator of the urlbuilder can be used) to indicate that a request always needs to be delivered even // if the component or a parent is not enabled. // alternative solution(b): wrap the imagecomponent into a controller and use a mapper // alternative solution(c): introduce a flag to the component to say "dispatch always", even if a parent component is not enabled // // - solution a would be easy, but would allow for forced dispatching by manipulating the url's flag. // for e.g. a Link button ("make me admin") that is disabled this is a security breach. // - solution b needs some wrapping, the advantage (for images) would be that they are cached by the browser if requested more than once // within a controller // - solution c is a safe and easy way to allow dispatching (only in case a mediaresource is returned as a result of the dispatching) even // if parent elements are not enabled // proposal: fix for 5.1.0 with solution c; for 5.0.1 the uncommenting of the line below is okay. //if (modalLayers == 0) panel.setEnabled(false); modalLayers++; } @Override public void pushCallout(Component content, String targetId, CalloutSettings settings) { // wrap the component into a modal foreground dialog with alpha-blended-background final Panel guiMsgPlace = new Panel("guimsgplace_for_callout"); VelocityContainer inset = new VelocityContainer("inset", VELOCITY_ROOT + "/callout.html", null, null) { public void validate(UserRequest ureq, ValidationResult vr) { super.validate(ureq, vr); // just before rendering, we need to tell the windowbackoffice that we are a favorite for accepting gui-messages. // the windowbackoffice doesn't know about guimessages, it is only a container that keeps them for one render cycle List<ZIndexWrapper> zindexed = wbo.getGuiMessages(); zindexed.add(new ZIndexWrapper(guiMsgPlace, 10)); } }; inset.put("cont", content); inset.put("guimsgplace", guiMsgPlace); inset.contextPut("guimsgtarget", targetId); int zindex = 900 + (modalLayers * 100) + 5; inset.contextPut("zindexoverlay", zindex+1); inset.contextPut("zindexshim", zindex); inset.contextPut("zindexarea", zindex+5); inset.contextPut("zindexextwindows", zindex+50); if(settings != null) { inset.contextPut("arrow", settings.isArrow()); inset.contextPut("orientation", settings.getOrientation().name()); } else { inset.contextPut("arrow", Boolean.TRUE); inset.contextPut("orientation", CalloutOrientation.bottom.name()); } modalPanel.pushContent(inset); modalLayers++; } /** * @see org.olat.core.gui.control.GuiStackHandle#pushContent(org.olat.core.gui.components.Component) */ public void pushContent(Component newContent) { if (modalLayers > 0) { // if, in a modaldialog, a push-to-main-area is issued, put it on the modal stack. // e.g. a usersearch (in modal mode) offers some subfunctionality which needs the whole screen. // probably rarely the case, but we support it. pushModalDialog(newContent); } else { panel.pushContent(newContent); } } /** * @see org.olat.core.gui.control.GuiStackHandle#popContent() */ public void popContent() { if (modalLayers > 0) { modalLayers--; modalPanel.popContent(); if (modalLayers == 0) { // unblock background panel //panel.setEnabled(true); } } else { panel.popContent(); } } /** * @return */ public StackedPanel getPanel() { return panel; } /** * @return Returns the modalPanel. */ public StackedPanel getModalPanel() { return modalPanel; } }