Package org.olat.core.gui.control.winmgr

Source Code of org.olat.core.gui.control.winmgr.WindowBackOfficeImpl

/**
* 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) 1999-2006 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.core.gui.control.winmgr;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import org.olat.core.gui.GlobalSettings;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.WindowManager;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.ComponentRenderer;
import org.olat.core.gui.components.Window;
import org.olat.core.gui.components.util.ComponentUtil;
import org.olat.core.gui.control.ChiefController;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.JSAndCSSAdderImpl;
import org.olat.core.gui.control.WindowBackOffice;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.guistack.GuiStack;
import org.olat.core.gui.control.guistack.GuiStackNiceImpl;
import org.olat.core.gui.control.guistack.GuiStackSimpleImpl;
import org.olat.core.gui.control.info.WindowControlInfo;
import org.olat.core.gui.control.pushpoll.WindowCommand;
import org.olat.core.gui.control.state.ExtendedControllerState;
import org.olat.core.gui.control.state.GUIPath;
import org.olat.core.gui.dev.controller.DevelopmentController;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.render.intercept.InterceptHandler;
import org.olat.core.gui.render.intercept.InterceptHandlerInstance;
import org.olat.core.gui.render.intercept.debug.GuiDebugDispatcherController;
import org.olat.core.helpers.Settings;
import org.olat.core.id.context.BusinessControl;
import org.olat.core.logging.AssertException;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.i18n.I18nManager;
import org.olat.core.util.i18n.ui.I18nUIFactory;
import org.olat.core.util.i18n.ui.InlineTranslationInterceptHandlerController;

/**
* Description:<br>
* impl of windowbackoffice - responsible for several activities around a (browser)window
*
* <P>
* Initial Date: 10.02.2007 <br>
* @author Felix Jost, http://www.goodsolutions.ch
*/
public class WindowBackOfficeImpl implements WindowBackOffice {

  private OLog log = Tracing.createLoggerFor(WindowBackOfficeImpl.class);
 
  private final WindowManagerImpl winmgrImpl;
  private Window window;
  private ChiefController windowOwner;
 
  private InterceptHandler linkedInterceptHandler;
  // not private to avoid synthetic accessor
  InterceptHandler debug_interceptHandler = null;
  InterceptHandler inlineTranslation_interceptHandler = null;
  private AjaxController ajaxC;
  private GuiDebugDispatcherController guidebugC;
  private InlineTranslationInterceptHandlerController inlineTranslationC;
 
  private BusinessControl bookmarkBusinessControl;
   
  private String iframeName;
 
  private Map<String, Object> data = new HashMap<String, Object>(); // request-transient render-related data
 
  private List<GUIPath> guipathentries = new ArrayList<GUIPath>();
  private int curGPpos = -1; // the position of the current guipath in the list: the one that was just dispatched (=the current state), or just restored by "backing"
  private WindowControlInfo rootWCI; // the topmost windowcontrolinfo, typically the one belonging to the windowcontrol which belongs to a basechiefcontroller
 
  private transient List<GenericEventListener> cycleListeners = new CopyOnWriteArrayList<GenericEventListener>();
  private WindowControlInfo newWci = null;
 
  private BackHandler backHandler = null;
 
