View Javadoc
1   package org.argeo.suite.e4.parts;
2   
3   import java.util.ArrayList;
4   import java.util.Calendar;
5   import java.util.GregorianCalendar;
6   import java.util.List;
7   
8   import javax.annotation.PostConstruct;
9   import javax.inject.Inject;
10  import javax.jcr.Node;
11  import javax.jcr.NodeIterator;
12  import javax.jcr.Property;
13  import javax.jcr.RepositoryException;
14  
15  import org.apache.commons.logging.Log;
16  import org.apache.commons.logging.LogFactory;
17  import org.argeo.activities.ActivitiesNames;
18  import org.argeo.activities.ActivitiesService;
19  import org.argeo.activities.ActivitiesTypes;
20  import org.argeo.activities.ui.TaskViewerContextMenu;
21  import org.argeo.cms.auth.CurrentUser;
22  import org.argeo.cms.util.CmsUtils;
23  import org.argeo.connect.ConnectException;
24  import org.argeo.connect.ConnectNames;
25  import org.argeo.connect.ui.ConnectWorkbenchUtils;
26  import org.argeo.connect.ui.Refreshable;
27  import org.argeo.connect.util.ConnectJcrUtils;
28  import org.argeo.eclipse.ui.EclipseUiUtils;
29  import org.argeo.jcr.JcrUtils;
30  import org.argeo.node.NodeUtils;
31  import org.argeo.suite.e4.SuiteMsg;
32  import org.argeo.tracker.TrackerNames;
33  import org.argeo.tracker.TrackerService;
34  import org.argeo.tracker.core.TrackerUtils;
35  import org.argeo.tracker.ui.TaskListLabelProvider;
36  import org.argeo.tracker.ui.TaskVirtualListComposite;
37  import org.eclipse.e4.core.di.annotations.Optional;
38  import org.eclipse.jface.viewers.ColumnLabelProvider;
39  import org.eclipse.jface.viewers.DoubleClickEvent;
40  import org.eclipse.jface.viewers.IDoubleClickListener;
41  import org.eclipse.jface.viewers.ILabelProvider;
42  import org.eclipse.jface.viewers.IStructuredSelection;
43  import org.eclipse.jface.viewers.TableViewer;
44  import org.eclipse.swt.SWT;
45  import org.eclipse.swt.events.MouseAdapter;
46  import org.eclipse.swt.events.MouseEvent;
47  import org.eclipse.swt.events.SelectionAdapter;
48  import org.eclipse.swt.events.SelectionEvent;
49  import org.eclipse.swt.graphics.Color;
50  import org.eclipse.swt.graphics.Point;
51  import org.eclipse.swt.layout.GridData;
52  import org.eclipse.swt.layout.GridLayout;
53  import org.eclipse.swt.layout.RowLayout;
54  import org.eclipse.swt.widgets.Composite;
55  import org.eclipse.swt.widgets.Display;
56  import org.eclipse.swt.widgets.Group;
57  import org.eclipse.swt.widgets.Label;
58  import org.eclipse.swt.widgets.Link;
59  
60  /** Argeo Suite Default Dashboard */
61  public class DefaultDashboardEditor extends AbstractSuiteDashboard implements Refreshable {
62  	final static Log log = LogFactory.getLog(DefaultDashboardEditor.class);
63  	// public final static String ID = AsUiPlugin.PLUGIN_ID +
64  	// ".defaultDashboardEditor";
65  
66  	@Inject
67  	private ActivitiesService activitiesService;
68  
69  	@Inject
70  	@Optional
71  	private TrackerService trackerService;
72  
73  	private String datePattern = "dd MMM yyyy";
74  
75  	private Composite headerCmp;
76  	private Composite taskListCmp;
77  	private TaskVirtualListComposite tvlc;
78  
79  	@PostConstruct
80  	public void createPartControl(Composite parent) {
81  		super.createPartControl(parent);
82  		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
83  		Composite bodyCmp = new Composite(parent, SWT.NO_FOCUS);
84  		bodyCmp.setLayoutData(EclipseUiUtils.fillAll());
85  		bodyCmp.setLayout(new GridLayout());
86  
87  		headerCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
88  		headerCmp.setLayoutData(EclipseUiUtils.fillWidth());
89  
90  		taskListCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
91  		taskListCmp.setLayoutData(EclipseUiUtils.fillAll());
92  		forceRefresh(null);
93  	}
94  
95  	@Override
96  	public void forceRefresh(Object object) {
97  		CmsUtils.clear(headerCmp);
98  		populateHeaderPart(headerCmp, NodeUtils.getUserHome(getSession()));
99  
100 		CmsUtils.clear(taskListCmp);
101 		populateTaskListCmp(taskListCmp);
102 
103 		headerCmp.getParent().layout(true, true);
104 	}
105 
106 	private void populateTaskListCmp(Composite parent) {
107 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
108 		NodeIterator nit = activitiesService.getMyTasks(getSession(), true);
109 		if (!nit.hasNext()) {
110 			Composite noTaskCmp = new Composite(parent, SWT.NO_FOCUS);
111 			noTaskCmp.setLayoutData(EclipseUiUtils.fillAll());
112 			noTaskCmp.setLayout(new GridLayout());
113 
114 			// Label noTaskLbl = new Label(noTaskCmp, SWT.CENTER);
115 			// noTaskLbl.setText("<i> <big> You have no pending Task. </big> </i>");
116 			// CmsUtils.markup(noTaskLbl);
117 			// noTaskLbl.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, true));
118 
119 		} else {
120 			TaskListLabelProvider labelProvider = new TaskListLabelProvider(getSystemAppService());
121 			tvlc = new TaskVirtualListComposite(parent, SWT.NO_FOCUS, (ILabelProvider) labelProvider, 54);
122 			tvlc.setLayoutData(EclipseUiUtils.fillAll());
123 			final TableViewer viewer = tvlc.getTableViewer();
124 			viewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray());
125 			final TaskViewerContextMenu contextMenu = new TaskViewerContextMenu(viewer, getSession(),
126 					activitiesService) {
127 				@Override
128 				public boolean performAction(String actionId) {
129 					boolean hasChanged = super.performAction(actionId);
130 					if (hasChanged) {
131 						viewer.getTable().setFocus();
132 						forceRefresh(null);
133 						// NodeIterator nit =
134 						// activitiesService.getMyTasks(getSession(), true);
135 						// viewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray());
136 					}
137 					return hasChanged;
138 				}
139 			};
140 			viewer.getTable().addMouseListener(new MouseAdapter() {
141 				private static final long serialVersionUID = 6737579410648595940L;
142 
143 				@Override
144 				public void mouseDown(MouseEvent e) {
145 					if (e.button == 3) {
146 						// contextMenu.setCurrFolderPath(currDisplayedFolder);
147 						contextMenu.show(viewer.getTable(), new Point(e.x, e.y),
148 								(IStructuredSelection) viewer.getSelection());
149 					}
150 				}
151 			});
152 			viewer.addDoubleClickListener(new IDoubleClickListener() {
153 
154 				@Override
155 				public void doubleClick(DoubleClickEvent event) {
156 					IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();
157 					Node task = (Node) sel.getFirstElement();
158 					getSystemWorkbenchService().openEntityEditor(task);
159 				}
160 			});
161 		}
162 	}
163 
164 	private boolean isOverdue(Node node, String propName) {
165 		try {
166 			Calendar now = GregorianCalendar.getInstance();
167 			return node.hasProperty(propName) && node.getProperty(propName).getDate().before(now);
168 		} catch (RepositoryException e) {
169 			throw new ConnectException("Cannot check overdue status with property " + propName + " on " + node, e);
170 		}
171 	}
172 
173 	private void populateHeaderPart(Composite bodyCmp, Node context) {
174 		bodyCmp.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, true)));
175 
176 		Composite leftCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
177 		leftCmp.setLayout(new GridLayout());
178 		leftCmp.setLayoutData(EclipseUiUtils.fillWidth());
179 		Composite rightCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
180 		rightCmp.setLayout(new GridLayout());
181 		rightCmp.setLayoutData(EclipseUiUtils.fillWidth());
182 
183 		// Title
184 		Label titleLbl = new Label(leftCmp, SWT.WRAP | SWT.LEAD);
185 		CmsUtils.markup(titleLbl);
186 		String titleStr = "<big><b>" + CurrentUser.getDisplayName() + "</b></big>";
187 		titleLbl.setText(titleStr);
188 		GridData gd = new GridData(SWT.BEGINNING, SWT.TOP, false, false);
189 		// gd.verticalIndent = 5;
190 		// gd.horizontalIndent = 10;
191 		titleLbl.setLayoutData(gd);
192 
193 		final Link createTaskLk = new Link(leftCmp, SWT.CENTER);
194 		// createTaskLk.setText("<a>Create a task</a>");
195 		createTaskLk.setText("<a>" + SuiteMsg.newTodo.lead() + "</a>");
196 		gd = new GridData(SWT.BEGINNING, SWT.TOP, false, false);
197 		// gd.verticalIndent = 5;
198 		gd.horizontalIndent = 10;
199 		createTaskLk.setLayoutData(gd);
200 
201 		createTaskLk.addSelectionListener(new SelectionAdapter() {
202 			private static final long serialVersionUID = -9028457805156989935L;
203 
204 			@Override
205 			public void widgetSelected(SelectionEvent e) {
206 				// String mainMixin = TrackerTypes.TRACKER_TASK;
207 				String mainMixin = ActivitiesTypes.ACTIVITIES_TASK;
208 				String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(createTaskLk.getShell(),
209 						getSession(), getSystemAppService(), getSystemWorkbenchService(), mainMixin);
210 				if (EclipseUiUtils.notEmpty(pathCreated))
211 					forceRefresh(null);
212 			}
213 		});
214 
215 		NodeIterator nit = activitiesService.getMyTasks(getSession(), true);
216 		if (nit.hasNext()) {
217 			List<Node> overdueTasks = new ArrayList<>();
218 			while (nit.hasNext()) {
219 				Node currNode = nit.nextNode();
220 				if (isOverdue(currNode, ActivitiesNames.ACTIVITIES_DUE_DATE))
221 					overdueTasks.add(currNode);
222 			}
223 			if (!overdueTasks.isEmpty()) {
224 				Composite overdueCmp = new Composite(leftCmp, SWT.NO_FOCUS);
225 				long size = overdueTasks.size();
226 				String overdueStr = "You have " + size + " overdue task" + (size > 1 ? "s" : "") + ": ";
227 				populateMuliValueClickableList(overdueCmp, overdueTasks.toArray(new Node[0]), new TaskLp(), overdueStr);
228 			}
229 		}
230 
231 		if (trackerService != null) {
232 			nit = trackerService.getMyMilestones(getSession(), true);
233 			List<Node> openMilestones = new ArrayList<>();
234 
235 			if (nit.hasNext()) {
236 				List<Node> overdueMilestones = new ArrayList<>();
237 				while (nit.hasNext()) {
238 					Node currNode = nit.nextNode();
239 					openMilestones.add(currNode);
240 					if (isOverdue(currNode, TrackerNames.TRACKER_TARGET_DATE))
241 						overdueMilestones.add(currNode);
242 				}
243 				if (!overdueMilestones.isEmpty()) {
244 					Composite overdueCmp = new Composite(leftCmp, SWT.NO_FOCUS);
245 					long size = overdueMilestones.size();
246 					String overdueStr = "You have " + size + " overdue milestone" + (size > 1 ? "s" : "") + ": ";
247 					populateMuliValueClickableList(overdueCmp, overdueMilestones.toArray(new Node[0]),
248 							new MilestoneLp(), overdueStr);
249 				}
250 			}
251 
252 			// My projects
253 			List<Node> openProjects = JcrUtils.nodeIteratorToList(trackerService.getMyProjects(getSession(), true));
254 			if (!openProjects.isEmpty()) {
255 				Group myProjectsGp = new Group(rightCmp, SWT.NO_FOCUS);
256 				myProjectsGp.setText("My open projects");
257 				myProjectsGp.setLayoutData(EclipseUiUtils.fillWidth());
258 				populateMuliValueClickableList(myProjectsGp, openProjects.toArray(new Node[0]), new ProjectLp(), null);
259 			}
260 
261 			// My Milestones
262 			if (!openMilestones.isEmpty()) {
263 				Group myMilestoneGp = new Group(rightCmp, SWT.NO_FOCUS);
264 				myMilestoneGp.setText("My open milestones");
265 				myMilestoneGp.setLayoutData(EclipseUiUtils.fillWidth());
266 				populateMuliValueClickableList(myMilestoneGp, openMilestones.toArray(new Node[0]), new MilestoneLp(),
267 						null);
268 			}
269 		}
270 	}
271 
272 	private class ProjectLp extends ColumnLabelProvider {
273 		private static final long serialVersionUID = 7231233932794865555L;
274 
275 		@Override
276 		public String getText(Object element) {
277 			Node project = (Node) element;
278 
279 			String percent;
280 			NodeIterator nit = TrackerUtils.getIssues(project, null, null, null, true);
281 			long openNb = nit.getSize();
282 
283 			nit = TrackerUtils.getIssues(project, null, null, null, false);
284 			long allNb = nit.getSize();
285 
286 			if (allNb < 1)
287 				percent = "empty";
288 			else {
289 				double num = allNb - openNb;
290 				double result = num / allNb * 100;
291 				percent = String.format("%.1f", result) + "% done";
292 			}
293 			StringBuilder builder = new StringBuilder();
294 			builder.append("<a>").append(ConnectJcrUtils.get(project, Property.JCR_TITLE)).append("</a>");
295 			builder.append(" (").append(percent).append(")");
296 
297 			return builder.toString();
298 		}
299 	}
300 
301 	private class MilestoneLp extends ColumnLabelProvider {
302 		private static final long serialVersionUID = 7231233932794865555L;
303 
304 		@Override
305 		public String getText(Object element) {
306 			Node milestone = (Node) element;
307 			Node project = TrackerUtils.getRelatedProject(trackerService, milestone);
308 			String dueDate = ConnectJcrUtils.getDateFormattedAsString(milestone, TrackerNames.TRACKER_TARGET_DATE,
309 					datePattern);
310 
311 			String percent;
312 			String propName = TrackerNames.TRACKER_MILESTONE_UID;
313 			String muid = ConnectJcrUtils.get(milestone, ConnectNames.CONNECT_UID);
314 			NodeIterator nit = TrackerUtils.getIssues(project, null, propName, muid, true);
315 			long openNb = nit.getSize();
316 
317 			nit = TrackerUtils.getIssues(project, null, propName, muid, false);
318 			long allNb = nit.getSize();
319 
320 			if (allNb < 1)
321 				percent = "empty";
322 			else {
323 				double num = allNb - openNb;
324 				double result = num / allNb * 100;
325 				percent = String.format("%.1f", result) + "% done";
326 			}
327 			StringBuilder builder = new StringBuilder();
328 			builder.append("<a>").append(ConnectJcrUtils.get(milestone, Property.JCR_TITLE)).append("</a>");
329 			builder.append(" (");
330 			if (EclipseUiUtils.notEmpty(dueDate))
331 				builder.append("due to ").append(dueDate).append(", ");
332 
333 			builder.append(percent).append(")");
334 			return builder.toString();
335 		}
336 
337 		@Override
338 		public Color getForeground(Object element) {
339 			Node milestone = (Node) element;
340 			Calendar dueDate = ConnectJcrUtils.getDateValue(milestone, TrackerNames.TRACKER_TARGET_DATE);
341 			if (dueDate != null && dueDate.before(Calendar.getInstance()))
342 				return Display.getCurrent().getSystemColor(SWT.COLOR_RED);
343 			return null;
344 		}
345 	}
346 
347 	private class TaskLp extends ColumnLabelProvider {
348 		private static final long serialVersionUID = 7231233932794865555L;
349 
350 		@Override
351 		public String getText(Object element) {
352 			Node task = (Node) element;
353 			String dueDate = ConnectJcrUtils.getDateFormattedAsString(task, ActivitiesNames.ACTIVITIES_DUE_DATE,
354 					datePattern);
355 
356 			StringBuilder builder = new StringBuilder();
357 			builder.append("<a>").append(ConnectJcrUtils.get(task, Property.JCR_TITLE)).append("</a>");
358 			if (EclipseUiUtils.notEmpty(dueDate))
359 				builder.append(" (").append("due to ").append(dueDate).append(")");
360 			return builder.toString();
361 		}
362 
363 		@Override
364 		public Color getForeground(Object element) {
365 			Node milestone = (Node) element;
366 			Calendar dueDate = ConnectJcrUtils.getDateValue(milestone, TrackerNames.TRACKER_TARGET_DATE);
367 			if (dueDate != null && dueDate.before(Calendar.getInstance()))
368 				return Display.getCurrent().getSystemColor(SWT.COLOR_RED);
369 			return null;
370 		}
371 	}
372 
373 	// public void setActivitiesService(ActivitiesService activitiesService) {
374 	// this.activitiesService = activitiesService;
375 	// }
376 	//
377 	// public void setTrackerService(TrackerService trackerService) {
378 	// this.trackerService = trackerService;
379 	// }
380 
381 	// LOCAL HELPERS
382 	private void populateMuliValueClickableList(Composite parent, Node[] nodes, ColumnLabelProvider lp,
383 			String listLabel) {
384 		CmsUtils.clear(parent);
385 		RowLayout rl = new RowLayout(SWT.HORIZONTAL | SWT.WRAP);
386 		rl.wrap = true;
387 		rl.marginLeft = rl.marginTop = rl.marginBottom = 0;
388 		rl.marginRight = 8;
389 		parent.setLayout(rl);
390 
391 		if (EclipseUiUtils.notEmpty(listLabel)) {
392 			Link link = new Link(parent, SWT.NONE);
393 			link.setText(listLabel);
394 			link.setFont(EclipseUiUtils.getBoldFont(parent));
395 		}
396 
397 		int i = 1;
398 		for (Node node : nodes) {
399 			Link link = new Link(parent, SWT.NONE);
400 			CmsUtils.markup(link);
401 			link.setText(lp.getText(node) + (i != nodes.length ? ", " : ""));
402 			i++;
403 			// Color fc = lp.getForeground(node);
404 			// if (fc != null)
405 			// link.setForeground(fc);
406 
407 			link.addSelectionListener(new SelectionAdapter() {
408 				private static final long serialVersionUID = 1L;
409 
410 				@Override
411 				public void widgetSelected(final SelectionEvent event) {
412 					// CommandUtils.callCommand(getSystemWorkbenchService().getOpenEntityEditorCmdId(),
413 					// ConnectEditor.PARAM_JCR_ID, ConnectJcrUtils.getIdentifier(node));
414 					getSystemWorkbenchService().openEntityEditor(node);
415 				}
416 			});
417 		}
418 	}
419 
420 }