From ba9d2c8b671fc606da304040b300291d9c059e5f Mon Sep 17 00:00:00 2001
From: uhensler <urs.hensler@frentix.com>
Date: Mon, 5 Aug 2019 14:38:40 +0200
Subject: [PATCH] OO-4091: Use standard mechanism from FlexiTable to make the
 hierarchical view

---
 .../services/mark/impl/ui/MarkController.java |   9 +-
 .../services/mark/impl/ui/MarkedEvent.java    |  10 +-
 .../services/mark/impl/ui/UnmarkedEvent.java  |  16 +-
 .../table/AbstractFlexiTableRenderer.java     |   2 +-
 .../table/DefaultFlexiTreeTableDataModel.java |  10 +
 .../table/FlexiTreeTableDataModel.java        |   4 +-
 .../table/TreeNodeFlexiCellRenderer.java      |  23 ++
 .../org/olat/modules/fo/MessageLight.java     |   3 -
 .../modules/fo/ui/ForumMessageDataModel.java  |  25 +-
 .../fo/ui/ForumMessageListController.java     | 292 ++++++++----------
 .../modules/fo/ui/IndentCellRenderer.java     |  67 ----
 .../modules/fo/ui/MessageLightViewRow.java    |  38 ++-
 .../modules/fo/ui/MessageListController.java  |  15 +-
 .../fo/ui/MessageTreeRowComparator.java       |  56 ++++
 14 files changed, 319 insertions(+), 251 deletions(-)
 delete mode 100644 src/main/java/org/olat/modules/fo/ui/IndentCellRenderer.java
 create mode 100644 src/main/java/org/olat/modules/fo/ui/MessageTreeRowComparator.java

