From a09ff3ea2a01cbd425b207690d6c98b06d26c7ad Mon Sep 17 00:00:00 2001
From: felmer <felmer>
Date: Tue, 22 Mar 2011 15:30:11 +0000
Subject: [PATCH] LMS-2146 HistoryWidget and embedding into welcome page

SVN: 20454
---
 .../client/web/client/application/Dict.java   |   2 +
 .../framework/WelcomePanelHelper.java         |  14 +-
 .../application/model/EntityVisitModel.java   |  41 ++++
 .../client/application/ui/AbstractViewer.java |   8 +
 .../application/ui/widget/HistoryWidget.java  | 183 ++++++++++++++++++
 .../generic/server/AbstractServer.java        |  30 ++-
 .../openbis/generic/server/CommonServer.java  |   3 +-
 .../EntityVisitComparatorByTimeStamp.java     |  20 ++
 .../generic/shared/basic/dto/EntityVisit.java |  14 ++
 .../cisd/openbis/public/common-dictionary.js  |   5 +
 10 files changed, 306 insertions(+), 14 deletions(-)
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/model/EntityVisitModel.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/HistoryWidget.java
 create mode 100644 openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/EntityVisitComparatorByTimeStamp.java

diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java
index 6332612f305..089742db2fb 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/Dict.java
@@ -1080,5 +1080,7 @@ public abstract class Dict
 
     public static final String WARNING_NO_SCRIPT_MESSAGE = "warning_no_script_message";
 
+    public static final String LAST_VISITS = "last_visits";
+
     // ----- end generic ------------------
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/WelcomePanelHelper.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/WelcomePanelHelper.java
index dd5a2b82392..7a8a5b4be90 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/WelcomePanelHelper.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/framework/WelcomePanelHelper.java
@@ -16,11 +16,14 @@
 
 package ch.systemsx.cisd.openbis.generic.client.web.client.application.framework;
 
+import com.extjs.gxt.ui.client.Style.LayoutRegion;
 import com.extjs.gxt.ui.client.widget.Component;
 import com.extjs.gxt.ui.client.widget.LayoutContainer;
