From 8cd0a3254fe26fa0485f25d64c608dafedf3903c Mon Sep 17 00:00:00 2001
From: Nils Christian Ehmke <nie@informatik.uni-kiel.de>
Date: Thu, 30 May 2013 13:04:52 +0200
Subject: [PATCH] Further development of the cockpit editor.

---
 .../beans/view/CurrentCockpitEditorBean.java  | 288 ++++++++++++------
 .../main/webapp/pages/CockpitEditorPage.xhtml |  10 +-
 2 files changed, 208 insertions(+), 90 deletions(-)

diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java
index b473990d..e5b1db14 100644
--- a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java
+++ b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java
@@ -16,18 +16,28 @@
 
 package kieker.webgui.web.beans.view;
 
+import java.awt.Point;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import javax.annotation.Nullable;
 import javax.faces.application.Application;
 import javax.faces.application.FacesMessage;
 import javax.faces.component.UIComponent;
 import javax.faces.component.UIInput;
 import javax.faces.component.html.HtmlOutputText;
 import javax.faces.context.FacesContext;
+import javax.faces.event.AbortProcessingException;
+import javax.faces.event.AjaxBehaviorEvent;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 
 import kieker.analysis.model.analysisMetaModel.MIAnalysisMetaModelFactory;
 import kieker.analysis.model.analysisMetaModel.MIDisplay;
@@ -52,15 +62,20 @@ import kieker.webgui.web.beans.application.GlobalPropertiesBean;
 import kieker.webgui.web.beans.application.ProjectsBean;
 import kieker.webgui.web.beans.session.UserBean;
 
+import org.primefaces.component.behavior.ajax.AjaxBehavior;
+import org.primefaces.component.behavior.ajax.AjaxBehaviorListenerImpl;
 import org.primefaces.component.dashboard.Dashboard;
 import org.primefaces.component.panel.Panel;
 import org.primefaces.context.RequestContext;
+import org.primefaces.event.DashboardReorderEvent;
 import org.primefaces.event.TabChangeEvent;
 import org.primefaces.model.DashboardColumn;
 import org.primefaces.model.DashboardModel;
 import org.primefaces.model.DefaultDashboardColumn;
 import org.primefaces.model.DefaultDashboardModel;
 
