View Javadoc
1   package org.argeo.connect.e4.parts;
2   
3   import java.util.List;
4   
5   import javax.annotation.PostConstruct;
6   import javax.annotation.PreDestroy;
7   import javax.inject.Inject;
8   import javax.jcr.Repository;
9   import javax.jcr.Session;
10  
11  import org.argeo.cms.util.CmsUtils;
12  import org.argeo.connect.ConnectConstants;
13  import org.argeo.connect.UserAdminService;
14  import org.argeo.connect.e4.ConnectE4Constants;
15  import org.argeo.connect.e4.resources.parts.TagOrUntagInstancesWizard;
16  import org.argeo.connect.resources.ResourcesNames;
17  import org.argeo.connect.resources.ResourcesService;
18  import org.argeo.connect.ui.ConnectColumnDefinition;
19  import org.argeo.connect.ui.ConnectUiConstants;
20  import org.argeo.connect.ui.ConnectUiStyles;
21  import org.argeo.connect.ui.ConnectUiUtils;
22  import org.argeo.connect.ui.Refreshable;
23  import org.argeo.connect.ui.SystemWorkbenchService;
24  import org.argeo.connect.ui.util.JcrViewerDClickListener;
25  import org.argeo.connect.ui.util.VirtualJcrTableViewer;
26  import org.argeo.connect.ui.widgets.DateText;
27  import org.argeo.connect.ui.widgets.DelayedText;
28  import org.argeo.connect.util.ConnectJcrUtils;
29  import org.argeo.eclipse.ui.EclipseUiUtils;
30  import org.argeo.jcr.JcrUtils;
31  import org.eclipse.e4.ui.di.Focus;
32  import org.eclipse.e4.ui.model.application.ui.basic.MPart;
33  import org.eclipse.jface.dialogs.MessageDialog;
34  import org.eclipse.jface.viewers.TableViewer;
35  import org.eclipse.jface.wizard.Wizard;
36  import org.eclipse.jface.wizard.WizardDialog;
37  import org.eclipse.rap.rwt.service.ServerPushSession;
38  import org.eclipse.swt.SWT;
39  import org.eclipse.swt.events.ModifyEvent;
40  import org.eclipse.swt.events.ModifyListener;
41  import org.eclipse.swt.events.SelectionAdapter;
42  import org.eclipse.swt.events.SelectionEvent;
43  import org.eclipse.swt.events.ShellAdapter;
44  import org.eclipse.swt.events.ShellEvent;
45  import org.eclipse.swt.events.TraverseEvent;
46  import org.eclipse.swt.events.TraverseListener;
47  import org.eclipse.swt.graphics.Point;
48  import org.eclipse.swt.graphics.Rectangle;
49  import org.eclipse.swt.layout.GridData;
50  import org.eclipse.swt.layout.GridLayout;
51  import org.eclipse.swt.layout.RowData;
52  import org.eclipse.swt.layout.RowLayout;
53  import org.eclipse.swt.widgets.Button;
54  import org.eclipse.swt.widgets.Composite;
55  import org.eclipse.swt.widgets.Control;
56  import org.eclipse.swt.widgets.Label;
57  import org.eclipse.swt.widgets.Link;
58  import org.eclipse.swt.widgets.Shell;
59  import org.eclipse.swt.widgets.Text;
60  
61  /**
62   * Search the repository with a given entity type at base path. Expect a
63   * {@Code SearchNodeEditorInput} editor input.
64   */
65  public abstract class AbstractSearchEntityEditor implements Refreshable {
66  
67  	/* DEPENDENCY INJECTION */
68  	@Inject
69  	private Repository repository;
70  	@Inject
71  	private ResourcesService resourcesService;
72  	@Inject
73  	private UserAdminService userAdminService;
74  	@Inject
75  	private SystemWorkbenchService systemWorkbenchService;
76  	@Inject
77  	private MPart mPart;
78  
79  	private Session session;
80  	private String entityType;
81  	private String basePath;
82  
83  	// This page widgets
84  	private VirtualJcrTableViewer tableCmp;
85  	private Text filterTxt;
86  	private TraverseListener traverseListener;
87  
88  	// Locally cache what is displayed in the UI. Enable exports among others.
89  	private Object[] elements;
90  	private String filterString;
91  
92  	public void init() {
93  		// setSite(site);
94  		// setInput(input);
95  		// String label = ((SearchNodeEditorInput) getEditorInput()).getName();
96  		// setPartName(label);
97  		session = ConnectJcrUtils.login(repository);
98  	}
99  
100 	@PostConstruct
101 	public void createPartControl(Composite parent) {
102 		entityType = mPart.getPersistedState().get(ConnectE4Constants.NODE_TYPE);
103 
104 		init();
105 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
106 
107 		Composite searchCmp = new Composite(parent, SWT.NO_FOCUS);
108 		searchCmp.setLayoutData(EclipseUiUtils.fillWidth());
109 		if (!showStaticFilterSection()) {
110 			searchCmp.setLayout(new GridLayout());
111 			Composite filterCmp = new Composite(searchCmp, SWT.NO_FOCUS);
112 			filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
113 			populateSearchPanel(filterCmp);
114 		}
115 		// a simple generic free search part
116 		else
117 			// Add a section with static filters
118 			populateStaticSearchPanel(searchCmp);
119 
120 		// A menu with various actions on selected elements
121 		if (hasCheckBoxes()) {
122 			Composite menuCmp = new Composite(parent, SWT.NO_FOCUS);
123 			createCheckBoxMenu(menuCmp);
124 			menuCmp.setLayoutData(EclipseUiUtils.fillWidth());
125 		}
126 
127 		// The table itself
128 		Composite tableCmp = new Composite(parent, SWT.NO_FOCUS);
129 		createListPart(tableCmp);
130 		tableCmp.setLayoutData(EclipseUiUtils.fillAll());
131 
132 		if (!systemWorkbenchService.lazyLoadLists())
133 			// initialize the table
134 			refreshFilteredList();
135 	}
136 
137 	protected abstract void refreshFilteredList();
138 
139 	/**
140 	 * Overwrite to false if implementation has no static filter session. Warning:
141 	 * the refreshStaticFilteredList() must still be implemented
142 	 */
143 	protected boolean showStaticFilterSection() {
144 		return true;
145 	}
146 
147 	/** Override to provide type specific static filters */
148 	protected void populateStaticFilters(Composite body) {
149 	}
150 
151 	/** Call when a place holder for this info exists */
152 	protected void setNbOfFoundResultsLbl(Label resultNbLbl) {
153 		if (elements == null)
154 			resultNbLbl.setText(" (No result yet) ");
155 		else if (elements.length == 0)
156 			resultNbLbl.setText(" (No result found) ");
157 		else if (elements.length == 1)
158 			resultNbLbl.setText(" One result found ");
159 		else
160 			resultNbLbl.setText(elements.length + " results found ");
161 		resultNbLbl.getParent().layout(true, true);
162 	}
163 
164 	protected void setNbOfFoundResultsLbl(long nbOfResults) {
165 		Label label = getResultLengthLbl();
166 		if (label != null && !label.isDisposed())
167 			setNbOfFoundResultsLbl(label);
168 	}
169 
170 	/**
171 	 * Override this to provide a label that displays the NB of found results
172 	 */
173 	protected Label getResultLengthLbl() {
174 		return null;
175 	}
176 
177 	/**
178 	 * Overwrite to true to use a CheckBoxTableViewer and provide selection
179 	 * abilities.
180 	 */
181 	protected boolean hasCheckBoxes() {
182 		return false;
183 	}
184 
185 	protected void createCheckBoxMenu(Composite parent) {
186 		RowLayout layout = new RowLayout(SWT.HORIZONTAL);
187 		parent.setLayout(layout);
188 
189 		final Button selectAllBtn = new Button(parent, SWT.PUSH);
190 		selectAllBtn.setToolTipText("Select all");
191 		selectAllBtn.setImage(parent.getDisplay().getSystemImage(SWT.CHECK));
192 		// PeopleRapImages.CHECK_UNSELECTEDPeopleRapImages.CHECK_UNSELECTED
193 		RowData rd = new RowData(40, SWT.DEFAULT);
194 		selectAllBtn.setLayoutData(rd);
195 
196 		selectAllBtn.addSelectionListener(new SelectionAdapter() {
197 			private static final long serialVersionUID = 1L;
198 
199 			@Override
200 			public void widgetSelected(SelectionEvent e) {
201 				if (tableCmp.getSelectedElements().length == 0) {
202 					tableCmp.setAllChecked(true);
203 
204 					selectAllBtn.setImage(parent.getDisplay().getSystemImage(SWT.CHECK));
205 					// selectAllBtn.setImage(PeopleRapImages.CHECK_SELECTED);
206 					selectAllBtn.setToolTipText("Unselect all (At least one element is already selected)");
207 				} else {
208 					tableCmp.setAllChecked(false);
209 					selectAllBtn.setImage(null);
210 					// selectAllBtn.setImage(PeopleRapImages.CHECK_UNSELECTED);
211 					selectAllBtn.setToolTipText("Select all");
212 				}
213 			}
214 		});
215 
216 		Button addTagBtn = new Button(parent, SWT.TOGGLE);
217 		addTagBtn.setText("Tag");
218 		// addTagBtn.setImage(PeopleRapImages.ICON_TAG);
219 		addToggleStateTagBtnListener(addTagBtn, ConnectConstants.RESOURCE_TAG, ResourcesNames.CONNECT_TAGS);
220 
221 		// if (isOrgOrPerson()) {
222 		// Button addMLBtn = new Button(parent, SWT.TOGGLE);
223 		// addMLBtn.setText(" Mailing List");
224 		// addMLBtn.setImage(PeopleRapImages.ICON_MAILING_LIST);
225 		// addToggleStateTagBtnListener(addMLBtn,
226 		// PeopleTypes.PEOPLE_MAILING_LIST, PeopleNames.PEOPLE_MAILING_LISTS);
227 		// }
228 	}
229 
230 	private void addToggleStateTagBtnListener(final Button addTagBtn, final String tagId,
231 			final String taggablePropName) {
232 
233 		addTagBtn.addSelectionListener(new SelectionAdapter() {
234 			private static final long serialVersionUID = 9066664395274942610L;
235 			private DropDownPopup dropDown;
236 
237 			@Override
238 			public void widgetSelected(SelectionEvent e) {
239 
240 				boolean toggled = addTagBtn.getSelection();
241 				if (toggled) {
242 					if (dropDown != null && !dropDown.isDisposed())
243 						// should never happen
244 						dropDown.setVisible(true);
245 					else {
246 
247 						dropDown = new DropDownPopup(addTagBtn, tagId, taggablePropName);
248 						dropDown.open();
249 					}
250 				} else {
251 					if (dropDown != null && !dropDown.isDisposed())
252 						dropDown.dispose();
253 				}
254 			}
255 		});
256 	}
257 
258 	private class DropDownPopup extends Shell {
259 		private static final long serialVersionUID = 1L;
260 		private Button toggleBtn;
261 		private String tagId;
262 		private String taggablePropName;
263 
264 		public DropDownPopup(final Control source, String tagId, String taggablePropName) {
265 			super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
266 
267 			toggleBtn = (Button) source;
268 			this.tagId = tagId;
269 			this.taggablePropName = taggablePropName;
270 
271 			populate();
272 			// Add border and shadow style
273 			CmsUtils.style(DropDownPopup.this, ConnectUiStyles.POPUP_SHELL);
274 			pack();
275 			layout();
276 
277 			// position the popup
278 			Rectangle parPosition = source.getBounds();
279 			Point absolute = source.toDisplay(source.getSize().x, source.getSize().y);
280 			absolute.x = absolute.x - parPosition.width;
281 			absolute.y = absolute.y + 2;
282 			setLocation(absolute);
283 
284 			addShellListener(new ShellAdapter() {
285 				private static final long serialVersionUID = 5178980294808435833L;
286 
287 				@Override
288 				public void shellDeactivated(ShellEvent e) {
289 					close();
290 					dispose();
291 					if (!source.isDisposed()) {
292 						boolean toggled = toggleBtn.getSelection();
293 						if (toggled)
294 							toggleBtn.setSelection(false);
295 						e.doit = false;
296 					}
297 
298 				}
299 			});
300 			open();
301 		}
302 
303 		protected void populate() {
304 			Composite parent = DropDownPopup.this;
305 			parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
306 
307 			final Button addTagBtn = new Button(parent, SWT.LEAD | SWT.FLAT);
308 			addTagBtn.setText("Add to selected");
309 			addTagBtn.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
310 			addBtnListener(toggleBtn.getShell(), addTagBtn, tagId, taggablePropName,
311 					TagOrUntagInstancesWizard.TYPE_ADD);
312 
313 			final Button removeTagBtn = new Button(parent, SWT.LEAD | SWT.FLAT);
314 			removeTagBtn.setText("Remove from selected");
315 			removeTagBtn.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));
316 			addBtnListener(toggleBtn.getShell(), removeTagBtn, tagId, taggablePropName,
317 					TagOrUntagInstancesWizard.TYPE_REMOVE);
318 		}
319 	}
320 
321 	protected boolean isOrgOrPerson() {
322 		// SearchNodeEditorInput eei = (SearchNodeEditorInput) getEditorInput();
323 		// String nodeType = eei.getNodeType();
324 		// if (PeopleTypes.PEOPLE_PERSON.equals(nodeType) ||
325 		// PeopleTypes.PEOPLE_ORG.equals(nodeType))
326 		// return true;
327 		// else
328 		return false;
329 	}
330 
331 	private void addBtnListener(final Shell parentShell, final Button button, final String tagId,
332 			final String taggablePropName, final int actionType) {
333 		button.addSelectionListener(new SelectionAdapter() {
334 			private static final long serialVersionUID = 2236384303910015747L;
335 
336 			@Override
337 			public void widgetSelected(SelectionEvent e) {
338 				Object[] rows = tableCmp.getSelectedElements();
339 
340 				if (rows.length == 0)
341 					MessageDialog.openInformation(parentShell, "Unvalid selection",
342 							"No item is selected. Nothing has been done.");
343 				else {
344 					// We assume here we always use xpath query and thus we have
345 					// only single node rows
346 					Wizard wizard = new TagOrUntagInstancesWizard(button.getDisplay(), actionType, session,
347 							resourcesService, systemWorkbenchService, rows, null, tagId, taggablePropName);
348 					WizardDialog dialog = new WizardDialog(parentShell, wizard);
349 					int result = dialog.open();
350 					if (result == WizardDialog.OK) {
351 						refreshFilteredList();
352 					}
353 				}
354 			}
355 		});
356 	}
357 
358 	/** Overwrite to set the correct row height */
359 	protected int getCurrRowHeight() {
360 		return 20;
361 	}
362 
363 	/**
364 	 * Overwrite to false if the table should not be automatically refreshed on
365 	 * startup, see for instance {@code SearchByTagEditor} to have a relevant
366 	 * example
367 	 */
368 	protected boolean queryOnCreation() {
369 		return true;
370 	}
371 
372 	/**
373 	 * Overwrite to provide corresponding column definitions. Also used for exports
374 	 * generation
375 	 */
376 	public abstract List<ConnectColumnDefinition> getColumnDefinition(String extractId);
377 
378 	/**
379 	 * Call this when resetting static filters if you also want to reset the free
380 	 * text search field
381 	 */
382 	protected void resetFilterText() {
383 		getFilterText().setText("");
384 	}
385 
386 	/**
387 	 * Returns an array with the rows that where retrieved by the last search (or
388 	 * all if the filter has been reset in the meantime). By default, returned rows
389 	 * are still *not* linked to the export ID
390 	 */
391 	public Object[] getElements(String exportId) {
392 		return elements;
393 	}
394 
395 	/** Generates a pseudo query String that defines the last filter applied */
396 	public String getFilterAsString() {
397 		return filterString;
398 	}
399 
400 	protected void createListPart(Composite parent) {
401 		parent.setLayout(new GridLayout());
402 		tableCmp = new VirtualJcrTableViewer(parent, SWT.MULTI, getColumnDefinition(null), hasCheckBoxes());
403 		TableViewer tableViewer = tableCmp.getTableViewer();
404 		tableCmp.setLayoutData(EclipseUiUtils.fillAll());
405 
406 		// Small workaround to enable xpath queries
407 		String entityType = getEntityType();
408 		// Xpath queries: only one node by row
409 		if (getColumnDefinition(null).get(0).getSelectorName() == null)
410 			entityType = null;
411 		tableViewer.addDoubleClickListener(new JcrViewerDClickListener(entityType));
412 	}
413 
414 	/** Refresh the table viewer based on the free text search field */
415 	protected void populateStaticSearchPanel(final Composite parent) {
416 		parent.setLayout(new GridLayout(2, false));
417 
418 		Composite filterCmp = new Composite(parent, SWT.NO_FOCUS);
419 		filterCmp.setLayoutData(EclipseUiUtils.fillWidth());
420 		populateSearchPanel(filterCmp);
421 
422 		// add static filter abilities
423 		final Link more = new Link(parent, SWT.NONE);
424 		CmsUtils.markup(more);
425 		Composite staticFilterCmp = new Composite(parent, SWT.NO_FOCUS);
426 		staticFilterCmp.setLayoutData(EclipseUiUtils.fillWidth(2));
427 		populateStaticFilters(staticFilterCmp);
428 
429 		MoreLinkListener listener = new MoreLinkListener(more, staticFilterCmp, false);
430 		// initialise the layout
431 		listener.refresh();
432 		more.addSelectionListener(listener);
433 	}
434 
435 	private class MoreLinkListener extends SelectionAdapter {
436 		private static final long serialVersionUID = -524987616510893463L;
437 		private boolean isShown;
438 		private final Composite staticFilterCmp;
439 		private final Link moreLk;
440 
441 		public MoreLinkListener(Link moreLk, Composite staticFilterCmp, boolean isShown) {
442 			this.moreLk = moreLk;
443 			this.staticFilterCmp = staticFilterCmp;
444 			this.isShown = isShown;
445 		}
446 
447 		@Override
448 		public void widgetSelected(SelectionEvent e) {
449 			isShown = !isShown;
450 			refresh();
451 		}
452 
453 		public void refresh() {
454 			GridData gd = (GridData) staticFilterCmp.getLayoutData();
455 			if (isShown) {
456 				moreLk.setText("<a> Less... </a>");
457 				gd.heightHint = SWT.DEFAULT;
458 			} else {
459 				moreLk.setText("<a> More... </a>");
460 				gd.heightHint = 0;
461 			}
462 			forceLayout();
463 		}
464 
465 		private void forceLayout() {
466 			staticFilterCmp.getParent().getParent().layout(true, true);
467 		}
468 
469 	}
470 
471 	/** Refresh the table viewer based on the free text search field */
472 	protected void populateSearchPanel(Composite parent) {
473 		parent.setLayout(EclipseUiUtils.noSpaceGridLayout());
474 		boolean isDyn = systemWorkbenchService.queryWhenTyping();
475 		int style = SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL;
476 		// if (isDyn)
477 		// filterTxt = new DelayedText(parent, style,
478 		// ConnectUiConstants.SEARCH_TEXT_DELAY);
479 		// else
480 		// filterTxt = new Text(parent, style);
481 
482 		if (isDyn) {
483 			final DelayedText delayedText = new DelayedText(parent, style, ConnectUiConstants.SEARCH_TEXT_DELAY);
484 			final ServerPushSession pushSession = new ServerPushSession();
485 			(delayedText).addDelayedModifyListener(pushSession, new ModifyListener() {
486 				private static final long serialVersionUID = 5003010530960334977L;
487 
488 				public void modifyText(ModifyEvent event) {
489 					delayedText.getText().getDisplay().asyncExec(new Runnable() {
490 						@Override
491 						public void run() {
492 							refreshFilteredList();
493 						}
494 					});
495 					pushSession.stop();
496 				}
497 			});
498 			filterTxt = delayedText.getText();
499 		} else {
500 			filterTxt = new Text(parent, style);
501 		}
502 		filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
503 
504 		traverseListener = new TraverseListener() {
505 			private static final long serialVersionUID = 1914600503113422597L;
506 
507 			@Override
508 			public void keyTraversed(TraverseEvent e) {
509 				if (e.keyCode == SWT.CR) {
510 					e.doit = false;
511 					refreshFilteredList();
512 				}
513 			}
514 		};
515 		filterTxt.addTraverseListener(traverseListener);
516 
517 		// filterTxt.addModifyListener(new ModifyListener() {
518 		// private static final long serialVersionUID = 5003010530960334977L;
519 		//
520 		// public void modifyText(ModifyEvent event) {
521 		// refreshFilteredList();
522 		// }
523 		// });
524 	}
525 
526 	protected TraverseListener getTraverseListener() {
527 		return traverseListener;
528 	}
529 
530 	/** Overwrite to customise the filtering widgets */
531 	protected Text getFilterText() {
532 		return filterTxt;
533 	}
534 
535 	/** Use this method to update the result table */
536 	protected void setViewerInput(Object[] items) {
537 		this.elements = items;
538 		TableViewer tableViewer = tableCmp.getTableViewer();
539 
540 		tableViewer.setInput(items);
541 		// we must explicitly set the elements count
542 		tableViewer.setItemCount(items.length);
543 		setNbOfFoundResultsLbl(items.length);
544 		tableViewer.refresh();
545 	}
546 
547 	/** Use this method to string representing current applied filter */
548 	protected void setFilterString(String filterString) {
549 		this.filterString = filterString;
550 	}
551 
552 	// Life cycle management
553 	@Focus
554 	public void setFocus() {
555 		getFilterText().forceFocus();
556 	}
557 
558 	@PreDestroy
559 	public void dispose() {
560 		JcrUtils.logoutQuietly(session);
561 		// super.dispose();
562 	}
563 
564 	@Override
565 	public void forceRefresh(Object object) {
566 		refreshFilteredList();
567 	}
568 
569 	// Exposes to children classes
570 
571 	protected String getEntityType() {
572 		return entityType;
573 		// return ((SearchNodeEditorInput) getEditorInput()).getNodeType();
574 	}
575 
576 	protected String getBasePath() {
577 		return basePath;
578 		// return ((SearchNodeEditorInput) getEditorInput()).getBasePath();
579 	}
580 
581 	protected VirtualJcrTableViewer getTableViewer() {
582 		return tableCmp;
583 	}
584 
585 	protected Session getSession() {
586 		return session;
587 	}
588 
589 	protected UserAdminService getUserAdminService() {
590 		return userAdminService;
591 	}
592 
593 	protected ResourcesService getResourceService() {
594 		return resourcesService;
595 	}
596 
597 	protected SystemWorkbenchService getSystemWorkbenchService() {
598 		return systemWorkbenchService;
599 	}
600 
601 	// Helpers to create static filters UI
602 	protected Text createBoldLT(Composite parent, String title, String message, String tooltip) {
603 		return createBoldLT(parent, title, message, tooltip, 1);
604 	}
605 
606 	protected Text createBoldLT(Composite parent, String title, String message, String tooltip, int colspan) {
607 		ConnectUiUtils.createBoldLabel(parent, title);
608 		Text text = new Text(parent, SWT.BOTTOM | SWT.BORDER);
609 		text.setLayoutData(EclipseUiUtils.fillAll(colspan, 1));
610 		text.setMessage(message);
611 		text.setToolTipText(tooltip);
612 		return text;
613 	}
614 
615 	protected DateText createLD(Composite parent, String title, String tooltip) {
616 		DateText dateText = new DateText(parent, SWT.NO_FOCUS);
617 		dateText.setToolTipText(tooltip);
618 		dateText.setMessage(title);
619 		return dateText;
620 	}
621 
622 	/* DEPENDENCY INJECTION */
623 	// public void setRepository(Repository repository) {
624 	// this.repository = repository;
625 	// }
626 	//
627 	// public void setUserAdminService(UserAdminService userAdminService) {
628 	// this.userAdminService = userAdminService;
629 	// }
630 	//
631 	// public void setResourcesService(ResourcesService resourcesService) {
632 	// this.resourcesService = resourcesService;
633 	// }
634 	//
635 	// public void setSystemWorkbenchService(SystemWorkbenchService
636 	// systemWorkbenchService) {
637 	// this.systemWorkbenchService = systemWorkbenchService;
638 	// }
639 }