-import com.extjs.gxt.ui.client.widget.layout.FitLayout;
+import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
+import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
 
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget.HistoryWidget;
 
 /**
  * Helper methods for creating a panel with welcome information.
@@ -35,10 +38,15 @@ public class WelcomePanelHelper
 
     public static final Component createWelcomePanel(IViewContext<?> viewContext, String idPrefix)
     {
-        final LayoutContainer layoutContainer = new LayoutContainer(new FitLayout());
+        final LayoutContainer layoutContainer = new LayoutContainer(new BorderLayout());
+        layoutContainer.setStyleAttribute("background-color", "white");
         layoutContainer.setId(idPrefix + "welcome");
+        if (viewContext.getDisplaySettingsManager().getVisits().isEmpty() == false)
+        {
+            layoutContainer.add(new HistoryWidget(viewContext), new BorderLayoutData(LayoutRegion.WEST, 0.3f));
+        }
         HtmlPage welcomePage = new HtmlPage(getWelcomePageBaseName(viewContext));
-        layoutContainer.add(welcomePage);
+        layoutContainer.add(welcomePage, new BorderLayoutData(LayoutRegion.CENTER, 1f));
         return layoutContainer;
     }
 
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/model/EntityVisitModel.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/model/EntityVisitModel.java
new file mode 100644
index 00000000000..2ca3cccfc8e
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/model/EntityVisitModel.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.client.web.client.application.model;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityVisit;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class EntityVisitModel extends SimplifiedBaseModel
+{
+
+    private static final long serialVersionUID = 1L;
+
+    public EntityVisitModel(EntityVisit visit)
+    {
+        set(ModelDataPropertyNames.CODE, visit.getIdentifier());
+        set(ModelDataPropertyNames.OBJECT, visit);
+    }
+    
+    public EntityVisit getVisit()
+    {
+        return (EntityVisit) get(ModelDataPropertyNames.OBJECT);
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
index 39c15fc4eee..3118b209ab4 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/AbstractViewer.java
@@ -47,7 +47,9 @@ import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IDele
 import ch.systemsx.cisd.openbis.generic.client.web.client.application.util.IMessageProvider;
 import ch.systemsx.cisd.openbis.generic.shared.basic.ICodeHolder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolder;
+import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithProperties;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityVisit;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IEntityProperty;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.api.IManagedProperty;
 
@@ -184,6 +186,12 @@ public abstract class AbstractViewer<D extends IEntityInformationHolder> extends
     /** Updates data displayed in the browser (needed to open editor view). */
     protected void updateOriginalData(D newData)
     {
+        if (newData instanceof IEntityInformationHolderWithIdentifier)
+        {
+            IEntityInformationHolderWithIdentifier entity = (IEntityInformationHolderWithIdentifier) newData;
+            EntityVisit entityVisit = new EntityVisit(entity);
+            viewContext.getDisplaySettingsManager().rememberVisit(entityVisit);
+        }
         this.originalData = newData;
         this.displayIdSuffix = newData.getEntityType().getCode();
         updateTitle(getOriginalDataDescription());
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/HistoryWidget.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/HistoryWidget.java
new file mode 100644
index 00000000000..292c712b698
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/widget/HistoryWidget.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.widget;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.extjs.gxt.ui.client.data.ModelData;
+import com.extjs.gxt.ui.client.store.ListStore;
+import com.extjs.gxt.ui.client.store.TreeStore;
+import com.extjs.gxt.ui.client.widget.ContentPanel;
+import com.extjs.gxt.ui.client.widget.Label;
+import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
+import com.extjs.gxt.ui.client.widget.grid.ColumnData;
+import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
+import com.extjs.gxt.ui.client.widget.grid.Grid;
+import com.extjs.gxt.ui.client.widget.layout.FitLayout;
+import com.extjs.gxt.ui.client.widget.treegrid.TreeGrid;
+import com.extjs.gxt.ui.client.widget.treegrid.WidgetTreeGridCellRenderer;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Widget;
+
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.Dict;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.IViewContext;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.EntityVisitModel;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.ModelDataPropertyNames;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.model.SimplifiedBaseModel;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.renderer.LinkRenderer;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.columns.framework.LinkExtractor;
+import ch.systemsx.cisd.openbis.generic.client.web.client.application.ui.listener.OpenEntityDetailsTabHelper;
+import ch.systemsx.cisd.openbis.generic.shared.basic.EntityVisitComparatorByTimeStamp;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityVisit;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.MaterialIdentifier;
+
+/**
+ * History of last visited detail views.
+ *
+ * @author Franz-Josef Elmer
+ */
+public class HistoryWidget extends ContentPanel
+{
+    private static final class SimpleModel extends SimplifiedBaseModel
+    {
+        private static final long serialVersionUID = 1L;
+
+        public SimpleModel(String item)
+        {
+            set(ModelDataPropertyNames.CODE, item);
+        }
+    }
+    
+    public HistoryWidget(final IViewContext<?> viewContext)
+    {
+        setLayout(new FitLayout());
+        setHeaderVisible(true);
+        setHeading(viewContext.getMessage(Dict.LAST_VISITS));
+        TreeStore<ModelData> store = createStore(viewContext);
+        ColumnModel columnModel = createColumnModel(viewContext);
+
+        final TreeGrid<ModelData> treeGrid = new TreeGrid<ModelData>(store, columnModel);
+        treeGrid.setAutoExpandColumn(ModelDataPropertyNames.CODE);
+        treeGrid.getTreeView().setForceFit(true);
+        treeGrid.getTreeView().setSortingEnabled(false);
+        treeGrid.getStyle().setNodeCloseIcon(null);
+        treeGrid.getStyle().setNodeOpenIcon(null);
+        treeGrid.setAutoExpand(true);
+        treeGrid.setHideHeaders(true);
+        add(treeGrid);
+    }
+    
+    private TreeStore<ModelData> createStore(final IViewContext<?> viewContext)
+    {
+        TreeStore<ModelData> store = new TreeStore<ModelData>();
+        List<EntityVisit> visits = viewContext.getDisplaySettingsManager().getVisits();
+        Collections.sort(visits, new EntityVisitComparatorByTimeStamp());
+        EntityKind[] values = EntityKind.values();
+        for (EntityKind entityKind : values)
+        {
+            String entityKindAsString = entityKind.toString();
+            SimpleModel item = new SimpleModel(entityKind.getDescription() + "s");
+            store.add(item, true);
+            Map<String, List<EntityVisit>> typeToVisitMap = new HashMap<String, List<EntityVisit>>();
+            for (EntityVisit visit : visits)
+            {
+                if (entityKindAsString.equals(visit.getEntityKind()))
+                {
+                    List<EntityVisit> list = typeToVisitMap.get(visit.getEntityTypeCode());
+                    if (list == null)
+                    {
+                        list = new ArrayList<EntityVisit>();
+                        typeToVisitMap.put(visit.getEntityTypeCode(), list);
+                    }
+                    list.add(visit);
+                }
+            }
+            String[] types = typeToVisitMap.keySet().toArray(new String[0]);
+            Arrays.sort(types);
+            for (String type : types)
+            {
+                SimpleModel typeModel = new SimpleModel(type);
+                store.add(item, typeModel, true);
+                List<EntityVisit> list = typeToVisitMap.get(type);
+                Set<String> permIds = new HashSet<String>();
+                for (EntityVisit visit : list)
+                {
+                    String permID = visit.getPermID();
+                    if (permIds.contains(permID) == false)
+                    {
+                        permIds.add(permID);
+                        store.add(typeModel, new EntityVisitModel(visit), false);
+                    }
+                }
+            }
+        }
+        return store;
+    }
+
+    private ColumnModel createColumnModel(final IViewContext<?> viewContext)
+    {
+        ColumnConfig columnConfig = new ColumnConfig(ModelDataPropertyNames.CODE, "", 1);
+        columnConfig.setRenderer(new WidgetTreeGridCellRenderer<ModelData>()
+            {
+                @Override
+                public Widget getWidget(ModelData model, String property, ColumnData config,
+                        int rowIndex, int colIndex, ListStore<ModelData> store, Grid<ModelData> grid)
+                {
+                    if (model instanceof EntityVisitModel)
+                    {
+                        EntityVisitModel evm = (EntityVisitModel) model;
+
+                        EntityVisit visit = evm.getVisit();
+                        final String displayText = visit.getIdentifier();
+                        final EntityKind entityKind = EntityKind.valueOf(visit.getEntityKind());
+                        final String permID = visit.getPermID();
+                        final String href;
+                        if (entityKind == EntityKind.MATERIAL)
+                        {
+                            href =
+                                    LinkExtractor.tryExtract(MaterialIdentifier
+                                            .tryParseIdentifier(permID));
+                        } else
+                        {
+                            href = LinkExtractor.createPermlink(entityKind, permID);
+                        }
+                        final ClickHandler listener = new ClickHandler()
+                            {
+                                public void onClick(ClickEvent event)
+                                {
+                                    OpenEntityDetailsTabHelper.open(viewContext, entityKind,
+                                            permID, false);
+                                }
+                            };
+                        return LinkRenderer.getLinkWidget(displayText, listener, href, false);
+                    }
+                    return new Label(model.get(property).toString());
+                }
+            });
+        columnConfig.setMenuDisabled(true);
+        return new ColumnModel(Arrays.asList(columnConfig));
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
index df590950ab2..1ad976e90de 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/AbstractServer.java
@@ -18,7 +18,8 @@ package ch.systemsx.cisd.openbis.generic.server;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -42,6 +43,7 @@ import ch.systemsx.cisd.openbis.generic.shared.IRemoteHostValidator;
 import ch.systemsx.cisd.openbis.generic.shared.IServer;
 import ch.systemsx.cisd.openbis.generic.shared.ResourceNames;
 import ch.systemsx.cisd.openbis.generic.shared.authorization.validator.ExpressionValidator;
+import ch.systemsx.cisd.openbis.generic.shared.basic.EntityVisitComparatorByTimeStamp;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DisplaySettings;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityKind;
@@ -362,15 +364,7 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
             if (person != null)
             {
                 List<EntityVisit> visits = joinVisits(displaySettings, person);
-                Collections.sort(visits, new Comparator<EntityVisit>()
-                    {
-                        public int compare(EntityVisit o1, EntityVisit o2)
-                        {
-                            long t1 = o1.getTimeStamp();
-                            long t2 = o2.getTimeStamp();
-                            return t1 < t2 ? 1 : (t1 > t2 ? -1 : 0);
-                        }
-                    });
+                sortAndRemoveMultipleVisits(visits);
                 for (int i = visits.size() - 1; i >= maxEntityVisits; i--)
                 {
                     visits.remove(i);
@@ -384,6 +378,22 @@ public abstract class AbstractServer<T> extends AbstractServiceWithLogger<T> imp
         }
     }
 
+    private void sortAndRemoveMultipleVisits(List<EntityVisit> visits)
+    {
+        Collections.sort(visits, new EntityVisitComparatorByTimeStamp());
+        Set<String> permIds = new HashSet<String>();
+        for (Iterator<EntityVisit> iterator = visits.iterator(); iterator.hasNext();)
+        {
+            EntityVisit entityVisit = iterator.next();
+            String permID = entityVisit.getPermID();
+            if (permIds.contains(permID))
+            {
+                iterator.remove();
+            }
+            permIds.add(permID);
+        }
+    }
+
     @SuppressWarnings("deprecation")
     private List<EntityVisit> joinVisits(DisplaySettings displaySettings, PersonPE person)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
index d021068b88f..babb64cb943 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/CommonServer.java
@@ -1467,7 +1467,8 @@ public final class CommonServer extends AbstractCommonServer<ICommonServerForInt
                 return createInformationHolder(entityKind, permId, getDAOFactory().getPermIdDAO()
                         .tryToFindByPermId(permId, DtoConverters.convertEntityKind(entityKind)));
             case MATERIAL:
-                break;
+                MaterialIdentifier identifier = MaterialIdentifier.tryParseIdentifier(permId);
+                return getMaterialInformationHolder(sessionToken, identifier);
         }
         throw UserFailureException.fromTemplate("Operation not available for "
                 + entityKind.getDescription() + "s");
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/EntityVisitComparatorByTimeStamp.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/EntityVisitComparatorByTimeStamp.java
new file mode 100644
index 00000000000..eecf0089fcc
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/EntityVisitComparatorByTimeStamp.java
@@ -0,0 +1,20 @@
+package ch.systemsx.cisd.openbis.generic.shared.basic;
+
+import java.util.Comparator;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.EntityVisit;
+
+/**
+ * Comparator between {@link EntityVisit} instances. Newer visit comes before older visit. 
+ *
+ * @author Franz-Josef Elmer
+ */
+public class EntityVisitComparatorByTimeStamp implements Comparator<EntityVisit>
+{
+    public int compare(EntityVisit o1, EntityVisit o2)
+    {
+        long t1 = o1.getTimeStamp();
+        long t2 = o2.getTimeStamp();
+        return t1 < t2 ? 1 : (t1 > t2 ? -1 : 0);
+    }
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/EntityVisit.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/EntityVisit.java
index 5f4db6546f1..4ba5f2e5fb6 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/EntityVisit.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/EntityVisit.java
@@ -16,6 +16,7 @@
 
 package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
 
+import ch.systemsx.cisd.openbis.generic.shared.basic.IEntityInformationHolderWithIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.basic.ISerializable;
 
 /**
@@ -37,6 +38,19 @@ public class EntityVisit implements ISerializable
     private String permID;
     
     private long timeStamp;
+    
+    public EntityVisit()
+    {
+    }
+
+    public EntityVisit(IEntityInformationHolderWithIdentifier entity)
+    {
+        setEntityKind(entity.getEntityKind().toString());
+        setEntityTypeCode(entity.getEntityType().getCode());
+        setIdentifier(entity.getIdentifier());
+        setPermID(entity.getPermId());
+        setTimeStamp(System.currentTimeMillis());
+    }
 
     public String getEntityKind()
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js b/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js
index 453d1caa100..c93d7c65c91 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/public/common-dictionary.js
@@ -743,6 +743,11 @@ show_details: "Show",
 evaluation_in_progress: "Evaluation in progress...",
 warning_no_script_title: "Empty script",
 warning_no_script_message: "No script provided",
+
+//
+// History Widget
+//
+last_visits: "Last Visited Entities",
  
  // LAST LINE: KEEP IT AT THE END
   lastline: "" // we need a line without a comma
-- 
GitLab