/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <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 the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <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> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.modules.portfolio.ui.editor; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.Component; import org.olat.core.gui.components.link.Link; import org.olat.core.gui.components.link.LinkFactory; import org.olat.core.gui.components.velocity.VelocityContainer; import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.Event; import org.olat.core.gui.control.WindowControl; import org.olat.core.gui.control.controller.BasicController; 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.generic.closablewrapper.CloseableCalloutWindowController; import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; import org.olat.core.gui.translator.Translator; import org.olat.modules.portfolio.ui.editor.event.AddElementEvent; import org.olat.modules.portfolio.ui.editor.event.ChangePartEvent; import org.olat.modules.portfolio.ui.editor.event.ClosePartEvent; /** * * Initial date: 08.06.2016<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class PageEditorController extends BasicController { private final VelocityContainer mainVC; private CloseableModalController cmc; private PageElementAddController addCtrl; private AddElementsController addElementsCtrl; private CloseableCalloutWindowController addCalloutCtrl; private int counter; private final PageEditorProvider provider; private final PageEditorSecurityCallback secCallback; private List<EditorFragment> fragments = new ArrayList<>(); private Map<String,PageElementHandler> handlerMap = new HashMap<>(); public PageEditorController(UserRequest ureq, WindowControl wControl, PageEditorProvider provider, PageEditorSecurityCallback secCallback, Translator fallbackTranslator) { super(ureq, wControl, fallbackTranslator); this.provider = provider; this.secCallback = secCallback; // Set a fallback translator from runtime package. Important to not do it the other way round // because they have same keys in the package and overwrite each other otherwise. /*PackageTranslator fallbackTrans = (PackageTranslator)Util.createPackageTranslator(PageRunController.class, getLocale()); if (getTranslator() instanceof PackageTranslator) { PackageTranslator myTrans = (PackageTranslator) getTranslator(); myTrans.setFallBack(fallbackTrans); }*/ mainVC = createVelocityContainer("page_editor"); for(PageElementHandler handler:provider.getAvailableHandlers()) { handlerMap.put(handler.getType(), handler); } List<String> addElements = new ArrayList<>(); for(PageElementHandler handler:provider.getCreateHandlers()) { if(handler instanceof InteractiveAddPageElementHandler || handler instanceof SimpleAddPageElementHandler) { String id = "add." + handler.getType(); Link addLink = LinkFactory.createLink(id, id, "add", mainVC, this); addLink.setIconLeftCSS("o_icon o_icon-lg " + handler.getIconCssClass()); addLink.setUserObject(handler); mainVC.put(id, addLink); addElements.add(id); } } mainVC.contextPut("addElementLinks", addElements); loadModel(ureq); putInitialPanel(mainVC); } private void loadModel(UserRequest ureq) { List<? extends PageElement> elements = provider.getElements(); List<EditorFragment> newFragments = new ArrayList<>(elements.size()); for(PageElement element:elements) { EditorFragment fragment = createFragment(ureq, element); if(fragment != null) { newFragments.add(fragment); } } fragments = newFragments; mainVC.contextPut("fragments", newFragments); } @Override protected void doDispose() { // } @Override protected void event(UserRequest ureq, Controller source, Event event) { if(addCtrl == source) { if(event == Event.DONE_EVENT || event == Event.CHANGED_EVENT) { PageElement element = addCtrl.getPageElement(); AddElementInfos uobject = addCtrl.getUserObject(); EditorFragment fragment = doAddPageElement(ureq, element, uobject.getReferenceFragment(), uobject.getTarget()); // close editor right away (file upload etc makes more sense) doSaveElement(ureq, fragment); } cmc.deactivate(); cleanUp(); } else if(addElementsCtrl == source) { addCalloutCtrl.deactivate(); cleanUp(); if(event instanceof AddElementEvent) { AddElementEvent aee = (AddElementEvent)event; doAddElement(ureq, aee.getReferenceFragment(), aee.getHandler(), aee.getTarget()); } } else if(addCalloutCtrl == source) { cleanUp(); } else if(cmc == source) { cleanUp(); } else if(isEditorPartController(source)) { EditorFragment fragment = getEditorFragment(source); if(event instanceof ChangePartEvent) { ChangePartEvent changeEvent = (ChangePartEvent)event; PageElement element = changeEvent.getElement(); fragment.setPageElement(element); fireEvent(ureq, Event.CHANGED_EVENT); } else if(event instanceof ClosePartEvent) { doSaveElement(ureq, fragment); } } super.event(ureq, source, event); } private boolean isEditorPartController(Controller source) { for(EditorFragment fragment:fragments) { if(fragment.getEditorPart() == source) { return true; } } return false; } private EditorFragment getEditorFragment(Controller source) { for(EditorFragment fragment:fragments) { if(fragment.getEditorPart() == source) { return fragment; } } return null; } private void cleanUp() { removeAsListenerAndDispose(addElementsCtrl); removeAsListenerAndDispose(addCalloutCtrl); removeAsListenerAndDispose(addCtrl); removeAsListenerAndDispose(cmc); addElementsCtrl = null; addCalloutCtrl = null; addCtrl = null; cmc = null; } @Override protected void event(UserRequest ureq, Component source, Event event) { if(source instanceof Link) { Link link = (Link)source; String cmd = link.getCommand(); if("add".equals(cmd)) { PageElementHandler handler = (PageElementHandler)link.getUserObject(); doAddElement(ureq, null, handler, PageElementTarget.atTheEnd); } else if("add.element.above".equals(cmd)) { EditorFragment refEl = (EditorFragment)link.getUserObject(); openAddElementCallout(ureq, link, refEl, PageElementTarget.above); } else if("add.element.below".equals(cmd)) { EditorFragment refEl = (EditorFragment)link.getUserObject(); openAddElementCallout(ureq, link, refEl, PageElementTarget.below); } else if("save.element".equals(cmd)) { EditorFragment fragment = (EditorFragment)link.getUserObject(); doSaveElement(ureq, fragment); } else if("delete.element".equals(cmd)) { EditorFragment fragment = (EditorFragment)link.getUserObject(); doDeleteElement(ureq, fragment); } else if("move.up.element".equals(cmd)) { EditorFragment fragment = (EditorFragment)link.getUserObject(); doMoveUpElement(ureq, fragment); } else if("move.down.element".equals(cmd)) { EditorFragment fragment = (EditorFragment)link.getUserObject(); doMoveDownElement(ureq, fragment); } } else if(mainVC == source) { if("edit_fragment".equals(event.getCommand())) { String fragmentId = ureq.getParameter("fragment"); EditorFragment selectedFragment = null; for(EditorFragment f:fragments) { if(f.getComponentName().equals(fragmentId)) { selectedFragment = f; } } doEditElement(selectedFragment); } } } private void doEditElement(EditorFragment fragment) { for(EditorFragment eFragment:fragments) { eFragment.setEditMode(eFragment.equals(fragment)); } //The link must every time created as new Link addAboveLink = LinkFactory.createLink("add.element.above", "add.element.above", getTranslator(), mainVC, this, Link.LINK); addAboveLink.setIconLeftCSS("o_icon o_icon-sm o_icon_element_before"); addAboveLink.setElementCssClass("o_sel_add_element_above"); addAboveLink.setUserObject(fragment); addAboveLink.setVisible(provider.getCreateHandlers().size() > 0); addAboveLink.setEnabled(provider.getCreateHandlers().size() > 0); fragment.setAddElementAboveLink(addAboveLink); Link addBelowLink = LinkFactory.createLink("add.element.below", "add.element.below", getTranslator(), mainVC, this, Link.LINK); addBelowLink.setIconLeftCSS("o_icon o_icon-sm o_icon_element_after"); addBelowLink.setElementCssClass("o_sel_add_element_below"); addBelowLink.setUserObject(fragment); addBelowLink.setVisible(provider.getCreateHandlers().size() > 0); addBelowLink.setEnabled(provider.getCreateHandlers().size() > 0); fragment.setAddElementBelowLink(addBelowLink); Link saveLink = LinkFactory.createLink("save.and.close", "save.element", getTranslator(), mainVC, this, Link.LINK); saveLink.setIconLeftCSS("o_icon o_icon-sm o_icon_close"); saveLink.setElementCssClass("o_sel_save_element"); saveLink.setUserObject(fragment); fragment.setSaveLink(saveLink); Link moveUpLink = LinkFactory.createLink("move.up", "move.up.element", getTranslator(), mainVC, this, Link.LINK + Link.NONTRANSLATED); moveUpLink.setIconLeftCSS("o_icon o_icon-sm o_icon_move_up"); moveUpLink.setElementCssClass("o_sel_move_up_element"); moveUpLink.setCustomDisplayText(""); moveUpLink.setTitle(translate("move.up")); moveUpLink.setUserObject(fragment); moveUpLink.setEnabled(fragments.indexOf(fragment) > 0 && secCallback.canMoveUpAndDown()); moveUpLink.setVisible(secCallback.canMoveUpAndDown()); fragment.setMoveUpLink(moveUpLink); Link moveDownLink = LinkFactory.createLink("move.down", "move.down.element", getTranslator(), mainVC, this, Link.LINK + Link.NONTRANSLATED); moveDownLink.setIconLeftCSS("o_icon o_icon-sm o_icon_move_down"); moveDownLink.setElementCssClass("o_sel_move_down_element"); moveDownLink.setCustomDisplayText(""); moveUpLink.setTitle(translate("move.down")); moveDownLink.setUserObject(fragment); moveDownLink.setEnabled((fragments.indexOf(fragment) < (fragments.size() - 1)) && secCallback.canMoveUpAndDown()); moveDownLink.setVisible(secCallback.canMoveUpAndDown()); fragment.setMoveDownLink(moveDownLink); Link deleteLink = LinkFactory.createLink("delete", "delete.element", getTranslator(), mainVC, this, Link.LINK); deleteLink.setIconLeftCSS("o_icon o_icon-sm o_icon_delete_item"); deleteLink.setElementCssClass("o_sel_delete_element"); deleteLink.setUserObject(fragment); deleteLink.setVisible(secCallback.canDeleteElement()); deleteLink.setEnabled(secCallback.canDeleteElement()); fragment.setDeleteLink(deleteLink); mainVC.setDirty(true); } private void openAddElementCallout(UserRequest ureq, Link link, EditorFragment referenceFragment, PageElementTarget target) { addElementsCtrl = new AddElementsController(ureq, getWindowControl(), provider, referenceFragment, target); listenTo(addElementsCtrl); CalloutSettings calloutSettings; if(target == PageElementTarget.above) { calloutSettings = new CalloutSettings(true, CalloutOrientation.top); } else { calloutSettings = new CalloutSettings(false); } addCalloutCtrl = new CloseableCalloutWindowController(ureq, getWindowControl(), addElementsCtrl.getInitialComponent(), link.getDispatchID(), "", true, "", calloutSettings); listenTo(addCalloutCtrl); addCalloutCtrl.activate(); } private void doAddElement(UserRequest ureq, EditorFragment refenceFragment, PageElementHandler handler, PageElementTarget target) { if(addCtrl != null) return; if(handler instanceof InteractiveAddPageElementHandler) { InteractiveAddPageElementHandler interactiveHandler = (InteractiveAddPageElementHandler)handler; addCtrl = interactiveHandler.getAddPageElementController(ureq, getWindowControl()); if(addCtrl == null) { showWarning("not.implement"); } else { addCtrl.setUserObject(new AddElementInfos(refenceFragment, handler, target)); listenTo(addCtrl); String title = translate("add." + handler.getType()); cmc = new CloseableModalController(getWindowControl(), null, addCtrl.getInitialComponent(), true, title, true); listenTo(cmc); cmc.activate(); } } else if(handler instanceof SimpleAddPageElementHandler) { SimpleAddPageElementHandler simpleHandler = (SimpleAddPageElementHandler)handler; doAddPageElement(ureq, simpleHandler.createPageElement(getLocale()), refenceFragment, target); } } private EditorFragment doAddPageElement(UserRequest ureq, PageElement element, EditorFragment referenceFragment, PageElementTarget target) { EditorFragment newFragment = null; if(target == PageElementTarget.atTheEnd) { newFragment = doAddPageElementAtTheEnd(ureq, element); } else if(target == PageElementTarget.above || target == PageElementTarget.below) { int index = fragments.indexOf(referenceFragment); if(target == PageElementTarget.below) { index = index + 1; } if(index >= fragments.size()) { newFragment = doAddPageElementAtTheEnd(ureq, element); } else { if(index < 0) { index = 0; } PageElement pageElement = provider.appendPageElementAt(element, index); newFragment = createFragment(ureq, pageElement); fragments.add(index, newFragment); } } mainVC.setDirty(true); doEditElement(newFragment); fireEvent(ureq, Event.CHANGED_EVENT); return newFragment; } private EditorFragment doAddPageElementAtTheEnd(UserRequest ureq, PageElement element) { PageElement pageElement = provider.appendPageElement(element); EditorFragment fragment = createFragment(ureq, pageElement); fragments.add(fragment); return fragment; } private void doSaveElement(UserRequest ureq, EditorFragment fragment) { fragment.setEditMode(false); mainVC.setDirty(true); fireEvent(ureq, Event.CHANGED_EVENT); } private void doDeleteElement(UserRequest ureq, EditorFragment fragment) { provider.removePageElement(fragment.getPageElement()); fragments.remove(fragment); mainVC.setDirty(true); fireEvent(ureq, Event.CHANGED_EVENT); } private void doMoveUpElement(UserRequest ureq, EditorFragment fragment) { int index = fragments.indexOf(fragment) - 1; if(index >= 0) { provider.moveUpPageElement(fragment.getPageElement()); fragments.remove(fragment); fragments.add(index, fragment); mainVC.setDirty(true); doEditElement(fragment); fireEvent(ureq, Event.CHANGED_EVENT); } } private void doMoveDownElement(UserRequest ureq, EditorFragment fragment) { int index = fragments.indexOf(fragment) + 1; if(index < fragments.size()) { provider.moveDownPageElement(fragment.getPageElement()); fragments.remove(fragment); fragments.add(index, fragment); mainVC.setDirty(true); doEditElement(fragment); fireEvent(ureq, Event.CHANGED_EVENT); } } private EditorFragment createFragment(UserRequest ureq, PageElement element) { PageElementHandler handler = handlerMap.get(element.getType()); if(handler == null) { logError("Cannot find an handler of type: " + element.getType(), null); } Controller editorPart = handler.getEditor(ureq, getWindowControl(), element); listenTo(editorPart); String cmpId = "frag-" + (++counter); EditorFragment fragment = new EditorFragment(element, handler, cmpId, editorPart); mainVC.put(cmpId, editorPart.getInitialComponent()); return fragment; } public static class EditorFragment { private boolean editMode; private PageElement element; private final PageElementHandler handler; private final String cmpId; private Controller editorPart; private Link addElementAboveLink, addElementBelowLink, saveLink, deleteLink, moveUpLink, moveDownLink; public EditorFragment(PageElement element, PageElementHandler handler, String cmpId, Controller editorPart) { this.element = element; this.handler = handler; this.cmpId = cmpId; this.editorPart = editorPart; } public boolean isEditMode() { return editMode; } public void setEditMode(boolean editMode) { this.editMode = editMode; if(editorPart instanceof PageElementEditorController) { ((PageElementEditorController)editorPart).setEditMode(editMode); } } public String getCmpId() { return cmpId; } public PageElement getPageElement() { return element; } public void setPageElement(PageElement element) { this.element = element; } public String getComponentName() { return cmpId; } public Controller getEditorPart() { return editorPart; } public Link getAddElementAboveLink() { return addElementAboveLink; } public void setAddElementAboveLink(Link addElementAboveLink) { this.addElementAboveLink = addElementAboveLink; } public Link getAddElementBelowLink() { return addElementBelowLink; } public void setAddElementBelowLink(Link addElementBelowLink) { this.addElementBelowLink = addElementBelowLink; } public Link getSaveLink() { return saveLink; } public void setSaveLink(Link saveLink) { this.saveLink = saveLink; } public Link getDeleteLink() { return deleteLink; } public void setDeleteLink(Link deleteLink) { this.deleteLink = deleteLink; } public Link getMoveUpLink() { return moveUpLink; } public void setMoveUpLink(Link moveUpLink) { this.moveUpLink = moveUpLink; } public Link getMoveDownLink() { return moveDownLink; } public void setMoveDownLink(Link moveDownLink) { this.moveDownLink = moveDownLink; } public String getType() { return handler.getType(); } public String getTypeCssClass() { return handler.getIconCssClass(); } public PageElementHandler getHandler() { return handler; } @Override public int hashCode() { return element.hashCode(); } @Override public boolean equals(Object obj) { if(this == obj) { return true; } if(obj instanceof EditorFragment) { EditorFragment eFragment = (EditorFragment)obj; return element != null && element.equals(eFragment.getPageElement()); } return false; } } }