View Javadoc
1   package org.argeo.tracker.e4.parts;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   import java.util.Map;
6   
7   import javax.jcr.Node;
8   import javax.jcr.NodeIterator;
9   import javax.jcr.Property;
10  import javax.jcr.PropertyType;
11  import javax.jcr.RepositoryException;
12  import javax.jcr.Session;
13  
14  import org.argeo.activities.ActivitiesNames;
15  import org.argeo.cms.ui.eclipse.forms.AbstractFormPart;
16  import org.argeo.cms.ui.eclipse.forms.FormToolkit;
17  import org.argeo.cms.ui.eclipse.forms.IFormPart;
18  import org.argeo.cms.ui.eclipse.forms.IManagedForm;
19  import org.argeo.cms.util.CmsUtils;
20  import org.argeo.connect.AppService;
21  import org.argeo.connect.ConnectException;
22  import org.argeo.connect.ConnectNames;
23  import org.argeo.connect.ui.ConnectImages;
24  import org.argeo.connect.ui.ConnectUiConstants;
25  import org.argeo.connect.ui.ConnectUiSnippets;
26  import org.argeo.connect.ui.ConnectWorkbenchUtils;
27  import org.argeo.connect.ui.parts.AskTitleDescriptionDialog;
28  import org.argeo.connect.util.ConnectJcrUtils;
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.TrackerService;
36  import org.argeo.tracker.TrackerTypes;
37  import org.argeo.tracker.core.TrackerUtils;
38  import org.argeo.tracker.ui.MilestoneListComposite;
39  import org.argeo.tracker.ui.TrackerLps;
40  import org.argeo.tracker.ui.TrackerUiConstants;
41  import org.argeo.tracker.ui.VersionComparator;
42  import org.argeo.tracker.ui.controls.RepartitionChart;
43  import org.argeo.tracker.ui.dialogs.ConfigureProjectWizard;
44  import org.argeo.tracker.ui.dialogs.ConfigureVersionWizard;
45  import org.eclipse.jface.action.Action;
46  import org.eclipse.jface.action.ToolBarManager;
47  import org.eclipse.jface.dialogs.MessageDialog;
48  import org.eclipse.jface.resource.ImageDescriptor;
49  import org.eclipse.jface.viewers.ColumnLabelProvider;
50  import org.eclipse.jface.viewers.DoubleClickEvent;
51  import org.eclipse.jface.viewers.IDoubleClickListener;
52  import org.eclipse.jface.viewers.IStructuredSelection;
53  import org.eclipse.jface.viewers.TableViewer;
54  import org.eclipse.jface.viewers.Viewer;
55  import org.eclipse.jface.viewers.ViewerComparator;
56  import org.eclipse.jface.window.Window;
57  import org.eclipse.jface.wizard.WizardDialog;
58  import org.eclipse.swt.SWT;
59  import org.eclipse.swt.custom.ScrolledComposite;
60  import org.eclipse.swt.events.ModifyEvent;
61  import org.eclipse.swt.events.ModifyListener;
62  import org.eclipse.swt.events.SelectionAdapter;
63  import org.eclipse.swt.events.SelectionEvent;
64  import org.eclipse.swt.layout.GridData;
65  import org.eclipse.swt.layout.GridLayout;
66  import org.eclipse.swt.widgets.Button;
67  import org.eclipse.swt.widgets.Composite;
68  import org.eclipse.swt.widgets.Label;
69  import org.eclipse.swt.widgets.Link;
70  import org.eclipse.swt.widgets.Shell;
71  import org.eclipse.swt.widgets.Table;
72  import org.eclipse.swt.widgets.Text;
73  
74  /** Default editor to display and edit an IT project */
75  public class ItProjectEditor extends AbstractTrackerEditor {
76  	// private static final long serialVersionUID = 1787310296792025442L;
77  
78  	// public static final String ID = TrackerUiPlugin.PLUGIN_ID +
79  	// ".itProjectEditor";
80  
81  	// Ease implementation
82  	private Node project;
83  
84  	// local parameters
85  	private final static int CHART_DATA_LIMIT = 8;
86  	private final static int CHART_WIDTH = 300;
87  	private final static int CHART_HEIGHT = 200;
88  
89  	// @Override
90  	// public void init(IEditorSite site, IEditorInput input) throws
91  	// PartInitException {
92  	// super.init(site, input);
93  	// }
94  
95  	@Override
96  	protected void addPages() {
97  		// Initialise the nodes
98  		project = getNode();
99  		try {
100 			addPage(new MainPage(this));
101 			addPage(new IssuesPage(this));
102 			addPage(new MilestoneListPage(this, ".milestoneList", project, getUserAdminService(), getTrackerService(),
103 					getAppWorkbenchService()));
104 			addPage(new VersionsPage(this));
105 			addPage(new ComponentsPage(this));
106 
107 			// if (CurrentUser.isInRole(NodeConstants.ROLE_ADMIN))
108 			// addPage(new TechnicalInfoPage(this, ID + ".techInfoPage", getNode()));
109 		} catch (Exception e) {
110 			throw new TrackerException("Cannot add pages for editor of " + getNode(), e);
111 		}
112 	}
113 
114 	// Specific pages
115 	private class MainPage extends AbstractEditorPage {
116 		public final static String PAGE_ID = "mainPage";
117 
118 		private Link managerLk;
119 		private Link overdueTasksLk;
120 		private Composite chartCmp;
121 		private Label descLbl;
122 
123 		public MainPage(AbstractTrackerEditor editor) {
124 			super(editor, PAGE_ID, "Overview");
125 		}
126 
127 		protected void createFormContent(final IManagedForm mf) {
128 			// ScrolledForm form = mf.getForm();
129 			// Composite body = form.getBody();
130 			ScrolledComposite form = mf.getForm();
131 			Composite body = new Composite(form, SWT.NONE);
132 
133 			GridLayout layout = new GridLayout();
134 			body.setLayout(layout);
135 			appendOverviewPart(body);
136 			appendOpenMilestonePart(body);
137 		}
138 
139 		/** Creates the general section */
140 		private void appendOverviewPart(Composite parent) {
141 			FormToolkit tk = getPageManagedForm().getToolkit();
142 			GridData twd;
143 
144 			Section section = TrackerUiUtils.addFormSection(tk, parent,
145 					ConnectJcrUtils.get(project, Property.JCR_TITLE));
146 			section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
147 
148 			Composite body = (Composite) section.getClient();
149 			GridLayout layout = new GridLayout();
150 			layout.numColumns = 5;
151 			body.setLayout(layout);
152 
153 			// Manager
154 			createFormBoldLabel(tk, body, "Manager");
155 			managerLk = new Link(body, SWT.NONE);
156 			managerLk.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));
157 
158 			// Overdue tasks
159 			createFormBoldLabel(tk, body, "Overdue Tasks");
160 			overdueTasksLk = new Link(body, SWT.NONE);
161 			overdueTasksLk.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));
162 
163 			// Chart
164 			chartCmp = new Composite(body, SWT.NO_FOCUS);
165 			chartCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
166 			twd = new GridData(SWT.CENTER, SWT.CENTER);
167 			// twd.maxWidth = CHART_WIDTH;
168 			// twd.rowspan = 2;
169 			chartCmp.setLayoutData(twd);
170 
171 			// Description
172 			twd = (GridData) TrackerUiUtils.createFormBoldLabel(tk, body, "Details").getLayoutData();
173 			// twd.valign = GridData.TOP;
174 			descLbl = new Label(body, SWT.WRAP);
175 			twd = new GridData(SWT.FILL, SWT.TOP, true, false);
176 			// twd.colspan = 3;
177 			descLbl.setLayoutData(twd);
178 
179 			SectionPart part = new SectionPart((Section) body.getParent()) {
180 
181 				@Override
182 				public void refresh() {
183 					String managerId = ConnectJcrUtils.get(project, TrackerNames.TRACKER_MANAGER);
184 					if (EclipseUiUtils.notEmpty(managerId))
185 						managerLk.setText(getUserAdminService().getUserDisplayName(managerId));
186 					else
187 						managerLk.setText("");
188 
189 					String desc = ConnectJcrUtils.get(project, Property.JCR_DESCRIPTION);
190 					descLbl.setText(desc);
191 
192 					// The chart
193 					CmsUtils.clear(chartCmp);
194 					GridData twd = (GridData) chartCmp.getLayoutData();
195 					Map<String, String> ot = TrackerUtils.getOpenTasksByAssignee(getUserAdminService(), project, null,
196 							CHART_DATA_LIMIT);
197 					if (ot == null || ot.isEmpty()) {
198 						Label lbl = new Label(chartCmp, SWT.CENTER);
199 						lbl.setFont(EclipseUiUtils.getItalicFont(body));
200 						lbl.setText("No open task has been found for this project.");
201 						twd.heightHint = SWT.DEFAULT;
202 					} else {
203 						RepartitionChart coc = new RepartitionChart(chartCmp, SWT.NO_FOCUS);
204 						twd.heightHint = CHART_HEIGHT;
205 						coc.setLayoutData(EclipseUiUtils.fillAll());
206 						coc.setInput("Open tasks by assignee", ot, CHART_WIDTH, CHART_HEIGHT);
207 					}
208 
209 					// Overdue tasks
210 					Long nb = TrackerUtils.getProjectOverdueTasksNumber(project);
211 					overdueTasksLk.setText(nb < 0 ? "-" : nb.toString());
212 
213 					parent.layout(true, true);
214 					section.setFocus();
215 					super.refresh();
216 				}
217 			};
218 			getPageManagedForm().addPart(part);
219 			addMainSectionMenu(part);
220 		}
221 
222 		private Section appendOpenMilestonePart(Composite parent) {
223 			FormToolkit tk = getPageManagedForm().getToolkit();
224 			Section section = TrackerUiUtils.addFormSection(tk, parent, "Open Milestones");
225 			section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
226 
227 			Composite body = (Composite) section.getClient();
228 			body.setLayout(new GridLayout());
229 			final MilestoneListComposite msBoxCmp = new MilestoneListComposite(body, SWT.NO_FOCUS,
230 					getAppWorkbenchService(), project);
231 			msBoxCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
232 
233 			SectionPart part = new SectionPart(section) {
234 
235 				@Override
236 				public void refresh() {
237 					NodeIterator nit = TrackerUtils.getOpenMilestones(project, null);
238 					msBoxCmp.setInput(JcrUtils.nodeIteratorToList(nit).toArray(new Node[0]));
239 					super.refresh();
240 				}
241 			};
242 			getPageManagedForm().addPart(part);
243 			return section;
244 		}
245 	}
246 
247 	// SECTION MENU
248 	private void addMainSectionMenu(SectionPart sectionPart) {
249 		ToolBarManager toolBarManager = TrackerUiUtils.addMenu(sectionPart.getSection());
250 
251 		String tooltip = "Edit the project main information";
252 		Action action = new OpenConfigureDialog(tooltip, ConnectImages.IMG_DESC_EDIT, sectionPart);
253 		toolBarManager.add(action);
254 
255 		tooltip = "Add a milestone to this project";
256 		action = new AddMilestone(sectionPart.getSection().getShell(), tooltip, ConnectImages.IMG_DESC_ADD);
257 		toolBarManager.add(action);
258 
259 		toolBarManager.update(true);
260 	}
261 
262 	// MENU ACTIONS
263 	private class OpenConfigureDialog extends Action {
264 		private static final long serialVersionUID = -6798429720348536525L;
265 		private final SectionPart sectionPart;
266 
267 		private OpenConfigureDialog(String name, ImageDescriptor img, SectionPart sectionPart) {
268 			super(name, img);
269 			this.sectionPart = sectionPart;
270 		}
271 
272 		@Override
273 		public void run() {
274 			Shell currShell = sectionPart.getSection().getShell();
275 			ConfigureProjectWizard wizard = new ConfigureProjectWizard(getUserAdminService(), getTrackerService(),
276 					project);
277 			WizardDialog dialog = new WizardDialog(currShell, wizard);
278 			try {
279 				if (dialog.open() == Window.OK && project.getSession().hasPendingChanges()) {
280 					updatePartName();
281 					sectionPart.getSection().setText(ConnectJcrUtils.get(project, Property.JCR_TITLE));
282 					sectionPart.refresh();
283 					sectionPart.markDirty();
284 					sectionPart.getSection().setFocus();
285 				}
286 			} catch (RepositoryException e) {
287 				throw new TrackerException("Cannot check session state on " + project, e);
288 			}
289 		}
290 	}
291 
292 	private class AddMilestone extends Action {
293 		private static final long serialVersionUID = 5112793747049604434L;
294 		final Shell shell;
295 
296 		private AddMilestone(Shell shell, String name, ImageDescriptor img) {
297 			super(name, img);
298 			this.shell = shell;
299 		}
300 
301 		@Override
302 		public void run() {
303 			String mainMixin = TrackerTypes.TRACKER_MILESTONE;
304 			Session referenceSession = ConnectJcrUtils.getSession(project);
305 			AppService appService = getAppService();
306 			String propName1 = TrackerNames.TRACKER_PROJECT_UID;
307 			String value1 = ConnectJcrUtils.get(project, ConnectNames.CONNECT_UID);
308 			String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(shell, referenceSession, appService,
309 					getAppWorkbenchService(), mainMixin, propName1, value1);
310 			if (EclipseUiUtils.notEmpty(pathCreated)) {
311 				Node created = ConnectJcrUtils.getNode(referenceSession, pathCreated);
312 				// ConnectWorkbenchUtils.callCommand(getAppWorkbenchService().getOpenEntityEditorCmdId(),
313 				// ConnectEditor.PARAM_JCR_ID, ConnectJcrUtils.getIdentifier(created));
314 				getAppWorkbenchService().openEntityEditor(created);
315 			}
316 		}
317 	}
318 
319 	// private void appendMilestoneCmp(Composite parent, Node milestone) {
320 	// String currTitle = ConnectJcrUtils.get(milestone, Property.JCR_TITLE);
321 	// String currId = ConnectJcrUtils.get(milestone, TrackerNames.TRACKER_ID);
322 	// int totalNb = (int) TrackerUtils.getIssues(project, null,
323 	// TrackerNames.TRACKER_MILESTONE_ID, currId).getSize();
324 	// int openNb = (int) TrackerUtils.getIssues(project, null,
325 	// TrackerNames.TRACKER_MILESTONE_ID, currId, true)
326 	// .getSize();
327 	// int closeNb = totalNb - openNb;
328 	//
329 	// if (totalNb <= 0)
330 	// return;
331 	//
332 	// Composite boxCmp = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER); //
333 	// boxCmp.setLayoutData(new GridData(FILL_GRAB));
334 	//
335 	// GridLayout layout = new GridLayout();
336 	// layout.numColumns = 2;
337 	// boxCmp.setLayout(layout);
338 	//
339 	// Link titleLk = new Link(boxCmp, SWT.WRAP);
340 	// titleLk.setLayoutData(new GridData(FILL_GRAB));
341 	// titleLk.setFont(EclipseUiUtils.getBoldFont(boxCmp));
342 	// titleLk.setText("<a>" + currId + "</a>");
343 	// titleLk.addSelectionListener(new SelectionAdapter() {
344 	// private static final long serialVersionUID = 5342086098924045174L;
345 	//
346 	// @Override
347 	// public void widgetSelected(SelectionEvent e) {
348 	// String jcrId = ConnectJcrUtils.getIdentifier(milestone);
349 	// CommandUtils.callCommand(getAppWorkbenchService().getOpenEntityEditorCmdId(),
350 	// OpenEntityEditor.PARAM_JCR_ID, jcrId);
351 	// }
352 	// });
353 	//
354 	// Composite chartCmp = new Composite(boxCmp, SWT.NO_FOCUS);
355 	// GridData twd = new GridData();
356 	// twd.rowspan = 3;
357 	// twd.heightHint = 40;
358 	// twd.valign = GridData.CENTER;
359 	// chartCmp.setLayoutData(twd);
360 	// chartCmp.setLayout(EclipseUiUtils.noSpaceGridLayout());
361 	//
362 	// CategoryOverviewChart coc = new CategoryOverviewChart(chartCmp,
363 	// SWT.NO_FOCUS);
364 	// coc.setInput(currTitle, closeNb, totalNb);
365 	// coc.setLayoutData(new GridData(310, 40));
366 	// coc.layout(true, true);
367 	//
368 	// Label datesLbl = new Label(boxCmp, SWT.WRAP);
369 	// String ddVal = ConnectJcrUtils.getDateFormattedAsString(milestone,
370 	// TrackerNames.TRACKER_TARGET_DATE,
371 	// TrackerUiConstants.defaultDateFormat);
372 	// if (EclipseUiUtils.isEmpty(ddVal)) {
373 	// datesLbl.setText("No due date defined");
374 	// datesLbl.setFont(EclipseUiUtils.getItalicFont(boxCmp));
375 	// } else
376 	// datesLbl.setText("Due date: " + ddVal);
377 	//
378 	// Label descLbl = new Label(boxCmp, SWT.WRAP);
379 	// String desc = ConnectJcrUtils.get(milestone, Property.JCR_DESCRIPTION);
380 	// if (EclipseUiUtils.isEmpty(desc))
381 	// descLbl.setText("-");
382 	// else
383 	// descLbl.setText(desc);
384 	// }
385 
386 	private class IssuesPage extends AbstractEditorPage {
387 		public final static String PAGE_ID = ".issueListPage";
388 
389 		private TableViewer tableViewer;
390 		private Text filterTxt;
391 
392 		public IssuesPage(AbstractTrackerEditor editor) {
393 			super(editor, PAGE_ID, "Issues");
394 		}
395 
396 		protected void createFormContent(final IManagedForm mf) {
397 			// ScrolledForm form = mf.getForm();
398 			// Composite body = form.getBody();
399 			ScrolledComposite form = mf.getForm();
400 			Composite body = new Composite(form, SWT.NONE);
401 
402 			GridLayout mainLayout = new GridLayout();
403 			body.setLayout(mainLayout);
404 
405 			Composite filterCmp = new Composite(body, SWT.NO_FOCUS);
406 			createFilterPart(filterCmp);
407 			filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
408 			Composite tableCmp = new Composite(body, SWT.NO_FOCUS);
409 			appendIssuesPart(tableCmp);
410 			tableCmp.setLayoutData(EclipseUiUtils.fillAll());
411 
412 			AbstractFormPart part = new AbstractFormPart() {
413 				@Override
414 				public void refresh() {
415 					refreshViewer(filterTxt.getText());
416 					super.refresh();
417 				}
418 			};
419 			mf.addPart(part);
420 			// form.reflow(true);
421 		}
422 
423 		private void appendIssuesPart(Composite parent) {
424 			List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
425 			columnDefs.add(new ColumnDefinition(getJcrLP(TrackerNames.TRACKER_ID), "ID", 40));
426 			columnDefs.add(new ColumnDefinition(getJcrLP(Property.JCR_TITLE), "Title", 300));
427 			columnDefs.add(new ColumnDefinition(getJcrLP(ActivitiesNames.ACTIVITIES_TASK_STATUS), "Status", 100));
428 			columnDefs.add(new ColumnDefinition(
429 					new TrackerLps().new DnLabelProvider(getUserAdminService(), ActivitiesNames.ACTIVITIES_ASSIGNED_TO),
430 					"Assignee", 160));
431 			columnDefs.add(new ColumnDefinition(new TrackerLps().new MilestoneLabelProvider(getAppService()),
432 					"Milestone", 220));
433 			columnDefs.add(new ColumnDefinition(new TrackerLps().new ImportanceLabelProvider(), "Importance", 100));
434 			columnDefs.add(new ColumnDefinition(new TrackerLps().new PriorityLabelProvider(), "Priority", 100));
435 			columnDefs.add(new ColumnDefinition(new TrackerLps().new CommentNbLabelProvider(), "Comments", 120));
436 
437 			tableViewer = TrackerUiUtils.createTableViewer(parent, SWT.SINGLE, columnDefs);
438 			addDClickListener(tableViewer);
439 			refreshViewer(null);
440 		}
441 
442 		private void refreshViewer(String filter) {
443 			NodeIterator nit = TrackerUtils.getIssues(project, filter);
444 			tableViewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray(new Node[0]));
445 			tableViewer.refresh();
446 		}
447 
448 		private void createFilterPart(Composite parent) {
449 			GridLayout layout = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
450 			layout.horizontalSpacing = 5;
451 			parent.setLayout(layout);
452 			parent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
453 
454 			filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
455 			filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
456 
457 			final Button addBtn = new Button(parent, SWT.PUSH);
458 			addBtn.setToolTipText("Create an issue");
459 			addBtn.setImage(ConnectImages.ADD);
460 
461 			filterTxt.addModifyListener(new ModifyListener() {
462 				private static final long serialVersionUID = 8130545587125370689L;
463 
464 				public void modifyText(ModifyEvent event) {
465 					refreshViewer(filterTxt.getText());
466 				}
467 			});
468 
469 			addBtn.addSelectionListener(new SelectionAdapter() {
470 				private static final long serialVersionUID = 9141984572082449486L;
471 
472 				@Override
473 				public void widgetSelected(SelectionEvent e) {
474 
475 					String mainMixin = TrackerTypes.TRACKER_ISSUE;
476 					Session referenceSession = ConnectJcrUtils.getSession(project);
477 					Shell shell = addBtn.getShell();
478 					AppService appService = getAppService();
479 					String propName1 = TrackerNames.TRACKER_PROJECT_UID;
480 					String value1 = ConnectJcrUtils.get(project, ConnectNames.CONNECT_UID);
481 					String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(shell, referenceSession,
482 							appService, getAppWorkbenchService(), mainMixin, propName1, value1);
483 					if (EclipseUiUtils.notEmpty(pathCreated))
484 						refreshViewer(filterTxt.getText());
485 
486 					// Session tmpSession = null;
487 					// try {
488 					// AppService as = getAppService();
489 					// tmpSession =
490 					// project.getSession().getRepository().login();
491 					// Node draftIssue = as.createDraftEntity(tmpSession,
492 					// TrackerTypes.TRACKER_ISSUE);
493 					// draftIssue.setProperty(TrackerNames.TRACKER_PROJECT_UID,
494 					// ConnectJcrUtils.get(project, ConnectNames.CONNECT_UID));
495 					// ConfigureIssueWizard wizard = new
496 					// ConfigureIssueWizard(getUserAdminService(),
497 					// getTrackerService(), draftIssue);
498 					// WizardDialog dialog = new WizardDialog(addBtn.getShell(),
499 					// wizard);
500 					// if (dialog.open() == Window.OK) {
501 					// String issueBasePath =
502 					// as.getBaseRelPath(TrackerTypes.TRACKER_ISSUE);
503 					// Node parent = tmpSession.getNode("/" + issueBasePath);
504 					// Node issue = getTrackerService().publishEntity(parent,
505 					// TrackerTypes.TRACKER_ISSUE,
506 					// draftIssue);
507 					// issue = getTrackerService().saveEntity(issue, false);
508 					// project.getSession().refresh(true);
509 					// refreshViewer(filterTxt.getText());
510 					// }
511 					// } catch (RepositoryException e1) {
512 					// throw new TrackerException("Unable to create issue on " +
513 					// project, e1);
514 					// } finally {
515 					// JcrUtils.logoutQuietly(tmpSession);
516 					// }
517 				}
518 			});
519 		}
520 	}
521 
522 	private class VersionsPage extends AbstractEditorPage {
523 		public final static String ID = "projectEditor.issuesPage";
524 
525 		private TableViewer tableViewer;
526 		private Text filterTxt;
527 
528 		public VersionsPage(AbstractTrackerEditor editor) {
529 			super(editor, ID, "Versions");
530 		}
531 
532 		protected void createFormContent(IManagedForm mf) {
533 			// ScrolledForm form = mf.getForm();
534 			// Composite body = form.getBody();
535 			ScrolledComposite form = mf.getForm();
536 			Composite body = new Composite(form, SWT.NONE);
537 
538 			GridLayout mainLayout = new GridLayout();
539 
540 			body.setLayout(mainLayout);
541 			Composite filterCmp = new Composite(body, SWT.NO_FOCUS);
542 			createFilterPart(filterCmp);
543 			filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
544 			Composite tableCmp = new Composite(body, SWT.NO_FOCUS);
545 			appendVersionsPart(mf, tableCmp);
546 			tableCmp.setLayoutData(EclipseUiUtils.fillAll());
547 
548 			// form.reflow(true);
549 		}
550 
551 		private void appendVersionsPart(IManagedForm mf, Composite parent) {
552 			List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
553 			columnDefs.add(new ColumnDefinition(getJcrLP(TrackerNames.TRACKER_ID), "ID", 80));
554 			columnDefs.add(new ColumnDefinition(new TrackerLps().new VersionDateLabelProvider(), "Release Date", 120));
555 			columnDefs.add(new ColumnDefinition(getCountLP(true), "Open issues", 120));
556 			columnDefs.add(new ColumnDefinition(getCountLP(false), "All issues", 120));
557 			columnDefs.add(new ColumnDefinition(getJcrLP(Property.JCR_DESCRIPTION), "Description", 300));
558 			if (canEdit())
559 				columnDefs.add(new ColumnDefinition(getEditionLP(), "", 120));
560 
561 			tableViewer = TrackerUiUtils.createTableViewer(parent, SWT.SINGLE, columnDefs);
562 			tableViewer.setComparator(new ViewerComparator() {
563 				private static final long serialVersionUID = 1L;
564 				VersionComparator comp = new VersionComparator();
565 
566 				public int compare(Viewer viewer, Object e1, Object e2) {
567 					Node n1 = (Node) e1;
568 					Node n2 = (Node) e2;
569 					// Last must be first: we skip 1 & 2
570 					return comp.compare(viewer, ConnectJcrUtils.get(n2, TrackerNames.TRACKER_ID),
571 							ConnectJcrUtils.get(n1, TrackerNames.TRACKER_ID));
572 				};
573 			});
574 			addDClickListener(tableViewer);
575 
576 			AbstractFormPart part = new AbstractFormPart() {
577 				@Override
578 				public void refresh() {
579 					refreshViewer(filterTxt.getText());
580 					super.refresh();
581 				}
582 			};
583 			mf.addPart(part);
584 
585 			if (canEdit()) {
586 				Table table = tableViewer.getTable();
587 				CmsUtils.setItemHeight(table, TrackerUiConstants.DEFAULT_ROW_HEIGHT);
588 				CmsUtils.markup(table);
589 				table.addSelectionListener(new EditionRwtAdapter(part));
590 			}
591 			refreshViewer(null);
592 		}
593 
594 		private void refreshViewer(String filter) {
595 			NodeIterator nit = TrackerUtils.getAllVersions(project, filter);
596 			tableViewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray(new Node[0]));
597 			tableViewer.refresh();
598 		}
599 
600 		private void createFilterPart(Composite parent) {
601 			GridLayout layout = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
602 			layout.horizontalSpacing = 5;
603 			parent.setLayout(layout);
604 			parent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
605 
606 			filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
607 			filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
608 
609 			final Button addBtn = new Button(parent, SWT.PUSH);
610 			addBtn.setToolTipText("Create a new version");
611 			addBtn.setImage(ConnectImages.ADD);
612 
613 			filterTxt.addModifyListener(new ModifyListener() {
614 				private static final long serialVersionUID = 8130545587125370689L;
615 
616 				public void modifyText(ModifyEvent event) {
617 					refreshViewer(filterTxt.getText());
618 				}
619 			});
620 
621 			addBtn.addSelectionListener(new SelectionAdapter() {
622 				private static final long serialVersionUID = -6057495212496327413L;
623 
624 				@Override
625 				public void widgetSelected(SelectionEvent e) {
626 					String mainMixin = TrackerTypes.TRACKER_VERSION;
627 					Session referenceSession = ConnectJcrUtils.getSession(project);
628 					Shell shell = addBtn.getShell();
629 					TrackerService trackerService = getTrackerService();
630 					String propName1 = TrackerNames.TRACKER_PROJECT_UID;
631 					String value1 = ConnectJcrUtils.get(project, ConnectNames.CONNECT_UID);
632 					String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(shell, referenceSession,
633 							trackerService, getAppWorkbenchService(), mainMixin, propName1, value1);
634 					if (EclipseUiUtils.notEmpty(pathCreated))
635 						refreshViewer(filterTxt.getText());
636 					// // FIXME this will fail for users with limited rights
637 					// ConfigureVersionWizard wizard = new
638 					// ConfigureVersionWizard(getTrackerService(), project);
639 					// WizardDialog dialog = new WizardDialog(addBtn.getShell(),
640 					// wizard);
641 					// if (dialog.open() == Window.OK) {
642 					// try {
643 					// project.getSession().save();
644 					// refreshViewer(filterTxt.getText());
645 					// } catch (RepositoryException e1) {
646 					// throw new TrackerException("Unable to create a version
647 					// for" + project, e1);
648 					// }
649 					// }
650 				}
651 			});
652 		}
653 	}
654 
655 	private class ComponentsPage extends AbstractEditorPage {
656 		public final static String ID = "projectEditor.componentsPage";
657 
658 		private TableViewer tableViewer;
659 		private Text filterTxt;
660 
661 		public ComponentsPage(AbstractTrackerEditor editor) {
662 			super(editor, ID, "Components");
663 		}
664 
665 		protected void createFormContent(final IManagedForm mf) {
666 			// ScrolledForm form = mf.getForm();
667 			// Composite body = form.getBody();
668 
669 			ScrolledComposite form = mf.getForm();
670 			Composite body = new Composite(form, SWT.NONE);
671 
672 			GridLayout mainLayout = new GridLayout();
673 			body.setLayout(mainLayout);
674 			Composite filterCmp = new Composite(body, SWT.NO_FOCUS);
675 			createFilterPart(filterCmp);
676 			filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
677 			Composite tableCmp = new Composite(body, SWT.NO_FOCUS);
678 			appendComponentsPart(mf, tableCmp);
679 			tableCmp.setLayoutData(EclipseUiUtils.fillAll());
680 			// form.reflow(true);
681 		}
682 
683 		private void appendComponentsPart(IManagedForm mf, Composite parent) {
684 			List<ColumnDefinition> columnDefs = new ArrayList<ColumnDefinition>();
685 			columnDefs.add(new ColumnDefinition(getJcrLP(Property.JCR_TITLE), "Title", 150));
686 			columnDefs.add(new ColumnDefinition(getJcrLP(Property.JCR_DESCRIPTION), "Description", 300));
687 			columnDefs.add(new ColumnDefinition(getCountLP(true), "Open issues", 120));
688 			columnDefs.add(new ColumnDefinition(getCountLP(false), "All issues", 120));
689 			if (canEdit())
690 				columnDefs.add(new ColumnDefinition(getEditionLP(), "", 120));
691 
692 			tableViewer = TrackerUiUtils.createTableViewer(parent, SWT.SINGLE, columnDefs);
693 			tableViewer.setComparator(new ViewerComparator() {
694 				private static final long serialVersionUID = 1L;
695 
696 				public int compare(Viewer viewer, Object e1, Object e2) {
697 					try {
698 						Node n1 = (Node) e1;
699 						Node n2 = (Node) e2;
700 						return ConnectJcrUtils.get(n1, Property.JCR_TITLE)
701 								.compareToIgnoreCase(ConnectJcrUtils.get(n2, Property.JCR_TITLE));
702 					} catch (ConnectException e) {
703 						// TODO clean this: silently catch exception when an
704 						// item has been deleted.
705 					}
706 					return 0;
707 				};
708 			});
709 			addDClickListener(tableViewer);
710 
711 			AbstractFormPart part = new AbstractFormPart() {
712 				@Override
713 				public void refresh() {
714 					refreshViewer(filterTxt.getText());
715 					super.refresh();
716 				}
717 			};
718 			mf.addPart(part);
719 
720 			if (canEdit()) {
721 				Table table = tableViewer.getTable();
722 				CmsUtils.setItemHeight(table, TrackerUiConstants.DEFAULT_ROW_HEIGHT);
723 				CmsUtils.markup(table);
724 				table.addSelectionListener(new EditionRwtAdapter(part));
725 			}
726 			refreshViewer(null);
727 		}
728 
729 		private void refreshViewer(String filter) {
730 			NodeIterator nit = TrackerUtils.getComponents(project, filter);
731 			if (nit == null || !nit.hasNext())
732 				tableViewer.setInput(null);
733 			else
734 				tableViewer.setInput(JcrUtils.nodeIteratorToList(nit).toArray(new Node[0]));
735 			tableViewer.refresh();
736 		}
737 
738 		private void createFilterPart(Composite parent) {
739 			GridLayout layout = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false));
740 			layout.horizontalSpacing = 5;
741 			parent.setLayout(layout);
742 			parent.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
743 			filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL);
744 			filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
745 			final Button addBtn = new Button(parent, SWT.PUSH);
746 			addBtn.setToolTipText("Create a new component");
747 			addBtn.setImage(ConnectImages.ADD);
748 			filterTxt.addModifyListener(new ModifyListener() {
749 				private static final long serialVersionUID = 8130545587125370689L;
750 
751 				public void modifyText(ModifyEvent event) {
752 					refreshViewer(filterTxt.getText());
753 				}
754 			});
755 
756 			addBtn.addSelectionListener(new SelectionAdapter() {
757 				private static final long serialVersionUID = 9141984572082449486L;
758 
759 				@Override
760 				public void widgetSelected(SelectionEvent e) {
761 
762 					String mainMixin = TrackerTypes.TRACKER_COMPONENT;
763 					Session referenceSession = ConnectJcrUtils.getSession(project);
764 					Shell shell = addBtn.getShell();
765 					TrackerService trackerService = getTrackerService();
766 					String propName1 = TrackerNames.TRACKER_PROJECT_UID;
767 					String value1 = ConnectJcrUtils.get(project, ConnectNames.CONNECT_UID);
768 					String pathCreated = ConnectWorkbenchUtils.createAndConfigureEntity(shell, referenceSession,
769 							trackerService, getAppWorkbenchService(), mainMixin, propName1, value1);
770 					if (EclipseUiUtils.notEmpty(pathCreated))
771 						refreshViewer(filterTxt.getText());
772 
773 					//
774 					// // FIXME save will fail for users with limited rights
775 					// ConfigureComponentWizard wizard = new
776 					// ConfigureComponentWizard(getTrackerService(), project);
777 					// WizardDialog dialog = new WizardDialog(addBtn.getShell(),
778 					// wizard);
779 					// if (dialog.open() == Window.OK) {
780 					// try {
781 					// project.getSession().save();
782 					// refreshViewer(filterTxt.getText());
783 					// } catch (RepositoryException e1) {
784 					// throw new TrackerException("Unable to create a version
785 					// for" + project, e1);
786 					// }
787 					// }
788 				}
789 			});
790 		}
791 	}
792 
793 	// LOCAL HELPERS
794 
795 	// Shorten this call
796 	private static ColumnLabelProvider getJcrLP(String propName) {
797 		return new SimpleJcrNodeLabelProvider(propName);
798 	}
799 
800 	private static ColumnLabelProvider getCountLP(boolean onlyOpen) {
801 		return new ColumnLabelProvider() {
802 			private static final long serialVersionUID = -998161071505982347L;
803 
804 			@Override
805 			public String getText(Object element) {
806 				Node category = (Node) element;
807 				long currNb = TrackerUtils.getIssueNb(category, onlyOpen);
808 				return currNb + "";
809 			}
810 		};
811 	}
812 
813 	private static ColumnLabelProvider getEditionLP() {
814 		return new ColumnLabelProvider() {
815 			private static final long serialVersionUID = 6502008085763687925L;
816 
817 			@Override
818 			public String getText(Object element) {
819 				Node category = (Node) element;
820 				String jcrId = ConnectJcrUtils.getIdentifier(category);
821 
822 				String editHref = ConnectUiConstants.CRUD_EDIT + "/" + jcrId;
823 				String editLinkStr = ConnectUiSnippets.getRWTLink(editHref, ConnectUiConstants.CRUD_EDIT);
824 
825 				// TODO enable deletion of referenced components
826 				if (TrackerUtils.getIssueNb(category, false) == 0) {
827 					String removeHref = ConnectUiConstants.CRUD_DELETE + "/" + jcrId;
828 					String removeLinkStr = ConnectUiSnippets.getRWTLink(removeHref, ConnectUiConstants.CRUD_DELETE);
829 					editLinkStr += "  or  " + removeLinkStr;
830 				}
831 				return editLinkStr;
832 			}
833 		};
834 	}
835 
836 	// private void addDClickListener(TableViewer tableViewer) {
837 	// tableViewer.addDoubleClickListener(new IDoubleClickListener() {
838 	// @Override
839 	// public void doubleClick(DoubleClickEvent event) {
840 	// Object element = ((IStructuredSelection)
841 	// event.getSelection()).getFirstElement();
842 	// String jcrId = ConnectJcrUtils.getIdentifier((Node) element);
843 	// CommandUtils.callCommand(getAppWorkbenchService().getOpenEntityEditorCmdId(),
844 	// OpenEntityEditor.PARAM_JCR_ID, jcrId);
845 	// }
846 	// });
847 	// }
848 
849 	private class EditionRwtAdapter extends SelectionAdapter {
850 		private static final long serialVersionUID = -7459078949241763141L;
851 
852 		private IFormPart part;
853 
854 		public EditionRwtAdapter(IFormPart part) {
855 			this.part = part;
856 		}
857 
858 		public void widgetSelected(SelectionEvent event) {
859 			if (event.detail == ConnectUiConstants.MARKUP_VIEWER_HYPERLINK) {
860 				String string = event.text;
861 				String[] token = string.split("/");
862 				String cmdId = token[0];
863 				String jcrId = token[1];
864 				Shell shell = event.display.getActiveShell();
865 				boolean hasChanged = false;
866 				try {
867 					Node node = project.getSession().getNodeByIdentifier(jcrId);
868 					if (ConnectUiConstants.CRUD_DELETE.equals(cmdId)) {
869 						if (MessageDialog.openConfirm(shell, "Confirm deletion",
870 								"Are you sure you want to delete " + ConnectJcrUtils.get(node, Property.JCR_TITLE))) {
871 							node.remove();
872 							project.getSession().save();
873 							hasChanged = true;
874 						}
875 					} else if (ConnectUiConstants.CRUD_EDIT.equals(cmdId)) {
876 						if (node.isNodeType(TrackerTypes.TRACKER_COMPONENT)) {
877 							String title = ConnectJcrUtils.get(node, Property.JCR_TITLE);
878 							String desc = ConnectJcrUtils.get(node, Property.JCR_DESCRIPTION);
879 							AskTitleDescriptionDialog dialog = new AskTitleDescriptionDialog(shell, "Edit component",
880 									title, desc);
881 							if (dialog.open() == Window.OK) {
882 								hasChanged = ConnectJcrUtils.setJcrProperty(node, Property.JCR_TITLE,
883 										PropertyType.STRING, dialog.getTitle());
884 								hasChanged |= ConnectJcrUtils.setJcrProperty(node, Property.JCR_DESCRIPTION,
885 										PropertyType.STRING, dialog.getDescription());
886 								if (hasChanged)
887 									project.getSession().save();
888 							}
889 						} else if (node.isNodeType(TrackerTypes.TRACKER_VERSION)) {
890 							ConfigureVersionWizard wizard = new ConfigureVersionWizard(getTrackerService(), node);
891 							WizardDialog dialog = new WizardDialog(shell, wizard);
892 							if (dialog.open() == Window.OK) {
893 								if (project.getSession().hasPendingChanges()) {
894 									project.getSession().save();
895 									hasChanged = true;
896 								}
897 							}
898 						}
899 					}
900 				} catch (RepositoryException e) {
901 					throw new TrackerException("Cannot " + cmdId + " with JcrId " + jcrId, e);
902 				}
903 				if (hasChanged)
904 					part.refresh();
905 			}
906 		}
907 	}
908 
909 	private void addDClickListener(TableViewer tableViewer) {
910 		tableViewer.addDoubleClickListener(new IDoubleClickListener() {
911 			@Override
912 			public void doubleClick(DoubleClickEvent event) {
913 				Object element = ((IStructuredSelection) event.getSelection()).getFirstElement();
914 				// String jcrId = ConnectJcrUtils.getIdentifier((Node) element);
915 				// CommandUtils.callCommand(getAppWorkbenchService().getOpenEntityEditorCmdId(),
916 				// ConnectEditor.PARAM_JCR_ID, jcrId);
917 				getAppWorkbenchService().openEntityEditor((Node) element);
918 			}
919 		});
920 	}
921 
922 //	private Label createFormBoldLabel(FormToolkit toolkit, Composite parent, String value) {
923 //		Label label = toolkit.createLabel(parent, " " + value, SWT.END);
924 //		label.setFont(EclipseUiUtils.getBoldFont(parent));
925 //		GridData twd = new GridData(SWT.END, SWT.BOTTOM);
926 //		label.setLayoutData(twd);
927 //		return label;
928 //	}
929 
930 }