/** * 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. * <p> */ package org.olat.ims.cp.ui; import java.util.ArrayList; import java.util.List; import org.dom4j.Element; import org.dom4j.tree.DefaultElement; 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.stack.TooledStackedPanel; import org.olat.core.gui.components.stack.TooledStackedPanel.Align; import org.olat.core.gui.components.tree.MenuTree; import org.olat.core.gui.components.tree.TreeDropEvent; import org.olat.core.gui.components.tree.TreeEvent; import org.olat.core.gui.components.tree.TreeNode; 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.CloseableModalController; import org.olat.core.gui.control.generic.modal.DialogBoxController; import org.olat.core.gui.control.generic.modal.DialogBoxUIFactory; import org.olat.core.util.tree.TreeHelper; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSLeaf; import org.olat.ims.cp.CPManager; import org.olat.ims.cp.CPTreeDataModel; import org.olat.ims.cp.ContentPackage; import org.olat.ims.cp.objects.CPItem; import org.olat.ims.cp.objects.CPOrganization; import org.olat.ims.cp.objects.CPResource; /** * The left-hand side of the cp editor shows the document tree with some edit * icons on top. * <P> * Initial Date: May 5, 2009 <br> * * @author gwassmann */ public class CPTreeController extends BasicController { private MenuTree treeCtr; private DialogBoxController dialogCtr; private CPFileImportController uploadCtr; private CPContentController contentCtr; private VelocityContainer contentVC; private ContentPackage cp; private CloseableModalController cmc; private Link importLink; private Link newLink; private Link copyLink; private Link deleteLink; private CPTreeDataModel treeModel; private CPPage currentPage; protected CPTreeController(UserRequest ureq, WindowControl control, ContentPackage cp) { super(ureq, control); contentVC = createVelocityContainer("treeView"); this.cp = cp; CPManager cpMgm = CPManager.getInstance(); treeModel = cpMgm.getTreeDataModel(cp); treeCtr = new MenuTree("cp"); treeCtr.setTreeModel(treeModel); treeCtr.setDragEnabled(true); treeCtr.setDropEnabled(true); treeCtr.setDropSiblingEnabled(true); treeCtr.setDndAcceptJSMethod("treeAcceptDrop_notWithChildren"); treeCtr.setExpandSelectedNode(false); treeCtr.addListener(this); contentVC.put("cptreecontroller.tree", treeCtr); contentVC.contextPut("treeId", treeCtr.getDispatchID()); putInitialPanel(contentVC); } void initToolbar(TooledStackedPanel toolbar) { importLink = LinkFactory.createToolLink("cptreecontroller.importlink", "cptreecontroller.importlink", translate("cptreecontroller.importlink_title"), this); importLink.setTooltip(translate("cptreecontroller.importlink_title")); importLink.setElementCssClass("o_sel_cp_import_link"); importLink.setIconLeftCSS("o_icon o_icon-lg o_icon_import"); toolbar.addTool(importLink, Align.left); newLink = LinkFactory.createToolLink("cptreecontroller.newlink", "cptreecontroller.newlink", translate("cptreecontroller.newlink_title"), this); newLink.setTooltip(translate("cptreecontroller.newlink_title")); newLink.setElementCssClass("o_sel_cp_new_link"); newLink.setIconLeftCSS("o_icon o_icon-lg o_icon_add"); toolbar.addTool(newLink, Align.left); copyLink = LinkFactory.createToolLink("cptreecontroller.copylink", "cptreecontroller.copylink", translate("cptreecontroller.copylink_title"), this); copyLink.setTooltip(translate("cptreecontroller.copylink_title")); copyLink.setElementCssClass("o_sel_cp_copy_link"); copyLink.setIconLeftCSS("o_icon o_icon-lg o_icon_copy"); toolbar.addTool(copyLink, Align.left); deleteLink = LinkFactory.createToolLink("cptreecontroller.deletelink", "cptreecontroller.deletelink", translate("cptreecontroller.deletelink_title"), this); deleteLink.setTooltip(translate("cptreecontroller.deletelink_title")); deleteLink.setElementCssClass("o_sel_cp_delete_link"); deleteLink.setIconLeftCSS("o_icon o_icon-lg o_icon_delete_item"); toolbar.addTool(deleteLink, Align.left); } /** * page setter * * @param page */ protected void setCurrentPage(CPPage page) { currentPage = page; } /** * Make this controller aware of the content controller. * * @param page */ protected void setContentController(CPContentController contentCtr) { this.contentCtr = contentCtr; } /** * deletes a page from the manifest * * @param nodeID */ private void deletePage(String identifier, boolean deleteResource) { if (identifier.equals("")) { // no page selected } else { CPManager cpMgm = CPManager.getInstance(); treeModel.removePath(); cpMgm.removeElement(cp, identifier, deleteResource); cpMgm.writeToFile(cp); updateTree(); } } private void updateTree() { treeModel.update(); treeCtr.setDirty(true); } /** * copies the page with given nodeID * * @param nodeID */ private String copyPage(CPPage page) { String newIdentifier = null; if (page != null) { CPManager cpMgm = CPManager.getInstance(); newIdentifier = cpMgm.copyElement(cp, page.getIdentifier()); cpMgm.writeToFile(cp); updateTree(); } return newIdentifier; } /** * Adds a new page to the CP * * @return */ protected String addNewHTMLPage() { String newId = CPManager.getInstance().addBlankPage(cp, translate("cptreecontroller.newpage.title"), currentPage.getIdentifier()); CPPage newPage = new CPPage(newId, cp); // Create an html file VFSContainer root = cp.getRootDir(); VFSLeaf htmlFile = root.createChildLeaf(newId + ".html"); newPage.setFile(htmlFile); updatePage(newPage); updateTree(); return newId; } /** * Adds a page to the CP * * @return */ protected String addPage(CPPage page) { CPManager cpMgm = CPManager.getInstance(); String newNodeID = ""; if (currentPage.getIdentifier().equals("")) { newNodeID = cpMgm.addBlankPage(cp, page.getTitle()); } else { // adds new page as child of currentPage newNodeID = cpMgm.addBlankPage(cp, page.getTitle(), currentPage.getIdentifier()); } setCurrentPage(new CPPage(newNodeID, cp)); cpMgm.writeToFile(cp); updateTree(); return newNodeID; } /** * @param page */ protected void updatePage(CPPage page) { setCurrentPage(page); CPManager cpMgm = CPManager.getInstance(); cpMgm.updatePage(cp, page); cpMgm.writeToFile(cp); updateTree(); selectTreeNodeByCPPage(page); } /** * Updates a page by nodeId. * * @param nodeId */ protected void updateNode(String nodeId, String title) { String nodeIdentifier = treeModel.getIdentifierForNodeID(nodeId); CPPage page = new CPPage(nodeIdentifier, cp); page.setTitle(title); updatePage(page); // will update also tree } /** * selects a Tree node in the tree with given id (if found). Returns false, if * node is not found, true otherwise info: todo: implement selection of node * in js tree * * @param id * @return */ protected boolean selectTreeNodeById(String id) { currentPage = new CPPage(id, cp); return selectTreeNodeByCPPage(currentPage); } /** * Selects the node in the tree with the given page (if found). Returns false, * if node is not found, true otherwise * * info: todo: implement selection of node in js tree * * @param page * @return */ protected boolean selectTreeNodeByCPPage(CPPage page) { currentPage = page; if(currentPage != null) { String identifier = currentPage.getIdentifier(); String nodeId = treeModel.getNodeIDForIdentifier(identifier); treeCtr.setSelectedNodeId(nodeId); } return true; } /** * Builds an html-info string about the current page and its linked resources * * @return HTML-String */ private String getCurrentPageInfoStringHTML() { // test if currentPage links to resource, which is used (linked) somewhere // else in the manifest CPManager cpMgm = CPManager.getInstance(); DefaultElement ele = cpMgm.getElementByIdentifier(cp, currentPage.getIdRef()); boolean single = false; if (ele instanceof CPResource) { CPResource res = (CPResource) ele; single = cpMgm.isSingleUsedResource(res, cp); } StringBuilder b = new StringBuilder(); b.append("<br /><ul>"); b.append("<li><b>" + translate("cptreecontroller.pagetitle") + "</b> " + currentPage.getTitle() + "</li>"); if (single) { b.append("<li><b>" + translate("cptreecontroller.file") + "</b> " + currentPage.getFileName() + "</li>"); } b.append("</ul>"); return b.toString(); } /** * Event-handling from components * */ protected void event(UserRequest ureq, Component source, Event event) { if (source == importLink) { uploadCtr = new CPFileImportController(ureq, getWindowControl(), cp, currentPage); listenTo(uploadCtr); String title = translate("cpfileuploadcontroller.form.title"); cmc = new CloseableModalController(getWindowControl(), translate("close"), uploadCtr.getInitialComponent(), true, title); listenTo(cmc); cmc.activate(); } else if (source == newLink) { fireEvent(ureq, new Event("New Page")); } else if (source == copyLink) { if (currentPage.isOrgaPage()) { showInfo("cptreecontroller.orga.cannot.be.copied"); } else { String newIdentifier = copyPage(currentPage); contentCtr.displayPageWithMetadataEditor(ureq, newIdentifier); } } else if (source == deleteLink) { if (currentPage.isOrgaPage()) { showInfo("cptreecontroller.orga.cannot.be.deleted"); } else { List<String> buttonLables = new ArrayList<String>(); buttonLables.add(translate("cptreecontrolller.delete.items.and.files")); buttonLables.add(translate("cptreecontrolller.delete.items.only")); buttonLables.add(translate("cancel")); dialogCtr = DialogBoxUIFactory.createGenericDialog(ureq, getWindowControl(), translate("cptreecontroller.q_delete_title"), translate("cptreecontroller.q_delete_text", getCurrentPageInfoStringHTML()), buttonLables); listenTo(dialogCtr); dialogCtr.activate(); } } else if (source == treeCtr) { if(event instanceof TreeEvent) { TreeEvent te = (TreeEvent)event; String selectedNodeID = treeModel.getIdentifierForNodeID(te.getNodeId()); currentPage = new CPPage(selectedNodeID, cp); fireEvent(ureq, new TreeEvent(TreeEvent.COMMAND_TREENODE_CLICKED, selectedNodeID)); } else if(event instanceof TreeDropEvent) { TreeDropEvent te = (TreeDropEvent)event; doDrop(ureq, te.getDroppedNodeId(), te.getTargetNodeId(), te.isAsChild()); } } } /** * Event-handling from controllers * */ protected void event(UserRequest ureq, Controller source, Event event) { if (source == cmc) { if (event.equals(CloseableModalController.CLOSE_MODAL_EVENT)) { removeAsListenerAndDispose(cmc); cmc = null; removeAsListenerAndDispose(uploadCtr); uploadCtr = null; } } else if (source == uploadCtr) { // Dispose the cmc and the podcastFormCtr first so modal dialog is free for metadata dialog cmc.deactivate(); removeAsListenerAndDispose(cmc); cmc = null; removeAsListenerAndDispose(uploadCtr); uploadCtr = null; // Forward event to main controller if (event instanceof NewCPPageEvent) { fireEvent(ureq, event); updateTree(); } } else if (source == dialogCtr) { // event from dialog (really-delete-dialog) if (event != Event.CANCELLED_EVENT) { int position = DialogBoxUIFactory.getButtonPos(event); // 0 = delete with resource // 1 = delete without resource // 2 = cancel if (position == 0 || position == 1) { boolean deleteResource = false; if (position == 0) { // Delete element including files deleteResource = true; } String parentIdentifier = getParentIdentifier(); // finally delete the page deletePage(currentPage.getIdentifier(), deleteResource); if (parentIdentifier != null) { contentCtr.displayPage(ureq, parentIdentifier); } } else { // Cancel dialog and close window. } } } } private void doDrop(UserRequest ureq, String droppedNodeId, String targetNodeId, boolean asChild) { TreeNode droppedNode = treeModel.getNodeById(droppedNodeId); TreeNode targetNode = treeModel.getNodeById(targetNodeId); String droppedNodeIdent = treeModel.getIdentifierForNodeID(droppedNodeId); String targetNodeIdent = treeModel.getIdentifierForNodeID(targetNodeId); CPManager cpMgm = CPManager.getInstance(); if(asChild) { cpMgm.moveElement(cp, droppedNodeIdent, targetNodeIdent, 0); cpMgm.writeToFile(cp); } else if(targetNode.getParent() == null) { //root -> do nothing } else { TreeNode parentTargetNode = (TreeNode)targetNode.getParent(); int index = TreeHelper.indexOf(targetNode, parentTargetNode); boolean sibling = droppedNode.getParent().equals(parentTargetNode); if(sibling) { int droppedCurentIndex = TreeHelper.indexOf(targetNode, parentTargetNode); if(droppedCurentIndex < index) { index = index -2; } } if(index < 0) { index = 0; } String parentTargetNodeIdent = treeModel.getIdentifierForNodeID(parentTargetNode.getIdent()); cpMgm.moveElement(cp, droppedNodeIdent, parentTargetNodeIdent, index + 1); cpMgm.writeToFile(cp); } selectTreeNodeById(droppedNodeIdent); updateTree(); fireEvent(ureq, new TreeEvent(TreeEvent.COMMAND_TREENODE_CLICKED, droppedNodeIdent)); } /** * Retrieves the parent identifier of the current page * * @return The identifier of the current page's parent */ private String getParentIdentifier() { DefaultElement currentElem = CPManager.getInstance().getElementByIdentifier(cp, currentPage.getIdentifier()); // Get the parent node to be displayed after deletion. String parentIdentifier = null; if (currentElem instanceof CPItem) { Element parent = ((CPItem) currentElem).getParentElement(); if (parent instanceof CPItem) { CPItem parentItem = (CPItem) parent; parentIdentifier = parentItem.getIdentifier(); } else if (parent instanceof CPOrganization) { CPOrganization parentItem = (CPOrganization) parent; parentIdentifier = parentItem.getIdentifier(); } } return parentIdentifier; } @Override protected void doDispose() { contentVC = null; } }