1 package org.argeo.tracker.e4.parts;
2
3 import java.nio.file.Path;
4 import java.nio.file.spi.FileSystemProvider;
5 import java.util.ArrayList;
6 import java.util.List;
7 import java.util.Map;
8
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 import javax.jcr.Session;
15 import javax.jcr.nodetype.NodeType;
16 import javax.jcr.security.AccessControlException;
17
18 import org.argeo.activities.ActivitiesNames;
19 import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
20 import org.argeo.cms.ui.eclipse.forms.FormToolkit;
21 import org.argeo.cms.ui.eclipse.forms.IManagedForm;
22 import org.argeo.cms.util.CmsUtils;
23 import org.argeo.connect.AppService;
24 import org.argeo.connect.ConnectNames;
25 import org.argeo.connect.ui.ConnectImages;
26 import org.argeo.connect.ui.ConnectWorkbenchUtils;
27 import org.argeo.connect.util.ConnectJcrUtils;
28 import org.argeo.documents.composites.DocumentsFolderComposite;
29 import org.argeo.eclipse.ui.ColumnDefinition;
30 import org.argeo.eclipse.ui.EclipseUiUtils;
31 import org.argeo.eclipse.ui.jcr.lists.SimpleJcrNodeLabelProvider;
32 import org.argeo.jcr.JcrUtils;
33 import org.argeo.tracker.TrackerException;
34 import org.argeo.tracker.TrackerNames;
35 import org.argeo.tracker.TrackerTypes;
36 import org.argeo.tracker.core.TrackerUtils;
37 import org.argeo.tracker.ui.MilestoneListComposite;
38 import org.argeo.tracker.ui.TrackerLps;
39 import org.argeo.tracker.ui.controls.RepartitionChart;
40 import org.argeo.tracker.ui.dialogs.ConfigureProjectWizard;
41 import org.eclipse.jface.action.Action;
42 import org.eclipse.jface.action.ToolBarManager;
43 import org.eclipse.jface.resource.ImageDescriptor;
44 import org.eclipse.jface.viewers.ColumnLabelProvider;
45 import org.eclipse.jface.viewers.DoubleClickEvent;
46 import org.eclipse.jface.viewers.IDoubleClickListener;
47 import org.eclipse.jface.viewers.IStructuredSelection;
48 import org.eclipse.jface.viewers.TableViewer;
49 import org.eclipse.jface.window.Window;
50 import org.eclipse.jface.wizard.WizardDialog;
51 import org.eclipse.swt.SWT;
52 import org.eclipse.swt.custom.ScrolledComposite;
53 import org.eclipse.swt.events.ModifyEvent;
54 import org.eclipse.swt.events.ModifyListener;
55 import org.eclipse.swt.events.SelectionAdapter;
56 import org.eclipse.swt.events.SelectionEvent;
57 import org.eclipse.swt.layout.GridData;
58 import org.eclipse.swt.layout.GridLayout;
59 import org.eclipse.swt.widgets.Button;
60 import org.eclipse.swt.widgets.Composite;
61 import org.eclipse.swt.widgets.Display;
62 import org.eclipse.swt.widgets.Label;
63 import org.eclipse.swt.widgets.Link;
64 import org.eclipse.swt.widgets.Shell;
65 import org.eclipse.swt.widgets.Text;
66
67
68 public class ProjectEditor extends AbstractTrackerEditor {
69
70
71
72 @Inject
73 private FileSystemProvider fileSystemProvider;
74
75
76 private Node project;
77
78
79 private final static int CHART_DATA_LIMIT = 8;
80 private final static int CHART_WIDTH = 300;
81 private final static int CHART_HEIGHT = 200;
82
83
84
85
86
87
88
89 @Override
90 protected void addPages() {
91 project = getNode();
92 try {
93 try {
94
95 Session session = project.getSession();
96 session.checkPermission(project.getPath(), Session.ACTION_ADD_NODE);
97 Node documents = JcrUtils.mkdirs(project, TrackerNames.TRACKER_DATA, NodeType.NT_FOLDER);
98 if (session.hasPendingChanges()) {
99 documents.addMixin(NodeType.MIX_TITLE);
100 documents.setProperty(Property.JCR_TITLE, "Documents");
101 session.save();
102 }
103 } catch (AccessControlException e) {
104
105 } catch (RepositoryException e) {
106 throw new TrackerException("Cannot initialise documents for project " + project, e);
107 }
108
109 addPage(new MainPage(this));
110 try {
111 if (project.hasNode(TrackerNames.TRACKER_DATA))
112 addPage(new DocumentsPage(this));
113 } catch (RepositoryException e) {
114 throw new TrackerException("Cannot initialise document page for project " + project, e);
115 }
116
117 addPage(new TasksPage(this));
118 addPage(new MilestoneListPage(this, ".milestoneList", project, getUserAdminService(), getTrackerService(),
119 getAppWorkbenchService()));
120
121
122
123 } catch (Exception e) {
124 throw new TrackerException("Cannot add pages for editor of " + getNode(), e);
125 }
126 }
127
128
129 private class MainPage extends AbstractEditorPage {
130 public final static String PAGE_ID = ".mainPage";
131
132 private Link managerLk;
133 private Link overdueTasksLk;
134 private Composite chartCmp;
135 private Label descLbl;
136
137 public MainPage(AbstractTrackerEditor editor) {
138 super(editor, PAGE_ID, "Overview");
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 @Override
155 protected void createFormContent(Composite body) {
156 appendOverviewPart(body);
157 appendOpenMilestonePart(body);
158 }
159
160
161 private void appendOverviewPart(Composite parent) {
162 FormToolkit tk = getFormToolkit();
163 GridData twd;
164
165
166
167
168
169
170 Composite body = new Composite(parent, SWT.BORDER);
171 GridLayout layout = new GridLayout(2, false);
172
173
174 body.setLayout(layout);
175 body.setLayoutData(CmsUtils.fillWidth());
176
177
178
179
180
181 createFormBoldLabel(tk, body, "Manager");
182 managerLk = new Link(body, SWT.NONE);
183
184 managerLk.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
185
186
187
188
189 createFormBoldLabel(tk, body, "Overdue Tasks");
190 overdueTasksLk = new Link(body, SWT.NONE);
191 overdueTasksLk.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
192
193
194 chartCmp = new Composite(body, SWT.NO_FOCUS);
195 chartCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
196 twd = new GridData(SWT.FILL, SWT.FILL, false, false);
197
198
199 chartCmp.setLayoutData(twd);
200
201
202
203
204 TrackerUiUtils.createFormBoldLabel(tk, body, "Details");
205
206 descLbl = new Label(body, SWT.WRAP);
207 twd = new GridData(SWT.FILL, SWT.FILL, false, false);
208
209 descLbl.setLayoutData(twd);
210
211 SectionPart part = new SectionPart(body.getParent()) {
212
213 @Override
214 public void refresh() {
215 String managerId = ConnectJcrUtils.get(project, TrackerNames.TRACKER_MANAGER);
216 if (EclipseUiUtils.notEmpty(managerId))
217 managerLk.setText(getUserAdminService().getUserDisplayName(managerId));
218 else
219 managerLk.setText("");
220
221 String desc = ConnectJcrUtils.get(project, Property.JCR_DESCRIPTION);
222 descLbl.setText(desc);
223
224
225 CmsUtils.clear(chartCmp);
226 GridData twd = (GridData) chartCmp.getLayoutData();
227 Map<String, String> ot = TrackerUtils.getOpenTasksByAssignee(getUserAdminService(), project, null,
228 CHART_DATA_LIMIT);
229 if (ot == null || ot.isEmpty()) {
230 Label lbl = new Label(chartCmp, SWT.CENTER);
231 lbl.setFont(EclipseUiUtils.getItalicFont(body));
232 lbl.setText("No open task has been found for this project.");
233 twd.heightHint = SWT.DEFAULT;
234 } else {
235 RepartitionChart coc = new RepartitionChart(chartCmp, SWT.NO_FOCUS);
236 twd.heightHint = CHART_HEIGHT;
237 coc.setLayoutData(EclipseUiUtils.fillAll());
238 coc.setInput("Open tasks by assignee", ot, CHART_WIDTH, CHART_HEIGHT);
239 }
240
241
242 Long nb = TrackerUtils.getProjectOverdueTasksNumber(project);
243 overdueTasksLk.setText(nb < 0 ? "-" : nb.toString());
244
245 parent.layout(true, true);
246 body.setFocus();
247 super.refresh();
248 }
249 };
250 part.initialize(getManagedForm());
251 getManagedForm().addPart(part);
252
253 }
254
255 private Section appendOpenMilestonePart(Composite parent) {
256 FormToolkit tk = getFormToolkit();
257 Section section = TrackerUiUtils.addFormSection(tk, parent, "Open Milestones");
258 section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
259
260 Composite body = (Composite) section.getClient();
261 body.setLayout(new GridLayout());
262 final MilestoneListComposite msBoxCmp = new MilestoneListComposite(body, SWT.NO_FOCUS,
263 getAppWorkbenchService(), project);
264 msBoxCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
265
266 SectionPart part = new SectionPart(section) {
267
268 @Override
269 public void refresh() {
270 NodeIterator nit = TrackerUtils.getOpenMilestones(project, null);
271 msBoxCmp.setInput(JcrUtils.nodeIteratorToList(nit).toArray(new Node[0]));
272 super.refresh();
273 }
274 };
275 part.initialize(getManagedForm());
276 getManagedForm().addPart(part);
277 return section;
278 }
279
280
281 private void addMainSectionMenu(SectionPart sectionPart) {
282 ToolBarManager toolBarManager = TrackerUiUtils.addMenu(sectionPart.getSection());
283
284 String tooltip = "Edit the project main information";
285 Action action = new OpenConfigureDialog(tooltip, ConnectImages.IMG_DESC_EDIT, sectionPart);
286 toolBarManager.add(action);
287
288 tooltip = "Add a milestone to this project";
289 action = new AddMilestone(sectionPart.getSection().getShell(), tooltip, ConnectImages.IMG_DESC_ADD);
290 toolBarManager.add(action);
291
292 toolBarManager.update(true);
293 }
294
295
296 private class OpenConfigureDialog extends Action {
297 private static final long serialVersionUID = -6798429720348536525L;
298 private final SectionPart sectionPart;
299
300 private OpenConfigureDialog(String name, ImageDescriptor img, SectionPart sectionPart) {
301 super(name, img);
302 this.sectionPart = sectionPart;
303 }
304
305 @Override
306 public void run() {
307 Shell currShell = Display.getCurrent().getActiveShell();
308 ConfigureProjectWizard wizard = new ConfigureProjectWizard(getUserAdminService(), getTrackerService(),
309 project);
310 WizardDialog dialog = new WizardDialog(currShell, wizard);
311 try {
312 if (dialog.open() == Window.OK && project.getSession().hasPendingChanges()) {
313 updatePartName();
314 sectionPart.getSection().setText(ConnectJcrUtils.get(project, Property.JCR_TITLE));
315 sectionPart.refresh();
316 sectionPart.markDirty();
317 sectionPart.getSection().setFocus();
318 }
319 } catch (RepositoryException e) {
320 throw new TrackerException("Cannot check session state on " + project, e);
321 }
322 }
323 }
324
325 private class AddMilestone extends Action {
326 private static final long serialVersionUID = 5112793747049604434L;
327 final Shell shell;
328
329 private AddMilestone(Shell shell, String name, ImageDescriptor img) {
330 super(name, img);
331 this.shell = shell;
332 }
333
334 @Override
335 public void run() {
336 String mainMixin = TrackerTypes.TRACKER_MILESTONE;
337 Session referenceSession = ConnectJcrUtils.getSession(project);
338 AppService appService = getAppService();
339 String propName1 = TrackerNames.TRACKER_PROJECT_UID;
340 String value1 = ConnectJcrUtils.get(project, ConnectNames.CONNECT_UID);
341 String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(shell, referenceSession, appService,
342 getAppWorkbenchService(), mainMixin, propName1, value1);
343 if (EclipseUiUtils.notEmpty(pathCreated)) {
344 Node created = ConnectJcrUtils.getNode(referenceSession, pathCreated);
345
346
347 getAppWorkbenchService().openEntityEditor(created);
348 }
349 }
350 }
351
352 }
353
354 private class DocumentsPage extends AbstractEditorPage {
355 private final static String PAGE_ID = ".documentsPage";
356
357 public DocumentsPage(AbstractTrackerEditor editor) {
358 super(editor, PAGE_ID, "Related Documents");
359 }
360
361 @Override
362 protected void createFormContent(IManagedForm managedForm) {
363
364 ScrolledComposite form = managedForm.getForm();
365 Composite parent = new Composite(form, SWT.NONE);
366 form.setContent(parent);
367 parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
368 DocumentsFolderComposite dfc = new DocumentsFolderComposite(parent, SWT.NO_FOCUS, getNode(),
369 getDocumentsService()) {
370 private static final long serialVersionUID = 1L;
371
372 @Override
373 protected void externalNavigateTo(Path path) {
374
375 Session session = ConnectJcrUtils.getSession(getNode());
376 Node currNode = ConnectJcrUtils.getNode(session, path.toString());
377
378
379
380 getAppWorkbenchService().openEntityEditor(currNode);
381 }
382 };
383 dfc.setLayoutData(EclipseUiUtils.fillAll());
384 String jcrPath = ConnectJcrUtils.getPath(project) + "/" + TrackerNames.TRACKER_DATA;
385 Path path = getDocumentsService().getPath(fileSystemProvider, jcrPath);
386 dfc.populate(path);
387 parent.layout(true, true);
388 }
389
390 }
391
392 private class TasksPage extends AbstractEditorPage {
393 private final static String PAGE_ID = ".tasksPage";
394
395 private TableViewer tableViewer;
396 private Text filterTxt;
397
398 public TasksPage(AbstractTrackerEditor editor) {
399 super(editor, PAGE_ID, "All tasks");
400 }
401
402 protected void createFormContent(final IManagedForm mf) {
403
404
405 ScrolledComposite form = mf.getForm();
406 Composite body = new Composite(form, SWT.NONE);
407 form.setContent(body);
408
409 GridLayout mainLayout = new GridLayout();
410 body.setLayout(mainLayout);
411
412 Composite filterCmp = new Composite(body, SWT.NO_FOCUS);
413 createFilterPart(filterCmp);
414 filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
415 Composite tableCmp = new Composite(body, SWT.NO_FOCUS);
416 appendIssuesPart(tableCmp);
417 tableCmp.setLayoutData(EclipseUiUtils.fillAll());
418
419 AbstractFormPart part = new AbstractFormPart() {
420 @Override
421 public void refresh() {
422 refreshViewer(filterTxt.getText());
423 super.refresh();
424 }
425 };
426 part.initialize(getManagedForm());
427 mf.addPart(part);
428
429 }
430
431 private void appendIssuesPart(Composite parent) {
432 List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
433 columnDefs.add(new ColumnDefinition(getJcrLP(TrackerNames.TRACKER_ID), "ID", 40));
434 columnDefs.add(new ColumnDefinition(getJcrLP(Property.JCR_TITLE), "Title", 300));
435 columnDefs.add(new ColumnDefinition(getJcrLP(ActivitiesNames.ACTIVITIES_TASK_STATUS), "Status", 100));
436 columnDefs.add(new ColumnDefinition(
437 new TrackerLps().new DnLabelProvider(getUserAdminService(), ActivitiesNames.ACTIVITIES_ASSIGNED_TO),
438 "Assignee", 160));
439 columnDefs.add(new ColumnDefinition(new TrackerLps().new MilestoneLabelProvider(getAppService()),
440 "Milestone", 220));
441 columnDefs.add(new ColumnDefinition(new TrackerLps().new CommentNbLabelProvider(), "Comments", 120));
442 columnDefs.add(new ColumnDefinition(new TrackerLps().new ImportanceLabelProvider(), "Importance", 100));
443 columnDefs.add(new ColumnDefinition(new TrackerLps().new PriorityLabelProvider(), "Priority", 100));
444
445 tableViewer = TrackerUiUtils.createTableViewer(parent, SWT.SINGLE, columnDefs);
446 addDClickListener(tableViewer);
447 refreshViewer(null);
448 }
449
450 private void refreshViewer(String filter) {
451 NodeIterator nit = TrackerUtils.getTasks(project, filter);
452 tableViewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray(new Node[0]));
453 tableViewer.refresh();
454 }
455
456 private void createFilterPart(Composite parent) {
457 GridLayout layout = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
458 layout.horizontalSpacing = 5;
459 parent.setLayout(layout);
460 parent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
461
462 filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
463 filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
464
465 final Button addBtn = new Button(parent, SWT.PUSH);
466 addBtn.setToolTipText("Create a task");
467 addBtn.setImage(ConnectImages.ADD);
468
469 filterTxt.addModifyListener(new ModifyListener() {
470 private static final long serialVersionUID = 8130545587125370689L;
471
472 public void modifyText(ModifyEvent event) {
473 refreshViewer(filterTxt.getText());
474 }
475 });
476
477 addBtn.addSelectionListener(new SelectionAdapter() {
478 private static final long serialVersionUID = 9141984572082449486L;
479
480 @Override
481 public void widgetSelected(SelectionEvent e) {
482 String mainMixin = TrackerTypes.TRACKER_TASK;
483 Session referenceSession = ConnectJcrUtils.getSession(project);
484 Shell shell = addBtn.getShell();
485 AppService appService = getAppService();
486 String propName1 = TrackerNames.TRACKER_PROJECT_UID;
487 String value1 = ConnectJcrUtils.get(project, ConnectNames.CONNECT_UID);
488 String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(shell, referenceSession,
489 appService, getAppWorkbenchService(), mainMixin, propName1, value1);
490 if (EclipseUiUtils.notEmpty(pathCreated))
491 refreshViewer(filterTxt.getText());
492 }
493 });
494 }
495 }
496
497
498
499
500 private static ColumnLabelProvider getJcrLP(String propName) {
501 return new SimpleJcrNodeLabelProvider(propName);
502 }
503
504 private void addDClickListener(TableViewer tableViewer) {
505 tableViewer.addDoubleClickListener(new IDoubleClickListener() {
506 @Override
507 public void doubleClick(DoubleClickEvent event) {
508 Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
509
510
511
512 getAppWorkbenchService().openEntityEditor((Node) element);
513 }
514 });
515 }
516
517
518
519 public void setFileSystemProvider(FileSystemProvider fileSystemProvider) {
520 this.fileSystemProvider = fileSystemProvider;
521 }
522 }