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