diff --git a/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkController.java b/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkController.java
index 5214b068817..fe9a648c113 100644
--- a/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkController.java
+++ b/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkController.java
@@ -112,9 +112,9 @@ public class MarkController extends FormBasicController {
 			Integer countI = stat.getCount();
 			if (mark!=null && mark.getCreator().equalsByPersistableKey(getIdentity())){
 				if (countI == 1) {
-					tooltip = getTranslator().translate("mark.stat.self.only");					
+					tooltip = getTranslator().translate("mark.stat.self.only");
 				} else {
-					tooltip = getTranslator().translate("mark.stat.self", new String[]{ new Integer(countI-1).toString() });			
+					tooltip = getTranslator().translate("mark.stat.self", new String[]{ new Integer(countI-1).toString() });
 				}
 			} else {
 				tooltip = getTranslator().translate("mark.stat", new String[]{ countI.toString() });
@@ -141,14 +141,15 @@ public class MarkController extends FormBasicController {
 			if(marked) {
 				if(mark == null) {
 					markingService.getMarkManager().removeMark(ores, ureq.getIdentity(), subPath);
+					fireEvent(ureq, new UnmarkedEvent(ores, subPath));
 				} else {
 					markingService.getMarkManager().removeMark(mark);
+					fireEvent(ureq, new UnmarkedEvent(mark.getOLATResourceable(), mark.getResSubPath()));
 					mark = null;
 				}
-				fireEvent(ureq, new UnmarkedEvent());
 			} else {
 				mark = markingService.getMarkManager().setMark(ores, identity, subPath, businessPath);
-				fireEvent(ureq, new MarkedEvent());
+				fireEvent(ureq, new MarkedEvent(mark));
 			}
 			marked = !marked;
 			markLink.setIconLeftCSS(marked ? Mark.MARK_CSS_LARGE : Mark.MARK_ADD_CSS_LARGE);
diff --git a/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkedEvent.java b/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkedEvent.java
index 805d02505ed..7a86a2df5c7 100644
--- a/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkedEvent.java
+++ b/src/main/java/org/olat/core/commons/services/mark/impl/ui/MarkedEvent.java
@@ -19,6 +19,7 @@
  */
 package org.olat.core.commons.services.mark.impl.ui;
 
+import org.olat.core.commons.services.mark.Mark;
 import org.olat.core.gui.control.Event;
 
 /**
@@ -31,8 +32,15 @@ public class MarkedEvent extends Event {
 
 	private static final long serialVersionUID = -4104070726631981649L;
 
-	public MarkedEvent() {
+	private final Mark mark;
+
+	public MarkedEvent(Mark mark) {
 		super("marked");
+		this.mark = mark;
+	}
+
+	public Mark getMark() {
+		return mark;
 	}
 
 }
diff --git a/src/main/java/org/olat/core/commons/services/mark/impl/ui/UnmarkedEvent.java b/src/main/java/org/olat/core/commons/services/mark/impl/ui/UnmarkedEvent.java
index 3fc40176532..885c2d4012d 100644
--- a/src/main/java/org/olat/core/commons/services/mark/impl/ui/UnmarkedEvent.java
+++ b/src/main/java/org/olat/core/commons/services/mark/impl/ui/UnmarkedEvent.java
@@ -20,6 +20,7 @@
 package org.olat.core.commons.services.mark.impl.ui;
 
 import org.olat.core.gui.control.Event;
+import org.olat.core.id.OLATResourceable;
 
 /**
  * 
@@ -31,8 +32,21 @@ public class UnmarkedEvent extends Event {
 
 	private static final long serialVersionUID = 3503240359856342650L;
 
-	public UnmarkedEvent() {
+	private final OLATResourceable ores;
+	private final String subPath;
+
+	public UnmarkedEvent(OLATResourceable ores, String subPath) {
 		super("unmarked");
+		this.ores = ores;
+		this.subPath = subPath;
+	}
+
+	public OLATResourceable getOres() {
+		return ores;
+	}
+
+	public String getSubPath() {
+		return subPath;
 	}
 
 }
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java
index 7939cfa1a0e..2551742adca 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/AbstractFlexiTableRenderer.java
@@ -425,7 +425,7 @@ public abstract class AbstractFlexiTableRenderer extends DefaultComponentRendere
 				  .append("</span></a>");
 			}
 			
-			if(ftE.getTreeTableDataModel() != null) {
+			if(ftE.getTreeTableDataModel() != null && ftE.getTreeTableDataModel().hasOpenCloseAll()) {
 				sb.append("<a id='")
 				  .append(dispatchId).append("_toa' href=\"javascript:;\" onclick=\"")
 				  .append(FormJSHelper.getXHRFnCallFor(ftE.getRootForm(), dispatchId, 1, true, true, true,
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiTreeTableDataModel.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiTreeTableDataModel.java
index 9a0dee965e3..feaf0ce9837 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiTreeTableDataModel.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/DefaultFlexiTreeTableDataModel.java
@@ -42,6 +42,7 @@ implements FlexiTreeTableDataModel<U>, FilterableFlexiTableModel {
 	protected final Set<U> openedRows = new HashSet<>();
 	protected List<U> backupRows;
 	protected U focusedNode;
+	private boolean hasOpenCloseAll = true;
 
 	public DefaultFlexiTreeTableDataModel(FlexiTableColumnModel columnModel) {
 		super(columnModel);
@@ -187,6 +188,15 @@ implements FlexiTreeTableDataModel<U>, FilterableFlexiTableModel {
 		return refreshedCrumbs;
 	}
 
+	@Override
+	public boolean hasOpenCloseAll() {
+		return hasOpenCloseAll;
+	}
+
+	protected void setHasOpenCloseAll(boolean hasOpenCloseAll) {
+		this.hasOpenCloseAll = hasOpenCloseAll;
+	}
+
 	@SuppressWarnings("unchecked")
 	@Override
 	public void openAll() {
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/FlexiTreeTableDataModel.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/FlexiTreeTableDataModel.java
index cb6f50ac17a..2df9e76c22c 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/FlexiTreeTableDataModel.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/FlexiTreeTableDataModel.java
@@ -48,6 +48,8 @@ public interface FlexiTreeTableDataModel<T extends FlexiTreeTableNode> extends F
 	
 	public void open(int row);
 	
+	public boolean hasOpenCloseAll();
+
 	/**
 	 * The table will call the openAll method and filter() right after.
 	 * 
@@ -61,5 +63,5 @@ public interface FlexiTreeTableDataModel<T extends FlexiTreeTableNode> extends F
 	public void popBreadcrumb(FlexiTreeTableNode node);
 	
 	public List<FlexiTreeTableNode> reloadBreadcrumbs(List<FlexiTreeTableNode> crumbs);
-
+	
 }
diff --git a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TreeNodeFlexiCellRenderer.java b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TreeNodeFlexiCellRenderer.java
index 232d9e2d353..33fc0b05c2b 100644
--- a/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TreeNodeFlexiCellRenderer.java
+++ b/src/main/java/org/olat/core/gui/components/form/flexible/impl/elements/table/TreeNodeFlexiCellRenderer.java
@@ -21,6 +21,7 @@ package org.olat.core.gui.components.form.flexible.impl.elements.table;
 
 import java.util.List;
 
+import org.olat.core.commons.persistence.SortKey;
 import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter;
 import org.olat.core.gui.components.form.flexible.impl.Form;
 import org.olat.core.gui.components.form.flexible.impl.FormJSHelper;
@@ -42,6 +43,7 @@ public class TreeNodeFlexiCellRenderer implements FlexiCellRenderer {
 	private FlexiCellRenderer labelDelegate = new TextFlexiCellRenderer();
 	
 	private boolean flatBySearchAndFilter;
+	private boolean flatBySort;
 	private final String action;
 	
 	public TreeNodeFlexiCellRenderer() {
@@ -60,6 +62,14 @@ public class TreeNodeFlexiCellRenderer implements FlexiCellRenderer {
 		this.flatBySearchAndFilter = flatBySearchAndFilter;
 	}
 
+	public boolean isFlatBySort() {
+		return flatBySort;
+	}
+
+	public void setFlatBySort(boolean flatBySort) {
+		this.flatBySort = flatBySort;
+	}
+
 	@Override
 	public void render(Renderer renderer, StringOutput target, Object cellValue, int row,
 			FlexiTableComponent source, URLBuilder ubu, Translator translator) {
@@ -77,10 +87,23 @@ public class TreeNodeFlexiCellRenderer implements FlexiCellRenderer {
 	}
 	
 	private boolean isFlat(FlexiTableElementImpl ftE) {
+		return isFlatSearchAndFilter(ftE) || isFlatSort(ftE) ;
+	}
+
+	private boolean isFlatSearchAndFilter(FlexiTableElementImpl ftE) {
 		return flatBySearchAndFilter
 				&& (StringHelper.containsNonWhitespace(ftE.getQuickSearchString()) || isFiltered(ftE.getSelectedFilters()));
 	}
 	
+	private boolean isFlatSort(FlexiTableElementImpl ftE) {
+		return flatBySort && isSorted(ftE);
+	}
+
+	private boolean isSorted(FlexiTableElementImpl ftE) {
+		SortKey[] keys = ftE.getOrderBy();
+		return keys != null && keys.length > 0 && keys[0] != null && !"natural".equals(keys[0].getKey());
+	}
+
 	private boolean isFiltered(List<FlexiTableFilter> filters) {
 		if(filters == null || filters.isEmpty()) return false;
 		
diff --git a/src/main/java/org/olat/modules/fo/MessageLight.java b/src/main/java/org/olat/modules/fo/MessageLight.java
index fcace6e0567..b81bad0ad9b 100644
--- a/src/main/java/org/olat/modules/fo/MessageLight.java
+++ b/src/main/java/org/olat/modules/fo/MessageLight.java
@@ -31,8 +31,6 @@ import org.olat.core.id.Identity;
  */
 public interface MessageLight extends MessageRef {
 	
-	public int getStatusCode();
-	
 	public String getTitle();
 	
 	public String getBody();
@@ -49,7 +47,6 @@ public interface MessageLight extends MessageRef {
 	
 	public Date getLastModified();
 	
-
 	public Long getParentKey();
 	
 	public MessageRef getThreadtop();
diff --git a/src/main/java/org/olat/modules/fo/ui/ForumMessageDataModel.java b/src/main/java/org/olat/modules/fo/ui/ForumMessageDataModel.java
index eab2a128e4e..8085cba2356 100644
--- a/src/main/java/org/olat/modules/fo/ui/ForumMessageDataModel.java
+++ b/src/main/java/org/olat/modules/fo/ui/ForumMessageDataModel.java
@@ -23,7 +23,9 @@ import java.util.List;
 import java.util.Locale;
 
 import org.olat.core.commons.persistence.SortKey;
+import org.olat.core.gui.components.form.flexible.elements.FlexiTableFilter;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTreeTableDataModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiSortableColumnDef;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SortableFlexiTableDataModel;
@@ -32,13 +34,15 @@ import org.olat.core.gui.translator.Translator;
 import org.olat.core.util.StringHelper;
 import org.olat.group.ui.main.AbstractMemberListController;
 
+import edu.emory.mathcs.backport.java.util.Collections;
+
 /**
  * 
  * Initial date: 12.11.2015<br>
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
-public class ForumMessageDataModel extends DefaultFlexiTableDataModel<MessageLightViewRow>
+public class ForumMessageDataModel extends DefaultFlexiTreeTableDataModel<MessageLightViewRow>
 	implements SortableFlexiTableDataModel<MessageLightViewRow> {
 	
 	private Translator translator;
@@ -48,14 +52,30 @@ public class ForumMessageDataModel extends DefaultFlexiTableDataModel<MessageLig
 		this.translator = translator;
 	}
 
+	@Override
+	public void filter(String searchString, List<FlexiTableFilter> filters) {
+		//
+	}
+	
+	@Override
+	public boolean hasChildren(int row) {
+		MessageLightViewRow viewRow = getObject(row);
+		return viewRow.hasChildren();
+	}
+
 	@Override
 	public void sort(SortKey orderBy) {
 		if(orderBy != null) {
 			if("natural".equals(orderBy.getKey())) {
-				//System.out.println();
+				List<MessageLightViewRow> objects = getObjects();
+				Collections.sort(objects, new MessageTreeRowComparator());
+				super.setObjects(objects);
+				setHasOpenCloseAll(true);
 			} else {
+				openAll();
 				List<MessageLightViewRow> views = new ForumMessageDataModelSort(orderBy, this, null).sort();
 				super.setObjects(views);
+				setHasOpenCloseAll(false);
 			}
 		}
 	}
@@ -129,4 +149,5 @@ public class ForumMessageDataModel extends DefaultFlexiTableDataModel<MessageLig
 			super(orderBy, tableModel, locale);
 		}
 	}
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/fo/ui/ForumMessageListController.java b/src/main/java/org/olat/modules/fo/ui/ForumMessageListController.java
index d3851ac74e3..8c6f62a032f 100644
--- a/src/main/java/org/olat/modules/fo/ui/ForumMessageListController.java
+++ b/src/main/java/org/olat/modules/fo/ui/ForumMessageListController.java
@@ -21,10 +21,7 @@ package org.olat.modules.fo.ui;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -51,6 +48,7 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTable
 import org.olat.core.gui.components.form.flexible.impl.elements.table.SelectionEvent;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlexiCellRenderer;
 import org.olat.core.gui.components.form.flexible.impl.elements.table.TextFlexiCellRenderer;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.TreeNodeFlexiCellRenderer;
 import org.olat.core.gui.components.link.Link;
 import org.olat.core.gui.control.Controller;
 import org.olat.core.gui.control.WindowControl;
@@ -61,7 +59,6 @@ import org.olat.core.util.mail.ui.BooleanCSSCellRenderer;
 import org.olat.core.util.resource.OresHelper;
 import org.olat.modules.fo.Forum;
 import org.olat.modules.fo.MessageLight;
-import org.olat.modules.fo.Status;
 import org.olat.modules.fo.manager.ForumManager;
 import org.olat.modules.fo.ui.ForumMessageDataModel.ForumMessageCols;
 import org.olat.modules.fo.ui.events.SelectMessageEvent;
@@ -72,18 +69,19 @@ import org.springframework.beans.factory.annotation.Autowired;
 /**
  * 
  * Initial date: 12.11.2015<br>
+ * 
  * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
  *
  */
 public class ForumMessageListController extends FormBasicController {
-	
+
 	protected static final String USER_PROPS_ID = ForumUserListController.class.getCanonicalName();
-	
+
 	public static final int USER_PROPS_OFFSET = 500;
-	
+
 	private FlexiTableElement tableEl;
 	private ForumMessageDataModel dataModel;
-	
+
 	private final Forum forum;
 	private final boolean withType;
 	private final boolean showMarks;
@@ -93,7 +91,7 @@ public class ForumMessageListController extends FormBasicController {
 	private final OLATResourceable forumOres;
 	private final List<UserPropertyHandler> userPropertyHandlers;
 	private MessageView userObject, selectView;
-	
+
 	@Autowired
 	private UserManager userManager;
 	@Autowired
@@ -102,26 +100,26 @@ public class ForumMessageListController extends FormBasicController {
 	private MarkingService markingService;
 	@Autowired
 	private BaseSecurityModule securityModule;
-	
-	public ForumMessageListController(UserRequest ureq, WindowControl wControl,
-			Forum forum, boolean withType, boolean showMarks, boolean showNew) {
+
+	public ForumMessageListController(UserRequest ureq, WindowControl wControl, Forum forum, boolean withType,
+			boolean showMarks, boolean showNew) {
 		super(ureq, wControl, LAYOUT_BAREBONE);
 		setTranslator(Util.createPackageTranslator(Forum.class, getLocale(), getTranslator()));
 		setTranslator(userManager.getPropertyHandlerTranslator(getTranslator()));
-		
+
 		this.forum = forum;
 		this.withType = withType;
 		this.showMarks = showMarks;
 		this.showNew = showNew;
 		this.guestOnly = ureq.getUserSession().getRoles().isGuestOnly();
 		forumOres = OresHelper.createOLATResourceableInstance("Forum", forum.getKey());
-		
+
 		isAdministrativeUser = securityModule.isUserAllowedAdminProps(ureq.getUserSession().getRoles());
 		userPropertyHandlers = userManager.getUserPropertyHandlersFor(USER_PROPS_ID, isAdministrativeUser);
-		
+
 		initForm(ureq);
 	}
-	
+
 	public MessageView getUserObject() {
 		return userObject;
 	}
@@ -139,11 +137,11 @@ public class ForumMessageListController extends FormBasicController {
 	}
 
 	public void loadAllMessages() {
-		Set<Long> readSet = !guestOnly? forumManager.getReadSet(getIdentity(), forum): Collections.emptySet();
+		Set<Long> readSet = !guestOnly ? forumManager.getReadSet(getIdentity(), forum) : Collections.emptySet();
 		List<MessageLight> allMessages = forumManager.getLightMessagesByForum(forum);
 		List<MessageLightView> views = new ArrayList<>(allMessages.size());
-		Map<Long,MessageLightView> keyToViews = new HashMap<>();
-		for(MessageLight message:allMessages) {
+		Map<Long, MessageLightView> keyToViews = new HashMap<>();
+		for (MessageLight message : allMessages) {
 			MessageLightView view = new MessageLightView(message, userPropertyHandlers, getLocale());
 			if (readSet.contains(message.getKey())) {
 				view.setNewMessage(false);
@@ -153,59 +151,68 @@ public class ForumMessageListController extends FormBasicController {
 			views.add(view);
 			keyToViews.put(view.getKey(), view);
 		}
-		
-		//calculate depth
+
+		// calculate depth
 		Map<Long, List<Long>> keyToParentline = new HashMap<>();
-		for(MessageLightView view:views) {
-			if(view.getParentKey() == null) {
+		for (MessageLightView view : views) {
+			if (view.getParentKey() == null) {
 				view.setDepth(0);
 			} else {
 				List<Long> parentLine = new ArrayList<>(5);
 				view.setDepth(1);
-				for(MessageLightView parent = keyToViews.get(view.getParentKey()); parent != null; parent = keyToViews.get(parent.getParentKey())) {
+				for (MessageLightView parent = keyToViews.get(view.getParentKey()); parent != null; parent = keyToViews
+						.get(parent.getParentKey())) {
 					view.setDepth(view.getDepth() + 1);
 					parentLine.add(parent.getKey());
 				}
 				keyToParentline.put(view.getKey(), parentLine);
 			}
 		}
-		
-		//order
-		List<MessageNode> threads = convertToThreadTrees(views);
-		Collections.sort(threads, new MessageNodeComparator());
-		List<MessageLightView> orderedViews = new ArrayList<>(allMessages.size());
-		flatTree(threads, orderedViews);
-		List<MessageLightViewRow> rows = appendLinks(orderedViews);
+
+		List<MessageLightViewRow> rows = forgeRows(views);
 		dataModel.setObjects(rows);
 	}
-	
+
 	public void loadMessages(List<MessageLightView> views) {
-		List<MessageLightViewRow> rows = appendLinks(views);
+		List<MessageLightViewRow> rows = forgeRows(views);
+		Collections.sort(rows, new MessageTreeRowComparator());
 		dataModel.setObjects(rows);
 		tableEl.reloadData();
 		tableEl.reset();
 	}
 
-	private List<MessageLightViewRow> appendLinks(List<MessageLightView> views) {
-		List<Mark> markList = !guestOnly
-				? markingService.getMarkManager().getMarks(forumOres, getIdentity(), null)
+	private List<MessageLightViewRow> forgeRows(List<MessageLightView> views) {
+		List<Mark> markList = !guestOnly ? markingService.getMarkManager().getMarks(forumOres, getIdentity(), null)
 				: Collections.emptyList();
-		Map<String,Mark> marks = new HashMap<>();
+		Map<String, Mark> marks = new HashMap<>();
 		for (Mark mark : markList) {
 			marks.put(mark.getResSubPath(), mark);
 		}
-		
+
 		List<MessageLightViewRow> rows = new ArrayList<>(views.size());
+		Map<Long, MessageLightViewRow> keysToRows = new HashMap<>();
 		for (MessageLightView view : views) {
 			Mark mark = marks.get(view.getKey().toString());
 			boolean marked = mark != null;
-			FormLink markLink = uifactory.addFormLink("mark_" + view.getKey(), "mark", "", null, null, Link.NONTRANSLATED);
-			markLink.setIconLeftCSS(marked? Mark.MARK_CSS_LARGE : Mark.MARK_ADD_CSS_LARGE);
-			
+			FormLink markLink = uifactory.addFormLink("mark_" + view.getKey(), "mark", "", null, null,
+					Link.NONTRANSLATED);
+			markLink.setIconLeftCSS(marked ? Mark.MARK_CSS_LARGE : Mark.MARK_ADD_CSS_LARGE);
+
 			MessageLightViewRow row = new MessageLightViewRow(view, mark, markLink);
 			markLink.setUserObject(row);
 			rows.add(row);
+			keysToRows.put(view.getKey(), row);
 		}
+
+		// parent
+		for (MessageLightViewRow row : rows) {
+			Long parentKey = row.getView().getParentKey();
+			if (parentKey != null) {
+				MessageLightViewRow parent = keysToRows.get(parentKey);
+				row.setParent(parent);
+			}
+		}
+
 		return rows;
 	}
 
@@ -213,34 +220,39 @@ public class ForumMessageListController extends FormBasicController {
 	protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) {
 		List<FlexiTableSort> sorts = new ArrayList<>();
 		sorts.add(new FlexiTableSort(translate("natural.sort"), "natural"));
-		
+
 		FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
-		if(withType) {
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ForumMessageCols.type, new StatusTypeCellRenderer()));
+		if (withType) {
+			columnsModel.addFlexiColumnModel(
+					new DefaultFlexiColumnModel(ForumMessageCols.type, new StatusTypeCellRenderer()));
 			sorts.add(new FlexiTableSort(translate(ForumMessageCols.type.i18nHeaderKey()), ForumMessageCols.type.name()));
 		}
-		
+
 		if (showMarks) {
 			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ForumMessageCols.mark));
 		}
-		
-		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ForumMessageCols.thread,
-				"select", new StaticFlexiCellRenderer("select", new IndentCellRenderer())));
+
+		TreeNodeFlexiCellRenderer treeNodeRenderer = new TreeNodeFlexiCellRenderer("select");
+		treeNodeRenderer.setFlatBySearchAndFilter(true);
+		treeNodeRenderer.setFlatBySort(true);
+		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ForumMessageCols.thread, "select", treeNodeRenderer));
 		sorts.add(new FlexiTableSort(translate(ForumMessageCols.thread.i18nHeaderKey()), ForumMessageCols.thread.name()));
-		
+
 		int colPos = USER_PROPS_OFFSET;
 		for (UserPropertyHandler userPropertyHandler : userPropertyHandlers) {
-			if (userPropertyHandler == null) continue;
+			if (userPropertyHandler == null)
+				continue;
 
 			String propName = userPropertyHandler.getName();
-			boolean visible = userManager.isMandatoryUserProperty(USER_PROPS_ID , userPropertyHandler);
+			boolean visible = userManager.isMandatoryUserProperty(USER_PROPS_ID, userPropertyHandler);
 
 			FlexiColumnModel col;
-			if(UserConstants.FIRSTNAME.equals(propName) || UserConstants.LASTNAME.equals(propName)) {
-				col = new DefaultFlexiColumnModel(userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos, "select", true, propName,
-						new StaticFlexiCellRenderer("select", new TextFlexiCellRenderer()));
+			if (UserConstants.FIRSTNAME.equals(propName) || UserConstants.LASTNAME.equals(propName)) {
+				col = new DefaultFlexiColumnModel(userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos, "select",
+						true, propName, new StaticFlexiCellRenderer("select", new TextFlexiCellRenderer()));
 			} else {
-				col = new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos, true, propName);
+				col = new DefaultFlexiColumnModel(visible, userPropertyHandler.i18nColumnDescriptorLabelKey(), colPos,
+						true, propName);
 			}
 
 			sorts.add(new FlexiTableSort(translate(userPropertyHandler.i18nColumnDescriptorLabelKey()), propName));
@@ -249,29 +261,31 @@ public class ForumMessageListController extends FormBasicController {
 		}
 
 		columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ForumMessageCols.lastModified));
-		sorts.add(new FlexiTableSort(translate(ForumMessageCols.lastModified.i18nHeaderKey()), ForumMessageCols.lastModified.name()));
+		sorts.add(new FlexiTableSort(translate(ForumMessageCols.lastModified.i18nHeaderKey()),
+				ForumMessageCols.lastModified.name()));
 
-		if(showNew && !guestOnly) {
+		if (showNew && !guestOnly) {
 			FlexiCellRenderer newMessageRenderer = new BooleanCSSCellRenderer(getTranslator(),
 					"o_icon o_forum_new_icon", null, "table.new.message.hover", null);
-			columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(ForumMessageCols.newMessage, newMessageRenderer));
+			columnsModel
+					.addFlexiColumnModel(new DefaultFlexiColumnModel(ForumMessageCols.newMessage, newMessageRenderer));
 		}
 
 		dataModel = new ForumMessageDataModel(columnsModel, getTranslator());
 		tableEl = uifactory.addTableElement(getWindowControl(), "messages", dataModel, getTranslator(), formLayout);
 		tableEl.setCssDelegate(new MessageCssDelegate());
-		
+
 		FlexiTableSortOptions sortOptions = new FlexiTableSortOptions();
 		sortOptions.setFromColumnModel(false);
 		sortOptions.setSorts(sorts);
 		tableEl.setSortSettings(sortOptions);
 	}
-	
+
 	@Override
 	protected void doDispose() {
 		//
 	}
-	
+
 	@Override
 	protected void formOK(UserRequest ureq) {
 		//
@@ -279,28 +293,25 @@ public class ForumMessageListController extends FormBasicController {
 
 	@Override
 	protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) {
-		if(tableEl == source) {
-			if(event instanceof SelectionEvent) {
-				SelectionEvent se = (SelectionEvent)event;
+		if (tableEl == source) {
+			if (event instanceof SelectionEvent) {
+				SelectionEvent se = (SelectionEvent) event;
 				String cmd = se.getCommand();
-				if("select".equals(cmd)) {
+				if ("select".equals(cmd)) {
 					MessageLightView message = dataModel.getObject(se.getIndex()).getView();
 					fireEvent(ureq, new SelectMessageEvent(SelectMessageEvent.SELECT_MESSAGE, message.getKey()));
 				}
 			}
-		} else if(source instanceof FormLink) {
-			FormLink link = (FormLink)source;
+		} else if (source instanceof FormLink) {
+			FormLink link = (FormLink) source;
 			String cmd = link.getCmd();
 			if ("mark".equals(cmd)) {
 				MessageLightViewRow row = (MessageLightViewRow) link.getUserObject();
 				if (row.isMarked()) {
 					doUnmark(row);
-					link.setIconLeftCSS(Mark.MARK_ADD_CSS_LARGE);
 				} else {
 					doMark(row);
-					link.setIconLeftCSS(Mark.MARK_CSS_LARGE);
 				}
-				link.getComponent().setDirty(true);
 				fireEvent(ureq, new MessageMarkedEvent(selectView.getKey(), row.getView().getKey()));
 			}
 		}
@@ -310,115 +321,74 @@ public class ForumMessageListController extends FormBasicController {
 	private void doMark(MessageLightViewRow row) {
 		MessageLightView view = row.getView();
 		Mark currentMark = row.getMark();
-		String businessPath = currentMark == null ?
-				getWindowControl().getBusinessControl().getAsString() + "[Message:" + view.getKey() + "]"
+		String businessPath = currentMark == null
+				? getWindowControl().getBusinessControl().getAsString() + "[Message:" + view.getKey() + "]"
 				: currentMark.getBusinessPath();
-		Mark mark = markingService.getMarkManager().setMark(forumOres, getIdentity(), view.getKey().toString(), businessPath);
+		Mark mark = markingService.getMarkManager().setMark(forumOres, getIdentity(), view.getKey().toString(),
+				businessPath);
+		onMarked(row, mark);
+	}
+
+	public void onMarked(Mark mark) {
+		String resSubPath = mark.getResSubPath();
+		Long messageKey = toMessageKey(resSubPath);
+		if (messageKey != null) {
+			for (MessageLightViewRow row : dataModel.getObjects()) {
+				if (row.getView().getKey().equals(messageKey)) {
+					onMarked(row, mark);
+					return;
+				}
+			}
+		}
+	}
+
+	private void onMarked(MessageLightViewRow row, Mark mark) {
 		row.setMark(mark);
+		row.getMarkLink().setIconLeftCSS(Mark.MARK_CSS_LARGE);
+		row.getMarkLink().getComponent().setDirty(true);
 	}
 
 	private void doUnmark(MessageLightViewRow row) {
 		MessageLightView view = row.getView();
-		markingService.getMarkManager().removeMark(forumOres, getIdentity(), view.getKey().toString());
-		row.setMark(null);
+		String resSubPath = view.getKey().toString();
+		markingService.getMarkManager().removeMark(forumOres, getIdentity(), resSubPath);
+		onUnmarked(row);
 	}
 
-	private void flatTree(List<MessageNode> nodes, List<MessageLightView> orderedViews) {
-		for(MessageNode node:nodes) {
-			orderedViews.add(node.getView());
-			if(node.hasChildren()) {
-				flatTree(node.getChildren(), orderedViews);
+	public void onUnmarked(String resSubPath) {
+		Long messageKey = toMessageKey(resSubPath);
+		if (messageKey != null) {
+			for (MessageLightViewRow row : dataModel.getObjects()) {
+				if (row.getView().getKey().equals(messageKey)) {
+					onUnmarked(row);
+					return;
+				}
 			}
 		}
 	}
-	
-	private List<MessageNode> convertToThreadTrees(List<MessageLightView> messages){
-		List<MessageNode> topNodeList = new ArrayList<>();
-	
-		for (Iterator<MessageLightView> iterTop = messages.iterator(); iterTop.hasNext();) {
-			MessageLightView msg = iterTop.next();
-			if (msg.getParentKey() == null) {
-				iterTop.remove();
-				MessageNode topNode = new MessageNode(msg);
-				addChildren(messages, topNode);
-				topNodeList.add(topNode);
-			}
-		}	
-		return topNodeList;
+
+	private void onUnmarked(MessageLightViewRow row) {
+		row.setMark(null);
+		row.getMarkLink().setIconLeftCSS(Mark.MARK_ADD_CSS_LARGE);
+		row.getMarkLink().getComponent().setDirty(true);
 	}
-	
-	private void addChildren(List<MessageLightView> messages, MessageNode mn){
-		for(Iterator<MessageLightView> iterMsg = messages.iterator(); iterMsg.hasNext(); ) {
-			MessageLightView msg = iterMsg.next();
-			if ((msg.getParentKey() != null) && (msg.getParentKey().equals(mn.getKey()))){
-				MessageNode childNode = new MessageNode(msg);
-				mn.addChild(childNode);
-				addChildren(messages, childNode);
-			}
+
+	private Long toMessageKey(String resSubPath) {
+		try {
+			return Long.parseLong(resSubPath);
+		} catch (NumberFormatException e) {
+			//
 		}
+		return null;
 	}
-	
+
 	private class MessageCssDelegate extends DefaultFlexiTableCssDelegate {
 		@Override
 		public String getRowCssClass(FlexiTableRendererType type, int pos) {
 			MessageLightView row = dataModel.getObject(pos).getView();
-			return row != null && selectView != null && row.getKey().equals(selectView.getKey()) ? "o_row_selected" : null;
-		}
-	}
-	
-	private static class MessageNode {
-		
-		private final MessageLightView view;
-		private List<MessageNode> children;
-		
-		public MessageNode(MessageLightView view) {
-			this.view = view;
-		}
-		
-		public Long getKey() {
-			return view.getKey();
-		}
-		
-		public boolean isSticky() {
-			return Status.getStatus(view.getStatusCode()).isSticky();
-		}
-		
-		public Date getLastModified() {
-			return view.getLastModified();
-		}
-		
-		public MessageLightView getView() {
-			return view;
-		}
-		
-		public boolean hasChildren() {
-			return children != null && children.size() > 0;
-		}
-		
-		public void addChild(MessageNode child) {
-			if(children == null) {
-				children = new ArrayList<>();
-			}
-			children.add(child);
-		}
-		
-		public List<MessageNode> getChildren() {
-			return children;
-		}
-	}
-	
-	public static class MessageNodeComparator implements Comparator<MessageNode> {
-		@Override
-		public int compare(final MessageNode m1, final MessageNode m2) {			
-			if(m1.isSticky() && m2.isSticky()) {
-				return m2.getLastModified().compareTo(m1.getLastModified()); //last first
-			} else if(m1.isSticky()) {
-				return -1;
-			} else if(m2.isSticky()){
-				return 1;
-			} else {
-				return m2.getLastModified().compareTo(m1.getLastModified()); //last first
-			}				
+			return row != null && selectView != null && row.getKey().equals(selectView.getKey()) ? "o_row_selected"
+					: null;
 		}
 	}
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/olat/modules/fo/ui/IndentCellRenderer.java b/src/main/java/org/olat/modules/fo/ui/IndentCellRenderer.java
deleted file mode 100644
index a4bceb094b9..00000000000
--- a/src/main/java/org/olat/modules/fo/ui/IndentCellRenderer.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * <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.fo.ui;
-
-import org.olat.core.commons.persistence.SortKey;
-import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiCellRenderer;
-import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableComponent;
-import org.olat.core.gui.render.Renderer;
-import org.olat.core.gui.render.StringOutput;
-import org.olat.core.gui.render.URLBuilder;
-import org.olat.core.gui.translator.Translator;
-import org.olat.core.util.Formatter;
-
-/**
- * 
- * Initial date: 12.11.2015<br>
- * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com
- *
- */
-public class IndentCellRenderer implements FlexiCellRenderer {
-
-	private static final int MAXINDENTS = 20;
-	
-	@Override
-	public void render(Renderer renderer, StringOutput target, Object cellValue, int row,
-			FlexiTableComponent source, URLBuilder ubu, Translator translator) {
-		
-		SortKey[] keys = source.getFlexiTableElement().getOrderBy();
-		if(keys != null && keys.length > 0 && keys[0] != null && !"natural".equals(keys[0].getKey())) {
-			if(cellValue instanceof String) {
-				target.append((String)cellValue);
-			}
-		} else {
-			Object m = source.getFlexiTableElement().getTableDataModel().getObject(row);
-			if(m instanceof MessageLightView && cellValue instanceof String) {
-				MessageLightView message = (MessageLightView)m;
-				int indent = message.getDepth();
-				if (indent > MAXINDENTS) {
-					indent = MAXINDENTS;
-				}
-				target.append("<div style=\"white-space: nowrap;")
-				      .append("padding-left: ").append(indent).append("em;\">")
-				      .append(Formatter.truncate((String)cellValue, 50 - indent))
-				      .append("</div>");
-			} else if(cellValue instanceof String) {
-				target.append((String)cellValue);
-			}
-		}
-	}
-}
diff --git a/src/main/java/org/olat/modules/fo/ui/MessageLightViewRow.java b/src/main/java/org/olat/modules/fo/ui/MessageLightViewRow.java
index beaa850b8cc..fe4aa20da23 100644
--- a/src/main/java/org/olat/modules/fo/ui/MessageLightViewRow.java
+++ b/src/main/java/org/olat/modules/fo/ui/MessageLightViewRow.java
@@ -19,8 +19,12 @@
  */
 package org.olat.modules.fo.ui;
 
+import java.util.Date;
+
 import org.olat.core.commons.services.mark.Mark;
 import org.olat.core.gui.components.form.flexible.elements.FormLink;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTreeTableNode;
+import org.olat.modules.fo.Status;
 
 /**
  * 
@@ -28,11 +32,13 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
  * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
  *
  */
-public class MessageLightViewRow {
+public class MessageLightViewRow implements FlexiTreeTableNode {
 
 	private final MessageLightView view;
 	private Mark mark;
 	private final FormLink markLink;
+	private MessageLightViewRow parent;
+	private boolean hasChildren;
 	
 	public MessageLightViewRow(MessageLightView view, Mark mark, FormLink markLink) {
 		this.view = view;
@@ -59,4 +65,34 @@ public class MessageLightViewRow {
 	public FormLink getMarkLink() {
 		return markLink;
 	}
+	
+	public boolean isSticky() {
+		return Status.getStatus(view.getStatusCode()).isSticky();
+	}
+	
+	public Date getLastModified() {
+		return view.getLastModified();
+	}
+
+	public void setParent(MessageLightViewRow parent) {
+		this.parent = parent;
+		if (parent != null) {
+			parent.hasChildren = true;
+		}
+	}
+
+	@Override
+	public FlexiTreeTableNode getParent() {
+		return parent;
+	}
+
+	@Override
+	public String getCrump() {
+		return null;
+	}
+
+	public boolean hasChildren() {
+		return hasChildren;
+	}
+
 }
diff --git a/src/main/java/org/olat/modules/fo/ui/MessageListController.java b/src/main/java/org/olat/modules/fo/ui/MessageListController.java
index f5cb5726be3..614812011b4 100644
--- a/src/main/java/org/olat/modules/fo/ui/MessageListController.java
+++ b/src/main/java/org/olat/modules/fo/ui/MessageListController.java
@@ -913,8 +913,12 @@ public class MessageListController extends BasicController implements GenericEve
 				MessageView splitedMessage = (MessageView)confirmSplitCtrl.getUserObject();
 				doSplitThread(ureq, splitedMessage);
 			}
-		} else if(event instanceof MarkedEvent || event instanceof UnmarkedEvent) {
-			reloadMessageTable(ureq);
+		} else if(event instanceof MarkedEvent) {
+			MarkedEvent me = (MarkedEvent) event;
+			messageTableCtrl.onMarked(me.getMark());
+		} else if(event instanceof UnmarkedEvent) {
+			UnmarkedEvent ue = (UnmarkedEvent)event;
+			messageTableCtrl.onUnmarked(ue.getSubPath());
 		} else if(source == cmc) {
 			cleanUp();
 		}
@@ -1289,13 +1293,6 @@ public class MessageListController extends BasicController implements GenericEve
 		}
 	}
 
-	private void reloadMessageTable(UserRequest ureq) {
-		String settings = getViewSettings(ureq);
-		if(VIEWMODE_MESSAGE.equals(settings)) {
-			doShowOne(ureq);
-		}
-	}
-	
 	private String getViewSettings(UserRequest ureq) {
 		Preferences prefs = ureq.getUserSession().getGuiPreferences();
 		Object setting = prefs.get(GUI_PREFS_VIEWMODE_CLASS, GUI_PREFS_VIEWMODE_KEY);
diff --git a/src/main/java/org/olat/modules/fo/ui/MessageTreeRowComparator.java b/src/main/java/org/olat/modules/fo/ui/MessageTreeRowComparator.java
new file mode 100644
index 00000000000..c91f45c2675
--- /dev/null
+++ b/src/main/java/org/olat/modules/fo/ui/MessageTreeRowComparator.java
@@ -0,0 +1,56 @@
+/**
+ * <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.fo.ui;
+
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTreeNodeComparator;
+import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTreeTableNode;
+
+/**
+ * 
+ * Initial date: 5 Aug 2019<br>
+ * @author uhensler, urs.hensler@frentix.com, http://www.frentix.com
+ *
+ */
+public class MessageTreeRowComparator  extends FlexiTreeNodeComparator {
+
+	@Override
+	protected int compareNodes(FlexiTreeTableNode o1, FlexiTreeTableNode o2) {
+		if (o1 instanceof MessageLightViewRow && o2 instanceof MessageLightViewRow) {
+			MessageLightViewRow row1 = (MessageLightViewRow)o1;
+			MessageLightViewRow row2 = (MessageLightViewRow)o2;
+			return compare(row1, row2);
+			
+		}
+		return super.compareNodes(o1, o2);
+	}
+	
+	private int compare(final MessageLightViewRow m1, final MessageLightViewRow m2) {
+		if(m1.isSticky() && m2.isSticky()) {
+			return m2.getLastModified().compareTo(m1.getLastModified()); //last first
+		} else if(m1.isSticky()) {
+			return -1;
+		} else if(m2.isSticky()){
+			return 1;
+		} else {
+			return m2.getLastModified().compareTo(m1.getLastModified()); //last first
+		}
+	}
+
+}
-- 
GitLab