View Javadoc
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  /** Default editor to display and edit a project */
68  public class ProjectEditor extends AbstractTrackerEditor {
69  	// private static final long serialVersionUID = -2589214457345896922L;
70  	// public static final String ID = TrackerUiPlugin.PLUGIN_ID + ".projectEditor";
71  
72  	@Inject
73  	private FileSystemProvider fileSystemProvider;
74  
75  	// Ease implementation
76  	private Node project;
77  
78  	// local parameters
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  	// @Override
84  	// public void init(IEditorSite site, IEditorInput input) throws
85  	// PartInitException {
86  	// super.init(site, input);
87  	// }
88  
89  	@Override
90  	protected void addPages() {
91  		project = getNode();
92  		try {
93  			try {
94  				// TODO enhance default parent node creation
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"); // FIXME
101 					session.save();
102 				}
103 			} catch (AccessControlException e) {
104 				// Expected exception for users with limited permission
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 			// if (CurrentUser.isInRole(NodeConstants.ROLE_ADMIN))
122 			// addPage(new TechnicalInfoPage(this, ID + ".techInfoPage", getNode()));
123 		} catch (Exception e) {
124 			throw new TrackerException("Cannot add pages for editor of " + getNode(), e);
125 		}
126 	}
127 
128 	// Specific pages
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 //		protected void createFormContent(final IManagedForm mf) {
142 //			// ScrolledForm form = mf.getForm();
143 //			// Composite body = form.getBody();
144 //			ScrolledComposite form = mf.getForm();
145 //			Composite body = new Composite(form, SWT.NONE);
146 //			form.setContent(body);
147 //			GridLayout layout = new GridLayout();
148 //			body.setLayout(layout);
149 ////			new Label(body, SWT.BORDER).setText("TEST PARENT");
150 //			appendOverviewPart(body);
151 //			appendOpenMilestonePart(body);
152 //		}
153 
154 		@Override
155 		protected void createFormContent(Composite body) {
156 			appendOverviewPart(body);
157 			appendOpenMilestonePart(body);
158 		}
159 
160 		/** Creates the general section */
161 		private void appendOverviewPart(Composite parent) {
162 			FormToolkit tk = getFormToolkit();
163 			GridData twd;
164 
165 			// Section section = TrackerUiUtils.addFormSection(tk, parent,
166 			// ConnectJcrUtils.get(project, Property.JCR_TITLE));
167 			// section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
168 			//
169 			// Composite body = (Composite) section.getClient();
170 			Composite body = new Composite(parent, SWT.BORDER);
171 			GridLayout layout = new GridLayout(2, false);
172 
173 			// layout.numColumns = 5;
174 			body.setLayout(layout);
175 			body.setLayoutData(CmsUtils.fillWidth());
176 			// new Label(body, SWT.BORDER).setText("TEST PARENT");
177 			//// if(true)
178 			// return;
179 
180 			// Manager
181 			createFormBoldLabel(tk, body, "Manager");
182 			managerLk = new Link(body, SWT.NONE);
183 //			managerLk.setText("TEST LINK");
184 			managerLk.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
185 			// if (true)
186 			// return;
187 
188 			// Overdue tasks
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 			// Chart
194 			chartCmp = new Composite(body, SWT.NO_FOCUS);
195 			chartCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
196 			twd = new GridData(SWT.FILL, SWT.FILL, false, false);
197 			// twd.maxWidth = CHART_WIDTH;
198 			// twd.rowspan = 2;
199 			chartCmp.setLayoutData(twd);
200 
201 			// Description
202 			// twd = (GridData) TrackerUiUtils.createFormBoldLabel(tk, body,
203 			// "Details").getLayoutData();
204 			TrackerUiUtils.createFormBoldLabel(tk, body, "Details");
205 			// twd.valign = GridData.TOP;
206 			descLbl = new Label(body, SWT.WRAP);
207 			twd = new GridData(SWT.FILL, SWT.FILL, false, false);
208 			// twd.colspan = 3;
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 					// The chart
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 					// Overdue tasks
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 			// addMainSectionMenu(part);
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 		// SECTION MENU
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 		// MENU ACTIONS
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 					// ConnectWorkbenchUtils.callCommand(getAppWorkbenchService().getOpenEntityEditorCmdId(),
346 					// ConnectEditor.PARAM_JCR_ID, ConnectJcrUtils.getIdentifier(created));
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 			// Composite parent = managedForm.getForm().getBody();
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 					// TODO rather directly use the jcrPath / an URI?
375 					Session session = ConnectJcrUtils.getSession(getNode());
376 					Node currNode = ConnectJcrUtils.getNode(session, path.toString());
377 					// String nodeId = ConnectJcrUtils.getIdentifier(currNode);
378 					// CommandUtils.callCommand(getAppWorkbenchService().getOpenEntityEditorCmdId(),
379 					// ConnectEditor.PARAM_JCR_ID, nodeId);
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 			// ScrolledForm form = mf.getForm();
404 			// Composite body = form.getBody();
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 			// form.reflow(true);
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 	// LOCAL HELPERS
498 
499 	// Shorten this call
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 				// String jcrId = ConnectJcrUtils.getIdentifier((Node) element);
510 				// CommandUtils.callCommand(getAppWorkbenchService().getOpenEntityEditorCmdId(),
511 				// ConnectEditor.PARAM_JCR_ID, jcrId);
512 				getAppWorkbenchService().openEntityEditor((Node) element);
513 			}
514 		});
515 	}
516 
517 
518 	/* DEPENDENCY INJECTION */
519 	public void setFileSystemProvider(FileSystemProvider fileSystemProvider) {
520 		this.fileSystemProvider = fileSystemProvider;
521 	}
522 }