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