1 /*******************************************************************************
2  * Copyright (c) 2014, 2017 TwelveTone LLC and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  * Steven Spungin <steven@spungin.tv> - initial API and implementation, Bug 432555, Bug 436889, Bug 437372, Bug 440469,
13  * Ongoing Maintenance
14  *******************************************************************************/
15 
16 package org.eclipse.e4.tools.emf.ui.internal.common.component.tabs;
17 
18 import java.io.StringReader;
19 import java.io.StringWriter;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.concurrent.ConcurrentHashMap;
28 
29 import javax.annotation.PostConstruct;
30 import javax.annotation.PreDestroy;
31 import javax.inject.Inject;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.parsers.ParserConfigurationException;
34 import javax.xml.transform.OutputKeys;
35 import javax.xml.transform.Transformer;
36 import javax.xml.transform.TransformerException;
37 import javax.xml.transform.TransformerFactory;
38 import javax.xml.transform.dom.DOMSource;
39 import javax.xml.transform.stream.StreamResult;
40 import javax.xml.xpath.XPath;
41 import javax.xml.xpath.XPathConstants;
42 import javax.xml.xpath.XPathFactory;
43 
44 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
45 import org.eclipse.core.runtime.preferences.InstanceScope;
46 import org.eclipse.e4.core.contexts.IEclipseContext;
47 import org.eclipse.e4.core.services.nls.Translation;
48 import org.eclipse.e4.tools.emf.ui.common.IModelResource;
49 import org.eclipse.e4.tools.emf.ui.common.ModelEditorPreferences;
50 import org.eclipse.e4.tools.emf.ui.common.Plugin;
51 import org.eclipse.e4.tools.emf.ui.internal.Messages;
52 import org.eclipse.e4.tools.emf.ui.internal.ResourceProvider;
53 import org.eclipse.e4.tools.emf.ui.internal.common.ModelEditor;
54 import org.eclipse.e4.tools.emf.ui.internal.common.component.dialogs.BundleImageCache;
55 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.EAttributeEditingSupport.ATT_TYPE;
56 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.E;
57 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.EmptyFilterOption;
58 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.TitleAreaFilterDialog;
59 import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.TitleAreaFilterDialogWithEmptyOptions;
60 import org.eclipse.e4.tools.emf.ui.internal.common.xml.EMFDocumentResourceMediator;
61 import org.eclipse.e4.tools.services.IResourcePool;
62 import org.eclipse.e4.ui.model.application.MApplication;
63 import org.eclipse.emf.common.command.Command;
64 import org.eclipse.emf.common.util.TreeIterator;
65 import org.eclipse.emf.ecore.EAttribute;
66 import org.eclipse.emf.ecore.EObject;
67 import org.eclipse.emf.ecore.util.EcoreUtil;
68 import org.eclipse.emf.edit.command.DeleteCommand;
69 import org.eclipse.emf.edit.domain.EditingDomain;
70 import org.eclipse.jface.text.DocumentEvent;
71 import org.eclipse.jface.text.IDocumentListener;
72 import org.eclipse.jface.viewers.ArrayContentProvider;
73 import org.eclipse.jface.viewers.ColumnLabelProvider;
74 import org.eclipse.jface.viewers.ILabelProvider;
75 import org.eclipse.jface.viewers.IStructuredSelection;
76 import org.eclipse.jface.viewers.LabelProvider;
77 import org.eclipse.jface.viewers.TableViewer;
78 import org.eclipse.jface.viewers.TableViewerColumn;
79 import org.eclipse.jface.viewers.Viewer;
80 import org.eclipse.jface.viewers.ViewerFilter;
81 import org.eclipse.jface.window.Window;
82 import org.eclipse.swt.SWT;
83 import org.eclipse.swt.custom.CTabFolder;
84 import org.eclipse.swt.custom.CTabItem;
85 import org.eclipse.swt.events.MouseAdapter;
86 import org.eclipse.swt.events.MouseEvent;
87 import org.eclipse.swt.events.SelectionAdapter;
88 import org.eclipse.swt.events.SelectionEvent;
89 import org.eclipse.swt.graphics.Color;
90 import org.eclipse.swt.graphics.GC;
91 import org.eclipse.swt.graphics.Image;
92 import org.eclipse.swt.layout.GridData;
93 import org.eclipse.swt.layout.GridLayout;
94 import org.eclipse.swt.widgets.Composite;
95 import org.eclipse.swt.widgets.Control;
96 import org.eclipse.swt.widgets.Display;
97 import org.eclipse.swt.widgets.Shell;
98 import org.eclipse.swt.widgets.Table;
99 import org.eclipse.swt.widgets.TableColumn;
100 import org.eclipse.swt.widgets.TableItem;
101 import org.eclipse.swt.widgets.ToolBar;
102 import org.eclipse.swt.widgets.ToolItem;
103 import org.osgi.service.prefs.BackingStoreException;
104 import org.w3c.dom.Document;
105 import org.w3c.dom.Element;
106 import org.w3c.dom.NodeList;
107 import org.xml.sax.InputSource;
108 
109 /**
110  * A tab that contains a list EObjects, and provides editable columns for
111  * EObject features.
112  *
113  * @author Steven Spungin
114  *
115  */
116 public class ListTab implements IViewEObjects {
117 
118 	static final String ELIPSIS = "..."; //$NON-NLS-1$
119 
120 	ConcurrentHashMap<String, List<EObject>> mapId_Object = new ConcurrentHashMap<>();
121 
122 	@Inject
123 	private IEclipseContext context;
124 
125 	@Inject
126 	private IModelResource modelResource;
127 
128 	private TableViewer tvResults;
129 
130 	@Inject
131 	private IGotoObject gotoObjectHandler;
132 
133 	@Inject
134 	private IResourcePool resourcePool;
135 
136 	@Inject
137 	@Translation
138 	protected Messages Messages;
139 
140 	@Inject
141 	MApplication app;
142 
143 	BundleImageCache imageCache;
144 
145 	private CTabItem tabItem;
146 
147 	private ModelResourceContentProvider provider;
148 
149 	private IDocumentListener documentListener;
150 
151 	private Collection<?> highlightedItems;
152 
153 	protected Image imgMarkedItem;
154 
155 	LinkedHashMap<String, EAttributeTableViewerColumn> defaultColumns = new LinkedHashMap<>();
156 	LinkedHashMap<String, EAttributeTableViewerColumn> optionalColumns = new LinkedHashMap<>();
157 	LinkedHashMap<String, TableColumn> requiredColumns = new LinkedHashMap<>();
158 	private TableViewerColumn colItem;
159 	private TableViewerColumn colGo;
160 	private TableViewerColumn colGoXmi;
161 	private TableViewerColumn colMarked;
162 
163 	private ToolItem filterByItem;
164 	private ToolItem filterByAttribute;
165 	private String filterByAttrName;
166 	private String filterByItemName;
167 	private EmptyFilterOption filterByAttrEmptyOption;
168 
169 	@PreDestroy
preDestroy()170 	public void preDestroy() {
171 		// race condition issue with observables (exception is not thrown when
172 		// break points are set)
173 		tvResults.setContentProvider(ArrayContentProvider.getInstance());
174 		context.get(EMFDocumentResourceMediator.class).getDocument().removeDocumentListener(documentListener);
175 	}
176 
177 	// save custom column and filter settings
saveSettings()178 	public void saveSettings() {
179 		final IEclipsePreferences pref = InstanceScope.INSTANCE.getNode(Plugin.ID);
180 		try {
181 			final Document doc = DocUtil.createDocument("list-tab"); //$NON-NLS-1$
182 			final Element cols = DocUtil.createChild(doc.getDocumentElement(), "columns"); //$NON-NLS-1$
183 
184 			final ArrayList<TableColumn> allCols = TableViewerUtil.getColumnsInDisplayOrder(tvResults);
185 			for (final TableColumn col : allCols) {
186 				String id;
187 				if (requiredColumns.containsValue(col)) {
188 					id = getKey(requiredColumns, col);
189 				} else {
190 					id = col.getText();
191 				}
192 				saveColumn(cols, id, col);
193 			}
194 
195 			final Element filters = DocUtil.createChild(doc.getDocumentElement(), "filters"); //$NON-NLS-1$
196 			if (E.notEmpty(filterByAttrName)) {
197 				final Element filter = DocUtil.createChild(filters, "filter"); //$NON-NLS-1$
198 				DocUtil.createChild(filter, "type").setTextContent("attribute"); //$NON-NLS-1$//$NON-NLS-2$
199 				DocUtil.createChild(filter, "condition").setTextContent(filterByAttrName); //$NON-NLS-1$
200 				DocUtil.createChild(filter, "emptyOption").setTextContent(filterByAttrEmptyOption.name()); //$NON-NLS-1$
201 			}
202 			if (E.notEmpty(filterByItemName)) {
203 				final Element filter = DocUtil.createChild(filters, "filter"); //$NON-NLS-1$
204 				DocUtil.createChild(filter, "type").setTextContent("item"); //$NON-NLS-1$ //$NON-NLS-2$
205 				DocUtil.createChild(filter, "condition").setTextContent(filterByItemName); //$NON-NLS-1$
206 			}
207 
208 			pref.put("list-tab-xml", docToString(doc)); //$NON-NLS-1$
209 		} catch (final ParserConfigurationException e1) {
210 			e1.printStackTrace();
211 		} catch (final TransformerException e) {
212 			e.printStackTrace();
213 		}
214 
215 		try {
216 			pref.flush();
217 		} catch (final BackingStoreException e) {
218 			e.printStackTrace();
219 		}
220 	}
221 
getKey(Map<String, ?> map, Object value)222 	private String getKey(Map<String, ?> map, Object value) {
223 		for (final Entry<String, ?> entry : map.entrySet()) {
224 			if (entry.getValue().equals(value)) {
225 				return entry.getKey();
226 			}
227 		}
228 		return null;
229 	}
230 
saveColumn(Element eleCols, String columnName, TableColumn objCol)231 	private void saveColumn(Element eleCols, String columnName, TableColumn objCol) {
232 		final Element col = DocUtil.createChild(eleCols, "column"); //$NON-NLS-1$
233 
234 		DocUtil.createChild(col, "attribute").setTextContent(columnName); //$NON-NLS-1$
235 
236 		final Integer width = objCol.getWidth();
237 		DocUtil.createChild(col, "width").setTextContent(width.toString()); //$NON-NLS-1$
238 	}
239 
240 	// load custom column and filter settings
loadSettings()241 	private void loadSettings() {
242 		final IEclipsePreferences pref = InstanceScope.INSTANCE.getNode(Plugin.ID);
243 
244 		final boolean restoreColumns = pref.getBoolean(ModelEditorPreferences.LIST_TAB_REMEMBER_COLUMNS, false);
245 		final boolean restoreFilters = pref.getBoolean(ModelEditorPreferences.LIST_TAB_REMEMBER_FILTERS, false);
246 		if (!restoreColumns && !restoreFilters) {
247 			return;
248 		}
249 
250 		final String xml = pref.get("list-tab-xml", ""); //$NON-NLS-1$ //$NON-NLS-2$
251 		if (E.notEmpty(xml)) {
252 			try {
253 				final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
254 						.parse(new InputSource(new StringReader(xml)));
255 				final XPath xpath = XPathFactory.newInstance().newXPath();
256 				NodeList list;
257 				if (restoreColumns) {
258 					// restore columns and column widths
259 					list = (NodeList) xpath.evaluate("//columns/column", doc, XPathConstants.NODESET); //$NON-NLS-1$
260 					for (int i = 0; i < list.getLength(); i++) {
261 						final Element ele = (Element) list.item(i);
262 						TableColumn col;
263 						final String colName = xpath.evaluate("attribute/text()", ele); //$NON-NLS-1$
264 						if (colName.isEmpty()) {
265 							continue;
266 						}
267 						col = requiredColumns.get(colName);
268 						if (col == null) {
269 							col = addColumn(colName).getTableViewerColumn().getColumn();
270 						}
271 
272 						// move it to the end of the list.
273 						final int currentIndex = TableViewerUtil.getVisibleColumnIndex(tvResults, col);
274 						final int[] order = tvResults.getTable().getColumnOrder();
275 						for (int idx = 0; idx < order.length; idx++) {
276 							if (order[idx] > currentIndex) {
277 								order[idx]--;
278 							} else if (order[idx] == currentIndex) {
279 								order[idx] = order.length - 1;
280 							}
281 						}
282 						tvResults.getTable().setColumnOrder(order);
283 
284 						//					if ("Item".equals(colName)) { //$NON-NLS-1$
285 						// col = colItem;
286 						//					} else if ("Item".equals(colName)) { //$NON-NLS-1$
287 						// col = colItem;
288 						// }
289 
290 						final String sWidth = xpath.evaluate("width/text()", ele); //$NON-NLS-1$
291 						try {
292 							col.setWidth(Integer.parseInt(sWidth));
293 						} catch (final Exception e) {
294 						}
295 					}
296 				}
297 
298 				if (restoreFilters) {
299 					// restore filters
300 					list = (NodeList) xpath.evaluate("//filters/filter", doc, XPathConstants.NODESET); //$NON-NLS-1$
301 					for (int i = 0; i < list.getLength(); i++) {
302 						final Element ele = (Element) list.item(i);
303 						final String type = xpath.evaluate("type/text()", ele); //$NON-NLS-1$
304 						final String condition = xpath.evaluate("condition/text()", ele); //$NON-NLS-1$
305 						final String emptyOption = xpath.evaluate("emptyOption/text()", ele); //$NON-NLS-1$
306 						if ("item".equals(type)) { //$NON-NLS-1$
307 							filterByItem(condition);
308 						} else if ("attribute".equals(type)) { //$NON-NLS-1$
309 							EmptyFilterOption emptyFilterOption;
310 							try {
311 								emptyFilterOption = EmptyFilterOption.valueOf(emptyOption);
312 							} catch (final Exception e) {
313 								emptyFilterOption = EmptyFilterOption.INCLUDE;
314 							}
315 							filterByAttribute(condition, emptyFilterOption);
316 						}
317 					}
318 				}
319 			} catch (final Exception e) {
320 			}
321 		}
322 	}
323 
324 	// @Refactor
docToString(Document doc)325 	static private String docToString(Document doc) throws TransformerException {
326 		final TransformerFactory tf = TransformerFactory.newInstance();
327 		final Transformer transformer = tf.newTransformer();
328 		transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
329 		final StringWriter writer = new StringWriter();
330 		transformer.transform(new DOMSource(doc), new StreamResult(writer));
331 		final String output = writer.getBuffer().toString().replaceAll("\n|\r", ""); //$NON-NLS-1$ //$NON-NLS-2$
332 		return output;
333 	}
334 
335 	// @Refactor
join(Collection<String> items, String separator)336 	static String join(Collection<String> items, String separator) {
337 		final StringBuilder sb = new StringBuilder();
338 		for (final String item : items) {
339 			sb.append(item);
340 			sb.append(separator);
341 		}
342 		if (sb.length() > 0) {
343 			sb.setLength(sb.length() - separator.length());
344 		}
345 		return sb.toString();
346 	}
347 
348 	@PostConstruct
postConstruct(final CTabFolder tabFolder)349 	public void postConstruct(final CTabFolder tabFolder) {
350 		imageCache = new BundleImageCache(context.get(Display.class), getClass().getClassLoader());
351 		tabFolder.addDisposeListener(e -> imageCache.dispose());
352 		try {
353 			imgMarkedItem = imageCache.create(Plugin.ID, "/icons/full/obj16/mark_occurrences.png"); //$NON-NLS-1$
354 		} catch (final Exception e2) {
355 			e2.printStackTrace();
356 		}
357 
358 		documentListener = new IDocumentListener() {
359 
360 			@Override
361 			public void documentChanged(DocumentEvent event) {
362 				reload();
363 			}
364 
365 			@Override
366 			public void documentAboutToBeChanged(DocumentEvent event) {
367 			}
368 		};
369 		context.get(EMFDocumentResourceMediator.class).getDocument().addDocumentListener(documentListener);
370 
371 		tabItem = new CTabItem(tabFolder, SWT.NONE, 1);
372 
373 		final Composite composite = new Composite(tabFolder, SWT.NONE);
374 		composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
375 		composite.setLayout(new GridLayout(2, false));
376 		tabItem.setControl(composite);
377 		tabItem.setText(Messages.ListTab_0);
378 
379 		tabItem.setImage(resourcePool.getImageUnchecked(ResourceProvider.IMG_Widgets_table_obj));
380 
381 		final ToolBar toolBar = new ToolBar(composite, SWT.FLAT | SWT.NO_FOCUS);
382 		toolBar.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false, 2, 1));
383 
384 		{
385 			final ToolItem button = new ToolItem(toolBar, SWT.PUSH);
386 			button.setText(Messages.ListTab_addColumn + ELIPSIS);
387 			button.setImage(imageCache.create("/icons/full/obj16/add_column.gif")); //$NON-NLS-1$
388 
389 			button.addSelectionListener(new SelectionAdapter() {
390 				@Override
391 				public void widgetSelected(SelectionEvent e) {
392 					final TitleAreaFilterDialogWithEmptyOptions dlg = createEObjectAttributePicker(Messages.ListTab_addColumn);
393 					dlg.setShowEmptyOptions(false);
394 					if (dlg.open() == Window.OK) {
395 						// Add Column
396 						final String attName = dlg.getFirstElement().toString();
397 						final EAttributeTableViewerColumn col = addColumn(attName);
398 						col.getTableViewerColumn().getColumn().pack();
399 						tvResults.refresh();
400 					}
401 				}
402 			});
403 		}
404 
405 		{
406 			final ToolItem button = new ToolItem(toolBar, SWT.PUSH);
407 			button.setText(Messages.ListTab_resetColumns);
408 			button.setImage(imageCache.create("/icons/full/obj16/reset_columns.gif")); //$NON-NLS-1$
409 
410 			button.addSelectionListener(new SelectionAdapter() {
411 				@Override
412 				public void widgetSelected(SelectionEvent e) {
413 					for (final EAttributeTableViewerColumn col : optionalColumns.values()) {
414 						col.dispose();
415 					}
416 					optionalColumns.clear();
417 				}
418 			});
419 		}
420 
421 		new ToolItem(toolBar, SWT.SEPARATOR);
422 
423 		filterByItem = new ToolItem(toolBar, SWT.NONE);
424 		{
425 
426 			filterByItem.setText(Messages.ListTab_filterByItem + ELIPSIS);
427 			filterByItem.setImage(imageCache.create("/icons/full/obj16/filter_by_item.gif")); //$NON-NLS-1$
428 
429 			filterByItem.addSelectionListener(new SelectionAdapter() {
430 
431 				@Override
432 				public void widgetSelected(SelectionEvent e) {
433 					final TitleAreaFilterDialog dlg = createElementTypePicker(Messages.ListTab_filterByItem);
434 					if (dlg.open() == Window.OK) {
435 						filterByItem(dlg.getFirstElement().toString());
436 					}
437 				}
438 			});
439 		}
440 
441 		filterByAttribute = new ToolItem(toolBar, SWT.NONE);
442 		{
443 
444 			filterByAttribute.setText(Messages.ListTab_filterByAttribute + ELIPSIS);
445 			filterByAttribute.setImage(imageCache.create("/icons/full/obj16/filter_by_attribute.gif")); //$NON-NLS-1$
446 
447 			filterByAttribute.addSelectionListener(new SelectionAdapter() {
448 				@Override
449 				public void widgetSelected(SelectionEvent e) {
450 					final TitleAreaFilterDialogWithEmptyOptions dlg = createEObjectAttributePicker(Messages.ListTab_filterByAttribute);
451 					if (dlg.open() == Window.OK) {
452 						filterByAttribute(dlg.getFirstElement().toString(), dlg.getEmptyFilterOption());
453 					}
454 				}
455 			});
456 		}
457 
458 		{
459 			final ToolItem filterRemove = new ToolItem(toolBar, SWT.NONE);
460 			filterRemove.setText(Messages.ListTab_removeFilter);
461 			filterRemove.setImage(imageCache.create("/icons/full/obj16/remove_filter.png")); //$NON-NLS-1$
462 
463 			filterRemove.addSelectionListener(new SelectionAdapter() {
464 				@Override
465 				public void widgetSelected(SelectionEvent e) {
466 					filterByItemName = null;
467 					filterByAttrName = null;
468 					tvResults.setFilters();
469 					filterByItem.setText(Messages.ListTab_filterByItem + ELIPSIS);
470 					filterByAttribute.setText(Messages.ListTab_markAttribute + ELIPSIS);
471 				}
472 			});
473 		}
474 
475 		new ToolItem(toolBar, SWT.SEPARATOR);
476 
477 		{
478 			final E4ToolItemMenu tiCommands = new E4ToolItemMenu(toolBar, context);
479 			tiCommands.getToolItem().setImage(imageCache.create("/icons/full/obj16/command.gif")); //$NON-NLS-1$
480 
481 			final ArrayList<String> commandIds = new ArrayList<>();
482 			commandIds.add("org.eclipse.e4.tools.emf.ui.command.mark_duplicate_attributes"); //$NON-NLS-1$
483 			commandIds.add("org.eclipse.e4.tools.emf.ui.command.mark_duplicate_ids"); //$NON-NLS-1$
484 			commandIds.add("org.eclipse.e4.tools.emf.ui.command.mark_duplicate_labels"); //$NON-NLS-1$
485 			commandIds.add("org.eclipse.e4.tools.emf.ui.command.repair_duplicate_ids"); //$NON-NLS-1$
486 			commandIds.add(E4ToolItemMenu.SEPARATOR);
487 			commandIds.add("org.eclipse.e4.tools.emf.ui.command.unmark"); //$NON-NLS-1$
488 			commandIds.add(E4ToolItemMenu.SEPARATOR);
489 			commandIds.add("org.eclipse.e4.tools.emf.ui.command.autosizeColumns"); //$NON-NLS-1$
490 			commandIds.add("org.eclipse.e4.tools.emf.ui.command.resetToDefault"); //$NON-NLS-1$
491 			tiCommands.addCommands(commandIds);
492 		}
493 
494 		tvResults = new TableViewer(composite, SWT.FULL_SELECTION);
495 		tvResults.getTable().setHeaderVisible(true);
496 		tvResults.getTable().setLinesVisible(true);
497 
498 		provider = new ModelResourceContentProvider();
499 		tvResults.setContentProvider(provider);
500 
501 		tvResults.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
502 		((GridData) tvResults.getTable().getLayoutData()).horizontalSpan = 2;
503 
504 		final Image imgForm = resourcePool.getImageUnchecked(ResourceProvider.IMG_Obj16_application_form);
505 		final Image imgXmi = resourcePool.getImageUnchecked(ResourceProvider.IMG_Obj16_chart_organisation);
506 
507 		colGo = new TableViewerColumn(tvResults, SWT.NONE);
508 		colGo.getColumn().setText(Messages.ListTab_col_go);
509 		requiredColumns.put("GoTree", colGo.getColumn()); //$NON-NLS-1$
510 		colGo.setLabelProvider(new ColumnLabelProvider() {
511 			@Override
512 			public Image getImage(Object element) {
513 				return imgForm;
514 			}
515 
516 			@Override
517 			public String getText(Object element) {
518 				return ""; //$NON-NLS-1$
519 			}
520 		});
521 
522 		colGoXmi = new TableViewerColumn(tvResults, SWT.NONE);
523 		colGoXmi.getColumn().setText(Messages.ListTab_col_go);
524 		requiredColumns.put("GoXmi", colGoXmi.getColumn()); //$NON-NLS-1$
525 		colGoXmi.setLabelProvider(new ColumnLabelProvider() {
526 			@Override
527 			public String getText(Object element) {
528 				return ""; //$NON-NLS-1$
529 			}
530 
531 			@Override
532 			public Image getImage(Object element) {
533 				return imgXmi;
534 			}
535 		});
536 
537 		tvResults.getTable().addMouseListener(new MouseAdapter() {
538 			@Override
539 			public void mouseDown(MouseEvent e) {
540 				if (TableViewerUtil.isColumnClicked(tvResults, e, colGo)) {
541 					gotoObjectHandler.gotoEObject(ModelEditor.TAB_FORM, (EObject) TableViewerUtil.getData(tvResults, e));
542 				} else if (TableViewerUtil.isColumnClicked(tvResults, e, colGoXmi)) {
543 					gotoObjectHandler.gotoEObject(ModelEditor.TAB_XMI, (EObject) TableViewerUtil.getData(tvResults, e));
544 				}
545 			}
546 		});
547 
548 		colMarked = new TableViewerColumn(tvResults, SWT.NONE);
549 		colMarked.getColumn().setWidth(16);
550 		colMarked.getColumn().setText(Messages.ListTab_mark);
551 		requiredColumns.put("Marked", colMarked.getColumn()); //$NON-NLS-1$
552 		colMarked.setLabelProvider(new ColumnLabelProvider() {
553 			@Override
554 			public Image getImage(Object element) {
555 				Image ret = null;
556 				if (isHighlighted(element)) {
557 					try {
558 						ret = imgMarkedItem;
559 					} catch (final Exception e) {
560 					}
561 				}
562 				return ret;
563 			}
564 
565 			@Override
566 			public String getText(Object element) {
567 				return ""; //$NON-NLS-1$
568 			}
569 		});
570 
571 		colItem = new TableViewerColumn(tvResults, SWT.NONE);
572 		colItem.getColumn().setText(Messages.ListTab_col_item);
573 		requiredColumns.put("Item", colItem.getColumn()); //$NON-NLS-1$
574 		colItem.setLabelProvider(new ColumnLabelProvider_Markable() {
575 			@Override
576 			public String getText(Object element) {
577 				final EObject eObject = (EObject) element;
578 				return super.getText(eObject.eClass().getName());
579 			}
580 		});
581 
582 		app.getContext().set("org.eclipse.e4.tools.active-object-viewer", this); //$NON-NLS-1$
583 
584 		final EAttributeTableViewerColumn colId = new EAttributeTableViewerColumn(tvResults,
585 				"elementId", "elementId", context); //$NON-NLS-1$//$NON-NLS-2$
586 		defaultColumns.put("elementId", colId); //$NON-NLS-1$
587 
588 		final EAttributeTableViewerColumn colLabel = new EAttributeTableViewerColumn_Markable(tvResults,
589 				"label", "label", context); //$NON-NLS-1$//$NON-NLS-2$
590 		defaultColumns.put("label", colLabel); //$NON-NLS-1$
591 
592 		// Custom selection for marked items
593 		tvResults.getTable().addListener(SWT.EraseItem, event -> {
594 			event.detail &= ~SWT.HOT;
595 			if ((event.detail & SWT.SELECTED) == 0) {
596 				return; // / item not selected
597 			}
598 
599 			final TableItem item = (TableItem) event.item;
600 			if (isHighlighted(item.getData())) {
601 
602 				final Table table = (Table) event.widget;
603 				final int clientWidth = table.getClientArea().width;
604 				final GC gc = event.gc;
605 				// Color oldForeground = gc.getForeground();
606 				// Color oldBackground = gc.getBackground();
607 
608 				// gc.setBackground(item.getDisplay().getSystemColor(SWT.COLOR_YELLOW));
609 				gc.setForeground(item.getDisplay().getSystemColor(SWT.COLOR_RED));
610 				gc.fillRectangle(0, event.y, clientWidth, event.height);
611 
612 				// gc.setForeground(oldForeground);
613 				// gc.setBackground(oldBackground);
614 				event.detail &= ~SWT.SELECTED;
615 			}
616 		});
617 
618 		tvResults.getTable().setFocus();
619 
620 		for (final EAttributeTableViewerColumn col : defaultColumns.values()) {
621 			col.getTableViewerColumn().getColumn().setMoveable(true);
622 		}
623 		for (final TableColumn col : requiredColumns.values()) {
624 			col.setMoveable(true);
625 		}
626 
627 		makeSortable(colId.getTableViewerColumn().getColumn(), new AttributeColumnLabelSorter(colId
628 				.getTableViewerColumn().getColumn(), "elementId")); //$NON-NLS-1$
629 		makeSortable(colLabel.getTableViewerColumn().getColumn(), new AttributeColumnLabelSorter(colLabel
630 				.getTableViewerColumn().getColumn(), "label")); //$NON-NLS-1$
631 		makeSortable(colItem.getColumn(), new TableViewerUtil.ColumnLabelSorter(colItem.getColumn()));
632 		makeSortable(colMarked.getColumn(), new TableViewerUtil.AbstractInvertableTableSorter() {
633 
634 			@Override
635 			public int compare(Viewer viewer, Object e1, Object e2) {
636 				final boolean mark1 = isHighlighted(e1);
637 				final boolean mark2 = isHighlighted(e2);
638 				if (mark1 && !mark2) {
639 					return -1;
640 				} else if (mark2 && !mark1) {
641 					return 1;
642 				} else {
643 					return 0;
644 				}
645 			}
646 		});
647 
648 		reload();
649 		TableViewerUtil.refreshAndPack(tvResults);
650 		loadSettings();
651 	}
652 
makeSortable(TableColumn column, TableViewerUtil.AbstractInvertableTableSorter sorter)653 	private void makeSortable(TableColumn column, TableViewerUtil.AbstractInvertableTableSorter sorter) {
654 		new TableViewerUtil.TableSortSelectionListener(tvResults, column, sorter, SWT.UP, false);
655 	}
656 
reload()657 	public void reload() {
658 		tvResults.setInput(modelResource);
659 	}
660 
getViewer()661 	public TableViewer getViewer() {
662 		return tvResults;
663 	}
664 
getTabItem()665 	public CTabItem getTabItem() {
666 		return tabItem;
667 	}
668 
getContext()669 	public IEclipseContext getContext() {
670 		return context;
671 	}
672 
673 	@Override
highlightEObjects(Collection<EObject> items)674 	public void highlightEObjects(Collection<EObject> items) {
675 		highlightedItems = items;
676 		tvResults.refresh();
677 	}
678 
679 	@Override
getAllEObjects()680 	public List<EObject> getAllEObjects() {
681 		final ArrayList<EObject> list = new ArrayList<>();
682 		final TreeIterator<Object> itTree = EcoreUtil.getAllContents(modelResource.getRoot());
683 		while (itTree.hasNext()) {
684 			final Object object = itTree.next();
685 			final EObject eObject = (EObject) object;
686 			final EAttribute att = EmfUtil.getAttribute(eObject, "elementId"); //$NON-NLS-1$
687 			if (att != null) {
688 				list.add(eObject);
689 			}
690 		}
691 		return list;
692 	}
693 
694 	@Override
getSelectedEObjects()695 	public Collection<EObject> getSelectedEObjects() {
696 		final ArrayList<EObject> selected = new ArrayList<>();
697 		for (final Object item : ((IStructuredSelection) tvResults.getSelection()).toList()) {
698 			if (item instanceof EObject) {
699 				selected.add((EObject) item);
700 
701 			}
702 		}
703 		return selected;
704 	}
705 
706 	@Override
deleteEObjects(Collection<EObject> list)707 	public void deleteEObjects(Collection<EObject> list) {
708 		if (list.isEmpty() == false) {
709 			final Command cmd = DeleteCommand.create(modelResource.getEditingDomain(), list);
710 			if (cmd.canExecute()) {
711 				modelResource.getEditingDomain().getCommandStack().execute(cmd);
712 			}
713 			reload();
714 		}
715 	}
716 
createEObjectAttributePicker(final String title)717 	private TitleAreaFilterDialogWithEmptyOptions createEObjectAttributePicker(final String title) {
718 		// Get Attribute Names
719 		final HashSet<String> set = new HashSet<>();
720 		final Collection<EObject> allEObjects = getAllEObjects();
721 		for (final EObject obj : allEObjects) {
722 			for (final EAttribute attribute : obj.eClass().getEAllAttributes()) {
723 				set.add(attribute.getName());
724 			}
725 		}
726 		final ArrayList<String> sorted = new ArrayList<>(set);
727 		sorted.sort(null);
728 
729 		// Select Attribute
730 		final ILabelProvider renderer = new LabelProvider() {
731 			@Override
732 			public String getText(Object element) {
733 				return String.valueOf(element);
734 			}
735 		};
736 		final TitleAreaFilterDialogWithEmptyOptions dlg = new TitleAreaFilterDialogWithEmptyOptions(
737 				context.get(Shell.class), renderer) {
738 			@Override
739 			protected Control createContents(Composite parent) {
740 				final Control ret = super.createContents(parent);
741 				setMessage(Messages.ListTab_selectAnAttribute);
742 				try {
743 					setTitleImage(imageCache.create(Plugin.ID, "/icons/full/wizban/attribute_wiz.gif")); //$NON-NLS-1$
744 				} catch (final Exception e) {
745 					e.printStackTrace();
746 				}
747 				setTitle(title);
748 				setElements(sorted.toArray(new String[0]));
749 				return ret;
750 			}
751 		};
752 		return dlg;
753 	}
754 
createElementTypePicker(final String title)755 	private TitleAreaFilterDialog createElementTypePicker(final String title) {
756 		// Get Attribute Names
757 		final HashSet<String> set = new HashSet<>();
758 		final Collection<EObject> allEObjects = getAllEObjects();
759 		for (final EObject obj : allEObjects) {
760 			set.add(obj.eClass().getName());
761 		}
762 
763 		final ArrayList<String> sorted = new ArrayList<>(set);
764 		sorted.sort(null);
765 
766 		final ILabelProvider renderer = new LabelProvider() {
767 			@Override
768 			public String getText(Object element) {
769 				return String.valueOf(element);
770 			}
771 		};
772 		final TitleAreaFilterDialog dlg = new TitleAreaFilterDialog(context.get(Shell.class), renderer) {
773 
774 			@Override
775 			protected Control createContents(Composite parent) {
776 				final Control ret = super.createContents(parent);
777 				setMessage(Messages.ListTab_selectAType);
778 				setTitle(title);
779 				setElements(sorted.toArray(new String[0]));
780 				return ret;
781 			}
782 		};
783 		return dlg;
784 	}
785 
786 	/**
787 	 * Adds a column if it does not already exist
788 	 *
789 	 * @param attName
790 	 * @return The existing or newly created column
791 	 */
addColumn(String attName)792 	private EAttributeTableViewerColumn addColumn(String attName) {
793 		EAttributeTableViewerColumn colName = defaultColumns.get(attName);
794 		if (colName == null) {
795 			colName = optionalColumns.get(attName);
796 			if (colName == null) {
797 				colName = new EAttributeTableViewerColumn_Markable(tvResults, attName, attName, context);
798 				optionalColumns.put(attName, colName);
799 				colName.getTableViewerColumn().getColumn().setMoveable(true);
800 				makeSortable(colName.getTableViewerColumn().getColumn(), new AttributeColumnLabelSorter(colName
801 						.getTableViewerColumn().getColumn(), attName));
802 				tvResults.refresh();
803 			}
804 		}
805 		return colName;
806 	}
807 
808 	static private class AttributeColumnLabelSorter extends TableViewerUtil.ColumnLabelSorter {
809 
810 		private final String attName;
811 
AttributeColumnLabelSorter(TableColumn col, String attName)812 		AttributeColumnLabelSorter(TableColumn col, String attName) {
813 			super(col);
814 			this.attName = attName;
815 		}
816 
817 		@Override
compare(Viewer viewer, Object e1, Object e2)818 		public int compare(Viewer viewer, Object e1, Object e2) {
819 			// if either is boolean, use boolean value, otherwise use text value
820 			final ATT_TYPE e1Type = EAttributeEditingSupport.getAttributeType(e1, attName);
821 			final ATT_TYPE e2Type = EAttributeEditingSupport.getAttributeType(e2, attName);
822 			if (e1Type == ATT_TYPE.BOOLEAN || e2Type == ATT_TYPE.BOOLEAN) {
823 				final Boolean b1 = (Boolean) EmfUtil.getAttributeValue((EObject) e1, attName);
824 				final Boolean b2 = (Boolean) EmfUtil.getAttributeValue((EObject) e2, attName);
825 				if (b1 == null && b2 != null) {
826 					return -2;
827 				} else if (b2 == null && b1 != null) {
828 					return 2;
829 				} else {
830 					return b1.compareTo(b2);
831 				}
832 			}
833 			return super.compare(viewer, e1, e2);
834 		}
835 	}
836 
837 	private class EAttributeTableViewerColumn_Markable extends EAttributeTableViewerColumn {
EAttributeTableViewerColumn_Markable(TableViewer tvResults, String label, String attName, IEclipseContext context)838 		public EAttributeTableViewerColumn_Markable(TableViewer tvResults, String label, String attName,
839 				IEclipseContext context) {
840 			super(tvResults, label, attName, context);
841 		}
842 
843 		@Override
getBackground(Object element)844 		public Color getBackground(Object element) {
845 			Color ret;
846 			if (isHighlighted(element)) {
847 				return ret = tvResults.getTable().getDisplay().getSystemColor(SWT.COLOR_YELLOW);
848 			}
849 			ret = super.getBackground(element);
850 			return ret;
851 		}
852 	}
853 
854 	private class ColumnLabelProvider_Markable extends ColumnLabelProvider {
855 		@Override
getBackground(Object element)856 		public Color getBackground(Object element) {
857 			Color ret;
858 			if (isHighlighted(element)) {
859 				ret = tvResults.getTable().getDisplay().getSystemColor(SWT.COLOR_YELLOW);
860 			} else {
861 				ret = super.getBackground(element);
862 			}
863 			return ret;
864 		}
865 	}
866 
867 	@Override
getEditingDomain()868 	public EditingDomain getEditingDomain() {
869 		return modelResource.getEditingDomain();
870 	}
871 
isHighlighted(Object element)872 	public boolean isHighlighted(Object element) {
873 		return highlightedItems != null && highlightedItems.contains(element);
874 	}
875 
filterByItem(String name)876 	private void filterByItem(String name) {
877 		filterByItemName = name;
878 		filterByAttrName = null;
879 		filterByAttrEmptyOption = null;
880 		mapId_Object.clear();
881 		final ArrayList<EObject> filtered = new ArrayList<>();
882 		for (final EObject object : getAllEObjects()) {
883 			if (object.eClass().getName().equals(filterByItemName)) {
884 				filtered.add(object);
885 				// filter.setText(Messages.ListTab_7 +
886 				// attFilter);
887 
888 			}
889 
890 			final ViewerFilter viewerFilter = new ViewerFilter() {
891 
892 				@Override
893 				public boolean select(Viewer viewer, Object parentElement, Object element) {
894 					return filtered.contains(element);
895 				}
896 
897 			};
898 			tvResults.setFilters(viewerFilter);
899 			filterByItem.setText(Messages.ListTab_filterByItem + ELIPSIS + "(" + filterByItemName + ")"); //$NON-NLS-1$//$NON-NLS-2$
900 			filterByAttribute.setText(Messages.ListTab_filterByAttribute + ELIPSIS);
901 		}
902 	}
903 
filterByAttribute(String name, final EmptyFilterOption emptyOption)904 	private void filterByAttribute(String name, final EmptyFilterOption emptyOption) {
905 		filterByAttrName = name;
906 		filterByAttrEmptyOption = emptyOption;
907 		filterByItemName = null;
908 		mapId_Object.clear();
909 		final ArrayList<EObject> filtered = new ArrayList<>();
910 		for (final EObject object : getAllEObjects()) {
911 			if (EmfUtil.getAttribute(object, filterByAttrName) != null) {
912 				filtered.add(object);
913 				final ViewerFilter viewerFilter = new ViewerFilter() {
914 
915 					@Override
916 					public boolean select(Viewer viewer, Object parentElement, Object element) {
917 						// if filtering on attribute, always
918 						// reject if not defined for model
919 						// element
920 						if (EmfUtil.getAttribute((EObject) element, filterByAttrName) == null) {
921 							return false;
922 						}
923 						switch (emptyOption) {
924 						case EXCLUDE:
925 							if (E.isEmpty(EmfUtil.getAttributeValue((EObject) element, filterByAttrName))) {
926 								return false;
927 							}
928 							return filtered.contains(element);
929 						case ONLY:
930 							if (E.notEmpty(EmfUtil.getAttributeValue((EObject) element, filterByAttrName))) {
931 								return false;
932 							}
933 							return true;
934 						default:
935 						case INCLUDE:
936 							if (E.isEmpty(EmfUtil.getAttributeValue((EObject) element, filterByAttrName))) {
937 								return true;
938 							}
939 							return filtered.contains(element);
940 						}
941 					}
942 				};
943 				tvResults.setFilters(viewerFilter);
944 				filterByAttribute.setText(Messages.ListTab_filterByAttribute + ELIPSIS + "(" + filterByAttrName + ")"); //$NON-NLS-1$//$NON-NLS-2$
945 				filterByItem.setText(Messages.ListTab_filterByItem + ELIPSIS);
946 
947 			}
948 		}
949 		final TableViewerColumn viewerColumn = addColumn(filterByAttrName).getTableViewerColumn();
950 		viewerColumn.getColumn().pack();
951 	}
952 
953 	@Override
autosizeContent()954 	public void autosizeContent() {
955 		for (final TableColumn col : tvResults.getTable().getColumns()) {
956 			col.pack();
957 			if (col.getWidth() < 10) {
958 				col.setWidth(10);
959 			}
960 		}
961 	}
962 
963 	@Override
resetToDefault()964 	public void resetToDefault() {
965 		for (final EAttributeTableViewerColumn col : optionalColumns.values()) {
966 			col.dispose();
967 		}
968 		optionalColumns.clear();
969 
970 		TableViewerUtil.resetColumnOrder(tvResults);
971 		TableViewerUtil.packAllColumns(tvResults);
972 	}
973 
974 }
975