+import org.eclipse.emf.common.util.EList;
+
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
@@ -79,7 +94,7 @@ public class CurrentCockpitEditorBean {
 	private static final int NUMBER_COLUMNS = 2;
 	private static final Log LOG = LogFactory.getLog(CurrentCockpitEditorBean.class);
 
-	private final MIAnalysisMetaModelFactory factory = new MAnalysisMetaModelFactory();
+	private final MIAnalysisMetaModelFactory factory = MAnalysisMetaModelFactory.eINSTANCE;
 	@Autowired
 	private IProjectService projectService;
 
@@ -157,38 +172,50 @@ public class CurrentCockpitEditorBean {
 			final Application application = fc.getApplication();
 
 			// Add a panel for every display connector we have
-			final List<MIDisplayConnector> displayConnectors = this.activeView.getDisplayConnectors();
 			this.currId = 0;
-			for (final MIDisplayConnector connector : displayConnectors) {
-				final Panel panel = (Panel) application.createComponent(fc, "org.primefaces.component.Panel", "org.primefaces.component.PanelRenderer");
-				panel.setId("displayConnector_" + Integer.toString(this.connectors.get(connector)));
-				panel.setHeader(connector.getName());
-				panel.setClosable(true);
-				panel.setToggleable(false);
-
-				this.getDashboard().getChildren().add(panel);
-				final DashboardColumn column = this.dashboard.getModel().getColumn(0);
-				column.addWidget(panel.getId());
-				final HtmlOutputText text = new HtmlOutputText();
-				text.setValue(connector.getDisplay().getName());
-
-				panel.getChildren().add(text);
-				this.currId++;
+
+			for (int col = 0; col < 2; col++) {
+				final Collection<MIDisplayConnector> displayConnectors = this.getSortedDisplayConnectors(this.activeView.getDisplayConnectors(), col);
+				final DashboardColumn column = this.dashboard.getModel().getColumn(col);
+
+				for (final MIDisplayConnector connector : displayConnectors) {
+					final Panel panel = (Panel) application.createComponent(fc, "org.primefaces.component.Panel", "org.primefaces.component.PanelRenderer");
+					panel.setId("displayConnector_" + Integer.toString(this.connectors.get(connector)));
+					panel.setHeader(connector.getName());
+					panel.setClosable(true);
+					panel.setToggleable(false);
+
+					final AjaxBehavior behaviour = new AjaxBehavior();
+					behaviour.setProcess("@this");
+					behaviour.addAjaxBehaviorListener(new AjaxBehaviorListenerImpl() {
+
+						@Override
+						public void processAjaxBehavior(final AjaxBehaviorEvent event) throws AbortProcessingException {
+							System.out.println(((Panel) event.getSource()).getId());
+						}
+
+					});
+					panel.addClientBehavior("close", behaviour);
+
+					this.getDashboard().getChildren().add(panel);
+
+					column.addWidget(panel.getId());
+					final HtmlOutputText text = new HtmlOutputText();
+					text.setValue(connector.getDisplay().getName());
+
+					panel.getChildren().add(text);
+					this.currId++;
+				}
 			}
 		}
 	}
 
-	/**
-	 * Clears the dashboard and removes all children within it.
-	 */
-	private void clearDashboard() {
-		// Run through all columns of the dashboard and remove the items
-		final List<DashboardColumn> columns = this.dashboard.getModel().getColumns();
-		for (final DashboardColumn column : columns) {
-			column.getWidgets().clear();
-		}
-		// Now run through the dashboard itself and remove the items as well
-		this.dashboard.getChildren().clear();
+	private Collection<MIDisplayConnector> getSortedDisplayConnectors(final EList<MIDisplayConnector> eList, final int col) {
+		final List<MIDisplayConnector> l = new ArrayList<MIDisplayConnector>(Collections2.filter(eList, new ColumnPredicate(col)));
+
+		Collections.sort(l, new PosComparator());
+
+		return l;
 	}
 
 	/**
@@ -206,6 +233,9 @@ public class CurrentCockpitEditorBean {
 					// Remember the current time! This is important for the later comparison of the time stamps.
 					this.resetTimeStamp();
 					this.reloadComponents();
+
+					this.stringToLayout(this.projectService.getCockpitLayout(this.projectName));
+
 					// Update the class loader and the specific classes used within various methods in this bean
 					this.fillDashboard();
 				}
@@ -221,18 +251,10 @@ public class CurrentCockpitEditorBean {
 		}
 	}
 
-	/**
-	 * Reloads the available components within this bean.
-	 */
 	private void reloadComponents() {
 		this.availableComponents = this.projectService.getAvailableComponents(this.projectName);
 	}
 
-	/**
-	 * This method delivers the project stored in this bean.
-	 * 
-	 * @return The project for this user.
-	 */
 	public MIProject getProject() {
 		return this.project;
 	}
@@ -246,21 +268,10 @@ public class CurrentCockpitEditorBean {
 		return Collections.nCopies(3, null);
 	}
 
-	/**
-	 * This method sets the project stored within this bean and returns the new page for the navigation.
-	 * 
-	 * @param newName
-	 *            The name of the project.
-	 */
 	public void setProjectName(final String newName) {
 		this.projectName = newName;
 	}
 
-	/**
-	 * This method delivers the project name stored in this bean.
-	 * 
-	 * @return The project name for this user.
-	 */
 	public String getProjectName() {
 		return this.projectName;
 	}
@@ -310,7 +321,7 @@ public class CurrentCockpitEditorBean {
 	public void saveProject(final boolean overwriteNewerProject) {
 		try {
 			this.projectService.saveProject(this.projectName, this.project, this.timeStamp, overwriteNewerProject, this.userBean.getUsername(), null,
-					this.projectService.getCockpitLayout(this.projectName));
+					this.layoutToString());
 			GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_INFO, this.globalPropertiesBean.getMsgProjectSaved());
 			// Update the time stamp!
 			this.resetTimeStamp();
@@ -420,7 +431,7 @@ public class CurrentCockpitEditorBean {
 			final Application application = fc.getApplication();
 
 			final Panel panel = (Panel) application.createComponent(fc, "org.primefaces.component.Panel", "org.primefaces.component.PanelRenderer");
-			panel.setId("displayConnector_" + Integer.toString(this.connectors.get(connector)));
+			panel.setId(this.displayConnectorToID(connector));
 			panel.setHeader(connector.getName());
 			panel.setClosable(true);
 			panel.setToggleable(false);
@@ -435,6 +446,33 @@ public class CurrentCockpitEditorBean {
 			this.currId++;
 
 			this.setModificationsFlag();
+
+			this.saveLayoutOfCurrentView();
+		}
+	}
+
+	private static final String ID_PREFIX = "displayConnector_";
+
+	final Map<MIView, Map<MIDisplayConnector, Point>> connectorPositions = new HashMap<MIView, Map<MIDisplayConnector, Point>>();
+
+	public void handleReorder(final DashboardReorderEvent event) {
+		this.saveLayoutOfCurrentView();
+	}
+
+	/**
+	 * This method is used as a validator for new display connector names.
+	 * 
+	 * @param context
+	 *            The context of the validation.
+	 * @param toValidate
+	 *            The components which has be validated.
+	 * @param value
+	 *            The new value.
+	 */
+	public void validateDisplayConnectorName(final FacesContext context, final UIComponent toValidate, final Object value) {
+		if ((value instanceof String) && (toValidate instanceof UIInput)) {
+			final boolean nameExists = this.existsDisplayConnectorName((String) value);
+			((UIInput) toValidate).setValid(!nameExists);
 		}
 	}
 
@@ -465,6 +503,26 @@ public class CurrentCockpitEditorBean {
 		}
 	}
 
+	/**
+	 * Getter for the property {@link CurrentCockpitEditorBean#dashboard}.
+	 * 
+	 * @return The current value of the property.
+	 */
+	public Dashboard getDashboard() {
+		return this.dashboard;
+	}
+
+	/**
+	 * Setter for the property {@link CurrentCockpitEditorBean#dashboard}.
+	 * 
+	 * @param dashboard
+	 *            The new value for the property.
+	 */
+	public void setDashboard(final Dashboard dashboard) {
+		this.dashboard = dashboard;
+		this.dashboard.setModel(this.dashboardModel);
+	}
+
 	/**
 	 * This method checks whether a display connector with the given name exists already.
 	 * 
@@ -491,50 +549,108 @@ public class CurrentCockpitEditorBean {
 		return result;
 	}
 
-	/**
-	 * This method is used as a validator for new display connector names.
-	 * 
-	 * @param context
-	 *            The context of the validation.
-	 * @param toValidate
-	 *            The components which has be validated.
-	 * @param value
-	 *            The new value.
-	 */
-	public void validateDisplayConnectorName(final FacesContext context, final UIComponent toValidate, final Object value) {
-		if ((value instanceof String) && (toValidate instanceof UIInput)) {
-			final boolean nameExists = this.existsDisplayConnectorName((String) value);
-			((UIInput) toValidate).setValid(!nameExists);
+	private void clearDashboard() {
+		// Run through all columns of the dashboard model and remove the items
+		final List<DashboardColumn> columns = this.dashboard.getModel().getColumns();
+		for (final DashboardColumn column : columns) {
+			column.getWidgets().clear();
 		}
+		// Now remove the items from the dashboard
+		this.dashboard.getChildren().clear();
 	}
 
-	/**
-	 * Getter for the property {@link CurrentCockpitEditorBean#dashboard}.
-	 * 
-	 * @return The current value of the property.
-	 */
-	public Dashboard getDashboard() {
-		return this.dashboard;
+	private void saveLayoutOfCurrentView() {
+		if (this.activeView != null) {
+			final Map<MIDisplayConnector, Point> layout = new HashMap<MIDisplayConnector, Point>();
+
+			// Run through all columns and put the position of every widget into the map
+			int col = 0;
+			for (final DashboardColumn column : this.dashboardModel.getColumns()) {
+				int row = 0;
+				for (final String widgetID : column.getWidgets()) {
+					layout.put(this.idToDisplayConnector(widgetID), new Point(col, row));
+					row++;
+				}
+				col++;
+			}
+
+			// Store the map for the whole view
+			this.connectorPositions.put(this.activeView, layout);
+		}
 	}
 
-	/**
-	 * Setter for the property {@link CurrentCockpitEditorBean#dashboard}.
-	 * 
-	 * @param dashboard
-	 *            The new value for the property.
-	 */
-	public void setDashboard(final Dashboard dashboard) {
-		this.dashboard = dashboard;
-		this.dashboard.setModel(this.dashboardModel);
+	private void stringToLayout(final String layout) {
+		final String[] elements = layout.split(" ");
+		int i = 0;
+
+		for (final MIView view : this.project.getViews()) {
+			final Map<MIDisplayConnector, Point> positions = new HashMap<MIDisplayConnector, Point>();
+			this.connectorPositions.put(view, positions);
+			for (final MIDisplayConnector connector : view.getDisplayConnectors()) {
+				final Point pos = new Point(Integer.valueOf(elements[i]), Integer.valueOf(elements[i + 1]));
+
+				positions.put(connector, pos);
+
+				i += 2;
+			}
+		}
+
 	}
 
-	/**
-	 * Delivers the available components.
-	 * 
-	 * @return A list with the available components.
-	 */
-	public ComponentListContainer getAvailableComponents() {
-		return this.availableComponents;
+	private String layoutToString() {
+		final StringBuilder builder = new StringBuilder();
+
+		for (final MIView view : this.project.getViews()) {
+			final Map<MIDisplayConnector, Point> positions = this.connectorPositions.get(view);
+			for (final MIDisplayConnector connector : view.getDisplayConnectors()) {
+				final Point pos = positions.get(connector);
+
+				builder.append(pos.x);
+				builder.append(' ');
+				builder.append(pos.y);
+				builder.append(' ');
+			}
+		}
+
+		return builder.toString();
+	}
+
+	private String displayConnectorToID(final MIDisplayConnector displayConnector) {
+		return ID_PREFIX + this.connectors.get(displayConnector);
+	}
+
+	private MIDisplayConnector idToDisplayConnector(final String id) {
+		final String shortID = id.substring(ID_PREFIX.length());
+		final int intID = Integer.valueOf(shortID);
+
+		return this.connectors.get(intID);
+	}
+
+	private class PosComparator implements Comparator<MIDisplayConnector> {
+
+		@Override
+		public int compare(final MIDisplayConnector o1, final MIDisplayConnector o2) {
+			final int pos1 = CurrentCockpitEditorBean.this.connectorPositions.get(CurrentCockpitEditorBean.this.activeView).get(o1).y;
+			final int pos2 = CurrentCockpitEditorBean.this.connectorPositions.get(CurrentCockpitEditorBean.this.activeView).get(o2).y;
+
+			return pos1 - pos2;
+		}
+
+	}
+
+	private class ColumnPredicate implements Predicate<MIDisplayConnector> {
+
+		private final int col;
+
+		public ColumnPredicate(final int col) {
+			this.col = col;
+		}
+
+		@Override
+		public boolean apply(@Nullable final MIDisplayConnector key) {
+			return CurrentCockpitEditorBean.this.connectorPositions.get(CurrentCockpitEditorBean.this.activeView).get(key).x == this.col;
+		}
+
 	}
 
 }
diff --git a/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml b/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml
index d8bb83b7..cddfd5c7 100644
--- a/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml
+++ b/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml
@@ -36,7 +36,7 @@
                     <p:remoteCommand autoRun="false" name="nodeSelected" action="#{currentCockpitEditorBean.nodeSelected()}" update=":propertiesForm" />
                 </h:form>
             </ui:define>
-            
+
             <ui:define name="js">
                 <!-- This javascript code will be executed in the onload-part of the body and shows a localized message via the growl-component. -->
                 <script>        
@@ -73,7 +73,9 @@
                                 nodeSelected([{name : 'id', value : event.currentTarget.id}]);
                             });
                         </script>
-                        <p:dashboard id="dynamicDashboard" binding="#{currentCockpitEditorBean.dashboard}"/>
+                        <p:dashboard id="dynamicDashboard" binding="#{currentCockpitEditorBean.dashboard}">
+                            <p:ajax event="reorder" listener="#{currentCockpitEditorBean.handleReorder}"/>
+                        </p:dashboard>
                     </ui:fragment>
                 </h:form>
             </ui:define>
@@ -143,13 +145,13 @@
             </ui:define>
 
             <ui:define name="furtherDialogIncludes">
-                 <p:confirmDialog id="confirmDialog" message="You have unsaved changed on your page. Do you really want to continue?" header="Unsaved Changes" severity="alert" widgetVar="closeConfirmation" >  
+                <p:confirmDialog id="confirmDialog" message="You have unsaved changed on your page. Do you really want to continue?" header="Unsaved Changes" severity="alert" widgetVar="closeConfirmation" >  
                     <h:form>
                         <p:commandButton id="confirm" value="#{localizedMessages.yes}" ajax="false" oncomplete="closeConfirmation.hide()" action="ProjectOverviewPage.xhtml?faces-redirect=true" />  
                         <p:commandButton id="decline" value="#{localizedMessages.cancel}" onclick="closeConfirmation.hide()" type="button" />   
                     </h:form>
                 </p:confirmDialog>  
-                
+
                 <!-- Include the dialogs for the views. -->
                 <ui:include src="../dialogs/CockpitEditorPageDialogs.xhtml" />
             </ui:define>
-- 
GitLab