  /**
   *
   */
  WindowBackOfficeImpl(final WindowManagerImpl winmgrImpl, String windowName, ChiefController windowOwner, int wboId) {
    this.winmgrImpl = winmgrImpl;
    this.windowOwner = windowOwner;
    this.iframeName = "oaa"+wboId;
    window = new Window(windowName, this);
   
   
    // TODO make simpler, we do only need to support one intercept handler at a time!
    linkedInterceptHandler = new InterceptHandler() {
      public InterceptHandlerInstance createInterceptHandlerInstance() {
        InterceptHandler debugH = debug_interceptHandler;
        InterceptHandler screenReaderH = winmgrImpl.getScreenreader_interceptHandler();
        InterceptHandler inlineTranslationH = inlineTranslation_interceptHandler;
       
        final InterceptHandlerInstance debugI = debugH == null? null: debugH.createInterceptHandlerInstance();
        final InterceptHandlerInstance screenReaderI = screenReaderH == null? null: screenReaderH.createInterceptHandlerInstance();
        final InterceptHandlerInstance inlineTranslationI = (inlineTranslationH == null ? null : inlineTranslationH.createInterceptHandlerInstance());
       
        return new InterceptHandlerInstance() {

          public ComponentRenderer createInterceptComponentRenderer(ComponentRenderer originalRenderer) {
            ComponentRenderer toUse = originalRenderer;
            if (screenReaderI != null) {
              toUse = screenReaderI.createInterceptComponentRenderer(toUse);
            }
            if (winmgrImpl.isShowDebugInfo() && debugI != null) {
              toUse = debugI.createInterceptComponentRenderer(toUse);
            }
            if (I18nManager.getInstance().isCurrentThreadMarkLocalizedStringsEnabled() && inlineTranslationI != null) {
              toUse = inlineTranslationI.createInterceptComponentRenderer(toUse);
            }
            return toUse;
          }};
      }};
   
   
   
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#getWindow()
   */
  public Window getWindow() {
    return window;
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#createDevelopmentController(org.olat.core.gui.UserRequest, org.olat.core.gui.control.WindowControl)
   */
  public Controller createDevelopmentController(UserRequest ureq, WindowControl windowControl) {
    DevelopmentController dc = new DevelopmentController(ureq, windowControl,this);
    return dc;
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#getGlobalSettings()
   */
  public GlobalSettings getGlobalSettings() {
    return winmgrImpl.getGlobalSettings();
  }

  /**
   * @return
   */
  public JSAndCSSAdderImpl createJSAndCSSAdder() {
    JSAndCSSAdderImpl jcImpl = new JSAndCSSAdderImpl(this);
    return jcImpl;
  }

  /**
   * @see org.olat.core.gui.control.WindowBackOffice#sendCommandTo(org.olat.core.gui.control.winmgr.Command)
   */
  public void sendCommandTo(Command wco) {
    ajaxC.sendCommandTo(new WindowCommand(this,wco));
  }

  /**
   * @param wrapHTML
   * @return
   */
  public MediaResource extractCommands(boolean wrapHTML) {
    return ajaxC.extractMediaResource(wrapHTML);
  }

  /**
   * @return
   */
  public InterceptHandler getInterceptHandler() {
    return linkedInterceptHandler;
  }

  /**
   * @param ureq
   * @param windowControl
   * @return the debug controller (not visible on screen, only in debug mode it wraps around each component for dispatching of gui debug info)
   */
  public Controller createDebugDispatcherController(UserRequest ureq, WindowControl windowControl) {
    guidebugC = new GuiDebugDispatcherController(ureq, windowControl);
    this.debug_interceptHandler  = guidebugC;
    return guidebugC;
  }

  /**
   * Factory method to create the inline translation tool dispatcher controller.
   * This implicitly sets the translation controller on the window back office
   *
   * @param ureq
   * @param windowControl
   * @return
   */
  public Controller createInlineTranslationDispatcherController(UserRequest ureq, WindowControl windowControl) {
    if (inlineTranslationC != null) throw new AssertException("Can't set the inline translation dispatcher twice!", null);
    inlineTranslationC = I18nUIFactory.createInlineTranslationIntercepHandlerController(ureq, windowControl);
    this.inlineTranslation_interceptHandler  = inlineTranslationC;
    return inlineTranslationC;
  }

  public Controller createAJAXController(UserRequest ureq) {
    boolean ajaxEnabled = winmgrImpl.isAjaxEnabled();
    ajaxC = new AjaxController(ureq, this, ajaxEnabled, iframeName);
    return ajaxC;
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#isDebuging()
   */
  public boolean isDebuging() {
    return Settings.isDebuging();
  }

  public WindowManagerImpl getWinmgrImpl() {
    return winmgrImpl;
  }

  /**
   *
   */
  public void dispose() {
    windowOwner.dispose();
  }

  /**
   * @param enabled
   */
  public void setAjaxEnabled(boolean enabled) {
    if (ajaxC != null) ajaxC.setAjaxEnabled(enabled);
  }

  /**
   * @param enabled
   */
  public void setHighLightingEnabled(boolean enabled) {
    if (ajaxC != null) ajaxC.setHighLightingEnabled(enabled);
  }

  /**
   * @param enabled
   */
  public void setShowJSON(boolean enabled) {
    if (ajaxC != null) ajaxC.setShowJSON(enabled);
  }

  /**
   * @param refreshInterval
   */
  public void setRequiredRefreshInterval(int refreshInterval) {
    if (ajaxC != null) ajaxC.setPollPeriod(refreshInterval);
  }

  /**
   * @param showDebugInfo
   */
  public void setShowDebugInfo(boolean showDebugInfo) {
    if (guidebugC != null) {
      guidebugC.setShowDebugInfo(showDebugInfo);
    }
  }
 
  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#getWindowManager()
   */
  public WindowManager getWindowManager() {
    return winmgrImpl;
  }

  public void informControllerDispatched(WindowControl wControl, Controller controller, Component source, Event event) {
   
    // add the gui path to the gui-path-history, if there has been a change at all (otherwise that transition is ignored;
    // when the user presses the back-button of the browser, he/she expects that some change on the screen happens)
   
    // VALIDATE events are special events which are to be ignored here - they are no user-dispatched events, but a result thereof.
    if (event == ComponentUtil.VALIDATE_EVENT) return;
   
    GUIPath guiPath = new GUIPathImpl();
    WindowControlInfo curwcI;
    if (newWci == null) {
      curwcI = wControl.getWindowControlInfo();
    } else {
      // take the special, supplied info
      curwcI = newWci;
      newWci = null;
    }
     
    WindowControlInfo prev = null;
    do {
      ExtendedControllerState state = curwcI.getExtendedControllerState();
      guiPath.addStateInfo(state);
      prev = curwcI;
    } while ((curwcI = curwcI.getParentWindowControlInfo()) != null);
    // the prev is now the windowcontrol with a null parent = the root
    // -> remember for later back-button handling/traversing
   
    if (rootWCI == null) {
      rootWCI = prev;
    } else if (rootWCI != prev) {
      //REVIEW:2008-02-28 PB: this happens for example in DOCKCONTROLLER in popuwindow
      //REVIEW:2008-02-28 PB: gets important for BackHandling
      log.warn("WindowControlInfo different root now?? for Controller:"+controller.getClass().getCanonicalName());
      //throw new AssertException("different root now??");
    }
    // clear old forward gui paths if needed
    int esize = guipathentries.size();
    if (curGPpos < esize-1) { // we are not rightmost,
      // that is we have traversed back in history -> clear forward history of gui paths, because those are dumped by the browser. (concept of only having one forward path)
      guipathentries.subList(curGPpos+1, esize).clear();
      guipathentries.add(guiPath);
      //System.out.println("first guipath on click after back:"+guiPath.toString());
      curGPpos++;
    } else { // curGP >= esize-1
      if (esize > 0) {
        // only add the new guipath if it is not the same as the previous
        // (meaning that no state change occured and thus should not be remembered -
        // we don't user to experience "nothing" when they press the browser back button)
        GUIPath latestGP = guipathentries.get(esize-1);
        if (!guiPath.isSame(latestGP)) {
          guipathentries.add(guiPath);
          //System.out.println("new guipath:"+guiPath.toString());
          curGPpos++;
        } else {
          //System.out.println("same guipath");
          // else ignore in history, because there is no (noticed) change
        }
      } else {
        // first entry: add
        guipathentries.add(guiPath);
        //System.out.println("first new guipath:"+guiPath.toString());
        curGPpos++;
      }
    }
   
  }
 
  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#informControllerCreated(org.olat.core.gui.control.DefaultController)
   */
  public void informControllerCreated(WindowControl wControl, Controller controller) {
    //System.out.println(controller);
  }
 
  /**
   * diff == 0 -> reload (->ignore, so it will cause a simple rerendering)<br>
   * diff < 0  -> browser-back<br>
   * diff > 0  -> browser-forward<br>
   * @param diff
   */
  public void browserBackOrForward(UserRequest ureq, int diff) {
    // 1. check if there is a controller which registered for a custom behaviour
    // e.g. a iqruncontroller (= a test or a survey, which should ignore or block the back/forward button)
    // the controller should <init>: acquireBackHandling(), and upon dispose: releaseBackHandling()
    if (backHandler != null) {
      backHandler.browserBackOrForward(ureq, diff);
    } else {
      // no special handler: back means we revert to the latest gui path.
      //
      // if back: check in guipathhistory: fetch latest guipath -> reactivate.
      // the reactivate is done by traversing from the root windowcontrolinfo to its children.
      // the following algorithm is used to determine which child matches the current path of the guipath:
      // a guipathentry consists of
      // a) the classname of the owning controller
      // b) the hashcode of the owning controller
      // c) the container-path of the initial-component of the owning controller
     
      // for each windowcontrolinfo, starting at the root entry:
      // 1. take the current guipathentry (and inc the pos)
      // 2. take the current windowcontrolinfo and apply the guipathentry to it, meaning the owning controller will adjust its
      //    state accordingly if needed. the state change is specific per controller and must be programmed.
      //    the notification is done via ...
      // 3. search the correct childwindowcontrolinfo where the next guipathentry is applied:
      //    if (classname and hashcode match) take this child (=same instance), else if (classname matches) then
      //    take this if only one child matches. if there are at least two children with the same classname that matches,
      //    then (a rather rare case) verify which has the matching container-parent-hierarchy.
      //    if no child is found, then the current controller did not adjust its state correctly (bug or not implemented) -> stop
      // 4. make the matching child windowcontrolinfo current and proceed with step 1.
 
      if (rootWCI == null) {
        // this may happen -only- right after the login when the home page appears and as the next click the browser-reload button is pressed.
        // to avoid this rare case red screen, we return silently.
        if (diff == 0) { // reload
          return;         
        } else {
          throw new AssertException("rootWCI was null: never dispatched, but pressed back or forward??");
        }
      }
      int esize = guipathentries.size();
     
      // BACK case
      if (diff < 0) {      
        if (esize < 1) {
          //throw new AssertException("back, but no entries??");
          // this case can happen when the user presses reload on the first page after the login (olat home page)
          return;
        }
        // get the guipath to restore our state : the latest one is the current one which needs to be backed
        GUIPath guiPath = guipathentries.get(curGPpos);
        curGPpos--;
        //System.out.println("backing using guipath:"+guiPath);
        adjustState(true, rootWCI, guiPath, ureq);     
      } else if (diff > 0) { // handle FORWARD case
        // ignore when we have reached then end of the history.
        // when e.g. a user traverses back in history using one click, but traverses more than one step, then olat can only detect "back", but
        // not how much (it could be done, but 99,9% the use case I guess to be the "normal" back, that is, hitting the browser back button (maybe repeatedly)).
        // so the browser may offer the "forward" button, even though we cannot forward.
        if (curGPpos == esize-1) return;
        GUIPath guiPath = guipathentries.get(curGPpos+1);
        //System.out.println("forwarding using guipath:"+guiPath);
        curGPpos++;
        adjustState(false, rootWCI, guiPath, ureq)
      }
    }
  }
 
  private void adjustState(boolean back, WindowControlInfo curWCInfo, GUIPath guiPath, UserRequest ureq) {
    int ecnt = guiPath.getExtendedControllerStateCount();
    // traverse from the parent to the children and grandchildren
    for (int i = ecnt-1; i >= 0 && curWCInfo != null; i--) {
      ExtendedControllerState ecstate = guiPath.getExtendedControllerStateAt(i);
      curWCInfo.adjustControllerState(back, ecstate, ureq);
      if (i >= 1) { // for all but the leaf
        ExtendedControllerState extChildState = guiPath.getExtendedControllerStateAt(i-1);
        // changeing a controller's state means there are maybe child controllers disposed and others newly created
        curWCInfo = findMatchingChild(extChildState, curWCInfo);
        // can return null if no child is found (see comments for step 3 above)
        if (curWCInfo == null) return;
        // else found: proceed with this child and the next guiPath Entry
      }
    }
    // either no matching child was found or the bottom of the guiPath-chain was reached -> we are done -> return and later rerender gui
  }
   
  /**
   * @param curWCI
   * @return
   */
  private WindowControlInfo findMatchingChild(ExtendedControllerState childState, WindowControlInfo curWCI) {
    List<WindowControlInfo> children = curWCI.getChildren();
    if (children == null) return null;
    List<WindowControlInfo> classMatches = new ArrayList<WindowControlInfo>(2);
   
    String contClassName = childState.getControllerClassName();
    long contId = childState.getControllerUniqueId();
   
    int size = children.size();
    for (int i = 0; i < size; i++) {
      WindowControlInfo wciChild = children.get(i);
      if (!wciChild.isControllerDisposed()) {
        ExtendedControllerState estate = wciChild.getExtendedControllerState();
        if (estate.getControllerUniqueId() == contId) {
          // same instance is still there, we have the match
          return wciChild;
        } else {
          // test on matching classnames
          if (contClassName.equals(estate.getControllerClassName())) {
            // add to potentials
            classMatches.add(wciChild);
          }
        }
      }
    }
   
    int matchesCnt = classMatches.size();
    if (matchesCnt == 0) {
      // no instance match and also no class name match: the parent controller did not exactly restore its state -> stop here
      return null;
    } else if (matchesCnt == 1) {
      // since exactly one match, there is no ambiguity -> return the match
      return classMatches.get(0);
    } else {
      // classMatches > 1 -> ambiguity
      // brasato:: todo now compare the controller initialcomponents parent-hierarchy
      throw new AssertException("todo!!");
    }
  }

  public String getBusinessControlPath() {
    return bookmarkBusinessControl == null? null : bookmarkBusinessControl.getAsString();
  }

 
  /**
   * @return
   */
  public String getIframeTargetName() {
    return iframeName;
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#createGuiStack(org.olat.core.gui.components.Component)
   */
  public GuiStack createGuiStack(Component initialComponent) {
    // only needed for a on-screen mode change = only for demo purposes.
    // normally the following code is appropriate:

    GuiStack gsh;
    if (winmgrImpl.isForScreenReader()) {
      gsh = new GuiStackSimpleImpl(initialComponent);
    } else {
      gsh = new GuiStackNiceImpl(this, initialComponent);     
    }
    return gsh;

    /*final GuiStack deleg = currentGuiStack;
    GuiStack switchable = new GuiStack() {
      public void pushModalDialog(Component content) {
        deleg.pushModalDialog(content);
      }

      public void pushContent(Component newContent) {
        deleg.pushContent(newContent);
      }

      public void popContent() {
        deleg.popContent();
      }

      public Panel getPanel() {
        return deleg.getPanel();
      }

      public Panel getModalPanel() {
        return deleg.getModalPanel();
      }};
   
    GuiStack gsh;
    if (winmgrImpl.isForScreenReader()) {
      gsh = new GuiStackSimpleImpl(initialComponent);
    } else {
      gsh = new GuiStackNiceImpl(initialComponent);     
    }
    return gsh;*/
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#invokeLater(java.lang.Runnable)
   */
  public void invokeLater(Runnable runnable) {
    // brasato:::: verify that this is now as it should be.
    // improve by handling those tasks after a ongoing dispatch/render is finished,
    // or when handleDirties is called.
    // the current solution below blocks the async caller - e.g. a user firing an eventbus message may then wait for all users to complete their
    // rendering process - normally way below 0.1 sec, but if we have 100 users which happen to just being rendered when the notifications takes place.
    // -> 10 seconds delay! this is not very probable but possible.
   
    synchronized(window) {//cluster_ok
      runnable.run();
    }
  }

  public void fireCycleEvent(Event cycleEvent) {
    for (GenericEventListener gel : cycleListeners) {
      gel.event(cycleEvent);
    }
    if (cycleEvent == Window.AFTER_VALIDATING) {
      // clear the added data for this cycle
      data.clear();
    }
   
  }

  public void addCycleListener(GenericEventListener gel) {
    cycleListeners.add(gel);
  }
 
  public void removeCycleListener(GenericEventListener gel) {
    // Since we use a CopyOnWriteArrayList it is save to remove an event
    // listener even when we are in the fireCycleEvent() method at the same time
    cycleListeners.remove(gel);
  }

  public Object getData(String key) {
    return data.get(key);
  }

  public void putData(String key, Object value) {
    data.put(key, value);
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#adjustGuiPathCenter(org.olat.core.gui.control.WindowBackOffice)
   */
  public void adjustGuiPathCenter(WindowControlInfo wci) {
    newWci  = wci;
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#acquireBackHandling(org.olat.core.gui.control.winmgr.BackHandler)
   */
  public void acquireBackHandling(BackHandler backHandler) {
    this.backHandler = backHandler;
  }

  /* (non-Javadoc)
   * @see org.olat.core.gui.control.WindowBackOffice#releaseBackHandling(org.olat.core.gui.control.winmgr.BackHandler)
   */
  public void releaseBackHandling(BackHandler backHandler) {
    /* FIXME:pb:2008-08-26: OLAT-3411 encountered on 6.0.3 release, must ne enabled again with BACK
    if (this.backHandler == null || this.backHandler != backHandler) throw new AssertException("wrong acquire/release order. backhandler:"+backHandler);
    this.backHandler = null;
    */
  }

TOP

Related Classes of org.olat.core.gui.control.winmgr.WindowBackOfficeImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.