1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation 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  *     IBM Corporation - initial API and implementation
13  *     Sebastian Davids - bug 128526, bug 128529
14  *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 430988, 457434, 472654
15  *     Simon Scholz <simon.scholz@vogella.com> - Bug 455527
16  *******************************************************************************/
17 package org.eclipse.ui.internal.dialogs;
18 
19 import java.util.ArrayList;
20 import java.util.List;
21 import org.eclipse.e4.core.contexts.IEclipseContext;
22 import org.eclipse.e4.ui.internal.workbench.swt.WorkbenchSWTActivator;
23 import org.eclipse.e4.ui.model.LocalizationHelper;
24 import org.eclipse.e4.ui.model.application.MApplication;
25 import org.eclipse.e4.ui.model.application.descriptor.basic.MPartDescriptor;
26 import org.eclipse.e4.ui.model.application.ui.basic.MWindow;
27 import org.eclipse.e4.ui.services.help.EHelpService;
28 import org.eclipse.e4.ui.workbench.modeling.EModelService;
29 import org.eclipse.e4.ui.workbench.modeling.EPartService;
30 import org.eclipse.jface.dialogs.Dialog;
31 import org.eclipse.jface.dialogs.IDialogConstants;
32 import org.eclipse.jface.dialogs.IDialogLabelKeys;
33 import org.eclipse.jface.dialogs.IDialogSettings;
34 import org.eclipse.jface.dialogs.PopupDialog;
35 import org.eclipse.jface.resource.JFaceResources;
36 import org.eclipse.jface.viewers.DoubleClickEvent;
37 import org.eclipse.jface.viewers.IDoubleClickListener;
38 import org.eclipse.jface.viewers.ISelectionChangedListener;
39 import org.eclipse.jface.viewers.IStructuredSelection;
40 import org.eclipse.jface.viewers.ITreeContentProvider;
41 import org.eclipse.jface.viewers.ITreeSelection;
42 import org.eclipse.jface.viewers.SelectionChangedEvent;
43 import org.eclipse.jface.viewers.StructuredSelection;
44 import org.eclipse.jface.viewers.TreeViewer;
45 import org.eclipse.swt.SWT;
46 import org.eclipse.swt.events.FocusAdapter;
47 import org.eclipse.swt.events.FocusEvent;
48 import org.eclipse.swt.events.KeyAdapter;
49 import org.eclipse.swt.events.KeyEvent;
50 import org.eclipse.swt.graphics.Color;
51 import org.eclipse.swt.graphics.Point;
52 import org.eclipse.swt.graphics.RGB;
53 import org.eclipse.swt.layout.GridData;
54 import org.eclipse.swt.widgets.Button;
55 import org.eclipse.swt.widgets.Composite;
56 import org.eclipse.swt.widgets.Control;
57 import org.eclipse.swt.widgets.Display;
58 import org.eclipse.swt.widgets.Label;
59 import org.eclipse.swt.widgets.Shell;
60 import org.eclipse.swt.widgets.Text;
61 import org.eclipse.ui.dialogs.FilteredTree;
62 import org.eclipse.ui.dialogs.PatternFilter;
63 import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
64 import org.eclipse.ui.internal.WorkbenchMessages;
65 
66 public class ShowViewDialog extends Dialog implements ISelectionChangedListener, IDoubleClickListener {
67 
68 	private static final String DIALOG_SETTING_SECTION_NAME = "ShowViewDialog"; //$NON-NLS-1$
69 
70 	private static final int LIST_HEIGHT = 300;
71 
72 	private static final int LIST_WIDTH = 250;
73 
74 	private static final String STORE_EXPANDED_CATEGORIES_ID = DIALOG_SETTING_SECTION_NAME
75 			+ ".STORE_EXPANDED_CATEGORIES_ID"; //$NON-NLS-1$
76 
77 	private static final String STORE_SELECTED_VIEW_ID = DIALOG_SETTING_SECTION_NAME + ".STORE_SELECTED_VIEW_ID"; //$NON-NLS-1$
78 
79 	private FilteredTree filteredTree;
80 
81 	private Color dimmedForeground;
82 
83 	private Button okButton;
84 
85 	private MApplication application;
86 
87 	private MPartDescriptor[] viewDescs = new MPartDescriptor[0];
88 
89 	private Label descriptionHint;
90 
91 	private IEclipseContext context;
92 
93 	private EModelService modelService;
94 
95 	private MWindow window;
96 
97 	private EPartService partService;
98 
99 	/**
100 	 * Constructs a new ShowViewDialog.
101 	 *
102 	 * @param shell
103 	 * @param application
104 	 * @param window
105 	 * @param modelService
106 	 * @param partService
107 	 * @param context
108 	 *
109 	 */
ShowViewDialog(Shell shell, MApplication application, MWindow window, EModelService modelService, EPartService partService, IEclipseContext context)110 	public ShowViewDialog(Shell shell, MApplication application, MWindow window, EModelService modelService,
111 			EPartService partService, IEclipseContext context) {
112 		super(shell);
113 		this.application = application;
114 		this.window = window;
115 		this.modelService = modelService;
116 		this.partService = partService;
117 		this.context = context;
118 	}
119 
120 	/**
121 	 * This method is called if a button has been pressed.
122 	 */
123 	@Override
buttonPressed(int buttonId)124 	protected void buttonPressed(int buttonId) {
125 		if (buttonId == IDialogConstants.OK_ID) {
126 			saveWidgetValues();
127 		}
128 		super.buttonPressed(buttonId);
129 	}
130 
131 	/**
132 	 * Notifies that the cancel button of this dialog has been pressed.
133 	 */
134 	@Override
cancelPressed()135 	protected void cancelPressed() {
136 		viewDescs = new MPartDescriptor[0];
137 		super.cancelPressed();
138 	}
139 
140 	@Override
configureShell(Shell shell)141 	protected void configureShell(Shell shell) {
142 		super.configureShell(shell);
143 		shell.setText(WorkbenchMessages.ShowView_shellTitle);
144 		EHelpService helpService = context.get(EHelpService.class);
145 		if (helpService != null) {
146 			helpService.setHelp(shell, IWorkbenchHelpContextIds.SHOW_VIEW_DIALOG);
147 		}
148 	}
149 
150 	@Override
createButtonsForButtonBar(Composite parent)151 	protected void createButtonsForButtonBar(Composite parent) {
152 		okButton = createButton(parent, IDialogConstants.OK_ID, WorkbenchMessages.ShowView_open_button_label, true);
153 		createButton(parent, IDialogConstants.CANCEL_ID, JFaceResources.getString(IDialogLabelKeys.CANCEL_LABEL_KEY),
154 				false);
155 		updateButtons();
156 	}
157 
158 	@Override
createDialogArea(Composite parent)159 	protected Control createDialogArea(Composite parent) {
160 		// Run super.
161 		Composite composite = (Composite) super.createDialogArea(parent);
162 		composite.setFont(parent.getFont());
163 
164 		createFilteredTreeViewer(composite);
165 
166 		layoutTopControl(filteredTree);
167 
168 		// Use F2... label
169 		descriptionHint = new Label(composite, SWT.WRAP);
170 		descriptionHint.setText(WorkbenchMessages.ShowView_selectViewHelp);
171 		descriptionHint.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
172 		descriptionHint.setVisible(false);
173 
174 		// Restore the last state
175 		restoreWidgetValues();
176 
177 		applyDialogFont(composite);
178 
179 		// Return results.
180 		return composite;
181 	}
182 
183 	/**
184 	 * Blends c1 and c2 based in the provided ratio.
185 	 *
186 	 * @param c1    first color
187 	 * @param c2    second color
188 	 * @param ratio percentage of the first color in the blend (0-100)
189 	 * @return the RGB value of the blended color
190 	 *
191 	 *         copied from FormColors.java
192 	 */
blend(RGB c1, RGB c2, int ratio)193 	private static RGB blend(RGB c1, RGB c2, int ratio) {
194 		int r = blend(c1.red, c2.red, ratio);
195 		int g = blend(c1.green, c2.green, ratio);
196 		int b = blend(c1.blue, c2.blue, ratio);
197 		return new RGB(r, g, b);
198 	}
199 
blend(int v1, int v2, int ratio)200 	private static int blend(int v1, int v2, int ratio) {
201 		int b = (ratio * v1 + (100 - ratio) * v2) / 100;
202 		return Math.min(255, b);
203 	}
204 
205 	/**
206 	 * Create a new filtered tree viewer in the parent.
207 	 *
208 	 * @param parent the parent <code>Composite</code>.
209 	 */
createFilteredTreeViewer(Composite parent)210 	private void createFilteredTreeViewer(Composite parent) {
211 		PatternFilter filter = new ViewPatternFilter();
212 		int styleBits = SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER;
213 		filteredTree = new FilteredTree(parent, styleBits, filter, true, true);
214 		filteredTree.setQuickSelectionMode(true);
215 		filteredTree.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
216 
217 		TreeViewer treeViewer = filteredTree.getViewer();
218 		Control treeControl = treeViewer.getControl();
219 		RGB dimmedRGB = blend(treeControl.getForeground().getRGB(), treeControl.getBackground().getRGB(), 60);
220 		dimmedForeground = new Color(treeControl.getDisplay(), dimmedRGB);
221 		treeControl.addDisposeListener(e -> dimmedForeground.dispose());
222 
223 		treeViewer
224 				.setLabelProvider(new ViewLabelProvider(context, modelService, partService, window, dimmedForeground));
225 		treeViewer.setContentProvider(new ViewContentProvider(application));
226 		treeViewer.setComparator(new ViewComparator());
227 		treeViewer.setInput(application);
228 		treeViewer.addSelectionChangedListener(this);
229 		treeViewer.addDoubleClickListener(this);
230 		treeViewer.getControl().addKeyListener(new KeyAdapter() {
231 			@Override
232 			public void keyPressed(KeyEvent e) {
233 				handleTreeViewerKeyPressed(e);
234 			}
235 		});
236 
237 		// if the tree has only one or zero views, disable the filter text
238 		// control
239 		if (hasAtMostOneView(filteredTree.getViewer())) {
240 			Text filterText = filteredTree.getFilterControl();
241 			if (filterText != null) {
242 				filterText.setEnabled(false);
243 			}
244 		}
245 	}
246 
247 	/**
248 	 * Return whether or not there are less than two views in the list.
249 	 *
250 	 * @param tree
251 	 * @return <code>true</code> if there are less than two views in the list.
252 	 */
hasAtMostOneView(TreeViewer tree)253 	private boolean hasAtMostOneView(TreeViewer tree) {
254 		ITreeContentProvider contentProvider = (ITreeContentProvider) tree.getContentProvider();
255 		Object[] children = contentProvider.getElements(tree.getInput());
256 
257 		if (children.length <= 1) {
258 			if (children.length == 0) {
259 				return true;
260 			}
261 			return !contentProvider.hasChildren(children[0]);
262 		}
263 		return false;
264 	}
265 
266 	@Override
doubleClick(DoubleClickEvent event)267 	public void doubleClick(DoubleClickEvent event) {
268 		IStructuredSelection s = (IStructuredSelection) event.getSelection();
269 		Object element = s.getFirstElement();
270 		if (filteredTree.getViewer().isExpandable(element)) {
271 			filteredTree.getViewer().setExpandedState(element, !filteredTree.getViewer().getExpandedState(element));
272 		} else if (viewDescs.length > 0) {
273 			saveWidgetValues();
274 			setReturnCode(OK);
275 			close();
276 		}
277 	}
278 
279 	/**
280 	 * Return the dialog store to cache values into
281 	 */
getDialogSettings()282 	protected IDialogSettings getDialogSettings() {
283 		IDialogSettings workbenchSettings = WorkbenchSWTActivator.getDefault().getDialogSettings();
284 		IDialogSettings section = workbenchSettings.getSection(DIALOG_SETTING_SECTION_NAME);
285 		if (section == null) {
286 			section = workbenchSettings.addNewSection(DIALOG_SETTING_SECTION_NAME);
287 		}
288 		return section;
289 	}
290 
291 	/**
292 	 * Returns the descriptors for the selected views.
293 	 *
294 	 * @return the descriptors for the selected views
295 	 */
getSelection()296 	public MPartDescriptor[] getSelection() {
297 		return viewDescs;
298 	}
299 
300 	/**
301 	 * Layout the top control.
302 	 *
303 	 * @param control the control.
304 	 */
layoutTopControl(Control control)305 	private void layoutTopControl(Control control) {
306 		GridData spec = new GridData(GridData.FILL_BOTH);
307 		spec.widthHint = LIST_WIDTH;
308 		spec.heightHint = LIST_HEIGHT;
309 		control.setLayoutData(spec);
310 	}
311 
312 	/**
313 	 * Use the dialog store to restore widget values to the values that they held
314 	 * last time this dialog was used to completion.
315 	 */
restoreWidgetValues()316 	protected void restoreWidgetValues() {
317 		IDialogSettings settings = getDialogSettings();
318 
319 		String[] expandedCategoryIds = settings.getArray(STORE_EXPANDED_CATEGORIES_ID);
320 		if (expandedCategoryIds == null)
321 			return;
322 
323 		if (expandedCategoryIds.length > 0)
324 			filteredTree.getViewer().setExpandedElements((Object[]) expandedCategoryIds);
325 
326 		String selectedPartId = settings.get(STORE_SELECTED_VIEW_ID);
327 		if (selectedPartId != null) {
328 			List<MPartDescriptor> descriptors = application.getDescriptors();
329 			for (MPartDescriptor descriptor : descriptors) {
330 				if (selectedPartId.equals(descriptor.getElementId())) {
331 					filteredTree.getViewer().setSelection(new StructuredSelection(descriptor), true);
332 					break;
333 				}
334 			}
335 		}
336 	}
337 
338 	/**
339 	 * Since OK was pressed, write widget values to the dialog store so that they
340 	 * will persist into the next invocation of this dialog
341 	 */
saveWidgetValues()342 	protected void saveWidgetValues() {
343 		IDialogSettings settings = getDialogSettings();
344 
345 		// Collect the ids of the all expanded categories
346 		Object[] expandedElements = filteredTree.getViewer().getExpandedElements();
347 		String[] expandedCategoryIds = new String[expandedElements.length];
348 		for (int i = 0; i < expandedElements.length; ++i) {
349 			if (expandedElements[i] instanceof MPartDescriptor)
350 				expandedCategoryIds[i] = ((MPartDescriptor) expandedElements[i]).getElementId();
351 			else
352 				expandedCategoryIds[i] = expandedElements[i].toString();
353 		}
354 
355 		// Save them for next time.
356 		settings.put(STORE_EXPANDED_CATEGORIES_ID, expandedCategoryIds);
357 
358 		String selectedViewId = ""; //$NON-NLS-1$
359 		if (viewDescs.length > 0) {
360 			// in the case of a multi-selection, it's probably less confusing
361 			// to store just the first rather than the whole multi-selection
362 			selectedViewId = viewDescs[0].getElementId();
363 		}
364 		settings.put(STORE_SELECTED_VIEW_ID, selectedViewId);
365 	}
366 
367 	/**
368 	 * Notifies that the selection has changed.
369 	 *
370 	 * @param event event object describing the change
371 	 */
372 	@Override
selectionChanged(SelectionChangedEvent event)373 	public void selectionChanged(SelectionChangedEvent event) {
374 		updateSelection(event);
375 		updateButtons();
376 		String tooltip = ""; //$NON-NLS-1$
377 		if (viewDescs.length > 0) {
378 			tooltip = viewDescs[0].getTooltip();
379 			tooltip = LocalizationHelper.getLocalized(tooltip, viewDescs[0], context);
380 		}
381 
382 		boolean hasTooltip = tooltip != null && tooltip.length() > 0;
383 		descriptionHint.setVisible(viewDescs.length == 1 && hasTooltip);
384 	}
385 
386 	/**
387 	 * Update the button enablement state.
388 	 */
updateButtons()389 	protected void updateButtons() {
390 		if (okButton != null) {
391 			okButton.setEnabled(getSelection().length > 0);
392 		}
393 	}
394 
395 	/**
396 	 * Update the selection object.
397 	 */
updateSelection(SelectionChangedEvent event)398 	protected void updateSelection(SelectionChangedEvent event) {
399 		ArrayList<MPartDescriptor> descs = new ArrayList<>();
400 		for (Object o : event.getStructuredSelection()) {
401 			if (o instanceof MPartDescriptor) {
402 				descs.add((MPartDescriptor) o);
403 			}
404 		}
405 
406 		viewDescs = new MPartDescriptor[descs.size()];
407 		descs.toArray(viewDescs);
408 	}
409 
410 	@Override
getDialogBoundsSettings()411 	protected IDialogSettings getDialogBoundsSettings() {
412 		return getDialogSettings();
413 	}
414 
handleTreeViewerKeyPressed(KeyEvent event)415 	void handleTreeViewerKeyPressed(KeyEvent event) {
416 		// popup the description for the selected view
417 		if (descriptionHint.isVisible() && event.keyCode == SWT.F2 && event.stateMask == 0) {
418 			ITreeSelection selection = filteredTree.getViewer().getStructuredSelection();
419 			// only show description if one view is selected
420 			if (selection.size() == 1) {
421 				Object o = selection.getFirstElement();
422 				if (o instanceof MPartDescriptor) {
423 					String description = ((MPartDescriptor) o).getTooltip();
424 					description = LocalizationHelper.getLocalized(description, (MPartDescriptor) o, context);
425 					if (description != null && description.length() == 0)
426 						description = WorkbenchMessages.ShowView_noDesc;
427 					popUp(description);
428 				}
429 			}
430 		}
431 	}
432 
popUp(final String description)433 	private void popUp(final String description) {
434 		new PopupDialog(filteredTree.getShell(), PopupDialog.HOVER_SHELLSTYLE, true, false, false, false, false, null,
435 				null) {
436 			private static final int CURSOR_SIZE = 15;
437 
438 			@Override
439 			protected Point getInitialLocation(Point initialSize) {
440 				// show popup relative to cursor
441 				Display display = getShell().getDisplay();
442 				Point location = display.getCursorLocation();
443 				location.x += CURSOR_SIZE;
444 				location.y += CURSOR_SIZE;
445 				return location;
446 			}
447 
448 			@Override
449 			protected Control createDialogArea(Composite parent) {
450 				Label label = new Label(parent, SWT.WRAP);
451 				label.setText(description);
452 				label.addFocusListener(new FocusAdapter() {
453 					@Override
454 					public void focusLost(FocusEvent event) {
455 						close();
456 					}
457 				});
458 				// Use the compact margins employed by PopupDialog.
459 				GridData gd = new GridData(GridData.BEGINNING | GridData.FILL_BOTH);
460 				gd.horizontalIndent = PopupDialog.POPUP_HORIZONTALSPACING;
461 				gd.verticalIndent = PopupDialog.POPUP_VERTICALSPACING;
462 				label.setLayoutData(gd);
463 				return label;
464 			}
465 		}.open();
466 	}
467 
468 	@Override
isResizable()469 	protected boolean isResizable() {
470 		return true;
471 	}
472 }
473