Improve dashboard
[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.ui.di.Focus;
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 @Inject
68 private TrackerService trackerService;
69
70 private String datePattern = "dd MMM yyyy";
71
72 private Composite headerCmp;
73 private Composite taskListCmp;
74 private TaskVirtualListComposite tvlc;
75
76 @PostConstruct
77 public void createPartControl(Composite parent) {
78 super.createPartControl(parent);
79 parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
80 Composite bodyCmp = new Composite(parent, SWT.NO_FOCUS);
81 bodyCmp.setLayoutData(EclipseUiUtils.fillAll());
82 bodyCmp.setLayout(new GridLayout());
83
84 headerCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
85 headerCmp.setLayoutData(EclipseUiUtils.fillWidth());
86
87 taskListCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
88 taskListCmp.setLayoutData(EclipseUiUtils.fillAll());
89 forceRefresh(null);
90 }
91
92 @Override
93 public void forceRefresh(Object object) {
94 CmsUtils.clear(headerCmp);
95 populateHeaderPart(headerCmp, NodeUtils.getUserHome(getSession()));
96
97 CmsUtils.clear(taskListCmp);
98 populateTaskListCmp(taskListCmp);
99
100 headerCmp.getParent().layout(true, true);
101 }
102
103 private void populateTaskListCmp(Composite parent) {
104 parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
105 NodeIterator nit = activitiesService.getMyTasks(getSession(), true);
106 if (!nit.hasNext()) {
107 Composite noTaskCmp = new Composite(parent, SWT.NO_FOCUS);
108 noTaskCmp.setLayoutData(EclipseUiUtils.fillAll());
109 noTaskCmp.setLayout(new GridLayout());
110
111 // Label noTaskLbl = new Label(noTaskCmp, SWT.CENTER);
112 // noTaskLbl.setText("<i> <big> You have no pending Task. </big> </i>");
113 // CmsUtils.markup(noTaskLbl);
114 // noTaskLbl.setLayoutData(new GridData(SWT.CENTER, SWT.BOTTOM, true, true));
115
116 } else {
117 TaskListLabelProvider labelProvider = new TaskListLabelProvider(trackerService);
118 tvlc = new TaskVirtualListComposite(parent, SWT.NO_FOCUS, (ILabelProvider) labelProvider, 54);
119 tvlc.setLayoutData(EclipseUiUtils.fillAll());
120 final TableViewer viewer = tvlc.getTableViewer();
121 viewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray());
122 final TaskViewerContextMenu contextMenu = new TaskViewerContextMenu(viewer, getSession(),
123 activitiesService) {
124 @Override
125 public boolean performAction(String actionId) {
126 boolean hasChanged = super.performAction(actionId);
127 if (hasChanged) {
128 viewer.getTable().setFocus();
129 forceRefresh(null);
130 // NodeIterator nit =
131 // activitiesService.getMyTasks(getSession(), true);
132 // viewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray());
133 }
134 return hasChanged;
135 }
136 };
137 viewer.getTable().addMouseListener(new MouseAdapter() {
138 private static final long serialVersionUID = 6737579410648595940L;
139
140 @Override
141 public void mouseDown(MouseEvent e) {
142 if (e.button == 3) {
143 // contextMenu.setCurrFolderPath(currDisplayedFolder);
144 contextMenu.show(viewer.getTable(), new Point(e.x, e.y),
145 (IStructuredSelection) viewer.getSelection());
146 }
147 }
148 });
149 viewer.addDoubleClickListener(new IDoubleClickListener() {
150
151 @Override
152 public void doubleClick(DoubleClickEvent event) {
153 IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();
154 Node task = (Node) sel.getFirstElement();
155 getSystemWorkbenchService().openEntityEditor(task);
156 }
157 });
158 }
159 }
160
161 private boolean isOverdue(Node node, String propName) {
162 try {
163 Calendar now = GregorianCalendar.getInstance();
164 return node.hasProperty(propName) && node.getProperty(propName).getDate().before(now);
165 } catch (RepositoryException e) {
166 throw new ConnectException("Cannot check overdue status with property " + propName + " on " + node, e);
167 }
168 }
169
170 private void populateHeaderPart(Composite bodyCmp, Node context) {
171 bodyCmp.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, true)));
172
173 Composite leftCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
174 leftCmp.setLayout(new GridLayout());
175 leftCmp.setLayoutData(EclipseUiUtils.fillWidth());
176 Composite rightCmp = new Composite(bodyCmp, SWT.NO_FOCUS);
177 rightCmp.setLayout(new GridLayout());
178 rightCmp.setLayoutData(EclipseUiUtils.fillWidth());
179
180 // Title
181 Label titleLbl = new Label(leftCmp, SWT.WRAP | SWT.LEAD);
182 CmsUtils.markup(titleLbl);
183 String titleStr = "<big><b>" + CurrentUser.getDisplayName() + "</b></big>";
184 titleLbl.setText(titleStr);
185 GridData gd = new GridData(SWT.BEGINNING, SWT.TOP, false, false);
186 // gd.verticalIndent = 5;
187 // gd.horizontalIndent = 10;
188 titleLbl.setLayoutData(gd);
189
190 final Link createTaskLk = new Link(leftCmp, SWT.CENTER);
191 createTaskLk.setText("<a>Create a task</a>");
192 gd = new GridData(SWT.BEGINNING, SWT.TOP, false, false);
193 // gd.verticalIndent = 5;
194 gd.horizontalIndent = 10;
195 createTaskLk.setLayoutData(gd);
196
197 createTaskLk.addSelectionListener(new SelectionAdapter() {
198 private static final long serialVersionUID = -9028457805156989935L;
199
200 @Override
201 public void widgetSelected(SelectionEvent e) {
202 // String mainMixin = TrackerTypes.TRACKER_TASK;
203 String mainMixin = ActivitiesTypes.ACTIVITIES_TASK;
204 String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(createTaskLk.getShell(),
205 getSession(), getSystemAppService(), getSystemWorkbenchService(), mainMixin);
206 if (EclipseUiUtils.notEmpty(pathCreated))
207 forceRefresh(null);
208 }
209 });
210
211 NodeIterator nit = activitiesService.getMyTasks(getSession(), true);
212 if (nit.hasNext()) {
213 List<Node> overdueTasks = new ArrayList<>();
214 while (nit.hasNext()) {
215 Node currNode = nit.nextNode();
216 if (isOverdue(currNode, ActivitiesNames.ACTIVITIES_DUE_DATE))
217 overdueTasks.add(currNode);
218 }
219 if (!overdueTasks.isEmpty()) {
220 Composite overdueCmp = new Composite(leftCmp, SWT.NO_FOCUS);
221 long size = overdueTasks.size();
222 String overdueStr = "You have " + size + " overdue task" + (size > 1 ? "s" : "") + ": ";
223 populateMuliValueClickableList(overdueCmp, overdueTasks.toArray(new Node[0]), new TaskLp(), overdueStr);
224 }
225 }
226
227 nit = trackerService.getMyMilestones(getSession(), true);
228 List<Node> openMilestones = new ArrayList<>();
229
230 if (nit.hasNext()) {
231 List<Node> overdueMilestones = new ArrayList<>();
232 while (nit.hasNext()) {
233 Node currNode = nit.nextNode();
234 openMilestones.add(currNode);
235 if (isOverdue(currNode, TrackerNames.TRACKER_TARGET_DATE))
236 overdueMilestones.add(currNode);
237 }
238 if (!overdueMilestones.isEmpty()) {
239 Composite overdueCmp = new Composite(leftCmp, SWT.NO_FOCUS);
240 long size = overdueMilestones.size();
241 String overdueStr = "You have " + size + " overdue milestone" + (size > 1 ? "s" : "") + ": ";
242 populateMuliValueClickableList(overdueCmp, overdueMilestones.toArray(new Node[0]), new MilestoneLp(),
243 overdueStr);
244 }
245 }
246
247 // My projects
248 List<Node> openProjects = JcrUtils.nodeIteratorToList(trackerService.getMyProjects(getSession(), true));
249 if (!openProjects.isEmpty()) {
250 Group myProjectsGp = new Group(rightCmp, SWT.NO_FOCUS);
251 myProjectsGp.setText("My open projects");
252 myProjectsGp.setLayoutData(EclipseUiUtils.fillWidth());
253 populateMuliValueClickableList(myProjectsGp, openProjects.toArray(new Node[0]), new ProjectLp(), null);
254 }
255
256 // My Milestones
257 if (!openMilestones.isEmpty()) {
258 Group myMilestoneGp = new Group(rightCmp, SWT.NO_FOCUS);
259 myMilestoneGp.setText("My open milestones");
260 myMilestoneGp.setLayoutData(EclipseUiUtils.fillWidth());
261 populateMuliValueClickableList(myMilestoneGp, openMilestones.toArray(new Node[0]), new MilestoneLp(), null);
262 }
263 }
264
265 private class ProjectLp extends ColumnLabelProvider {
266 private static final long serialVersionUID = 7231233932794865555L;
267
268 @Override
269 public String getText(Object element) {
270 Node project = (Node) element;
271
272 String percent;
273 NodeIterator nit = TrackerUtils.getIssues(project, null, null, null, true);
274 long openNb = nit.getSize();
275
276 nit = TrackerUtils.getIssues(project, null, null, null, false);
277 long allNb = nit.getSize();
278
279 if (allNb < 1)
280 percent = "empty";
281 else {
282 double num = allNb - openNb;
283 double result = num / allNb * 100;
284 percent = String.format("%.1f", result) + "% done";
285 }
286 StringBuilder builder = new StringBuilder();
287 builder.append("<a>").append(ConnectJcrUtils.get(project, Property.JCR_TITLE)).append("</a>");
288 builder.append(" (").append(percent).append(")");
289
290 return builder.toString();
291 }
292 }
293
294 private class MilestoneLp extends ColumnLabelProvider {
295 private static final long serialVersionUID = 7231233932794865555L;
296
297 @Override
298 public String getText(Object element) {
299 Node milestone = (Node) element;
300 Node project = TrackerUtils.getRelatedProject(trackerService, milestone);
301 String dueDate = ConnectJcrUtils.getDateFormattedAsString(milestone, TrackerNames.TRACKER_TARGET_DATE,
302 datePattern);
303
304 String percent;
305 String propName = TrackerNames.TRACKER_MILESTONE_UID;
306 String muid = ConnectJcrUtils.get(milestone, ConnectNames.CONNECT_UID);
307 NodeIterator nit = TrackerUtils.getIssues(project, null, propName, muid, true);
308 long openNb = nit.getSize();
309
310 nit = TrackerUtils.getIssues(project, null, propName, muid, false);
311 long allNb = nit.getSize();
312
313 if (allNb < 1)
314 percent = "empty";
315 else {
316 double num = allNb - openNb;
317 double result = num / allNb * 100;
318 percent = String.format("%.1f", result) + "% done";
319 }
320 StringBuilder builder = new StringBuilder();
321 builder.append("<a>").append(ConnectJcrUtils.get(milestone, Property.JCR_TITLE)).append("</a>");
322 builder.append(" (");
323 if (EclipseUiUtils.notEmpty(dueDate))
324 builder.append("due to ").append(dueDate).append(", ");
325
326 builder.append(percent).append(")");
327 return builder.toString();
328 }
329
330 @Override
331 public Color getForeground(Object element) {
332 Node milestone = (Node) element;
333 Calendar dueDate = ConnectJcrUtils.getDateValue(milestone, TrackerNames.TRACKER_TARGET_DATE);
334 if (dueDate != null && dueDate.before(Calendar.getInstance()))
335 return Display.getCurrent().getSystemColor(SWT.COLOR_RED);
336 return null;
337 }
338 }
339
340 private class TaskLp extends ColumnLabelProvider {
341 private static final long serialVersionUID = 7231233932794865555L;
342
343 @Override
344 public String getText(Object element) {
345 Node task = (Node) element;
346 String dueDate = ConnectJcrUtils.getDateFormattedAsString(task, ActivitiesNames.ACTIVITIES_DUE_DATE,
347 datePattern);
348
349 StringBuilder builder = new StringBuilder();
350 builder.append("<a>").append(ConnectJcrUtils.get(task, Property.JCR_TITLE)).append("</a>");
351 if (EclipseUiUtils.notEmpty(dueDate))
352 builder.append(" (").append("due to ").append(dueDate).append(")");
353 return builder.toString();
354 }
355
356 @Override
357 public Color getForeground(Object element) {
358 Node milestone = (Node) element;
359 Calendar dueDate = ConnectJcrUtils.getDateValue(milestone, TrackerNames.TRACKER_TARGET_DATE);
360 if (dueDate != null && dueDate.before(Calendar.getInstance()))
361 return Display.getCurrent().getSystemColor(SWT.COLOR_RED);
362 return null;
363 }
364 }
365
366 @Focus
367 public void setFocus() {
368 // refreshDocListGadget();
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 }