1 /*
   2  * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2019, Red Hat Inc. All rights reserved.
   4  *
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * The contents of this file are subject to the terms of either the Universal Permissive License
   8  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   9  *
  10  * or the following license:
  11  *
  12  * Redistribution and use in source and binary forms, with or without modification, are permitted
  13  * provided that the following conditions are met:
  14  *
  15  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  16  * and the following disclaimer.
  17  *
  18  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  19  * conditions and the following disclaimer in the documentation and/or other materials provided with
  20  * the distribution.
  21  *
  22  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  23  * endorse or promote products derived from this software without specific prior written permission.
  24  *
  25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  27  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  28  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  30  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  31  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  32  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33  */
  34 package org.openjdk.jmc.flightrecorder.ui.common;
  35 
  36 import java.util.stream.Collectors;
  37 
  38 import org.eclipse.swt.SWT;
  39 import org.eclipse.swt.events.ShellAdapter;
  40 import org.eclipse.swt.events.ShellEvent;
  41 import org.eclipse.swt.graphics.Point;
  42 import org.eclipse.swt.graphics.Rectangle;
  43 import org.eclipse.swt.layout.GridData;
  44 import org.eclipse.swt.layout.GridLayout;
  45 import org.eclipse.swt.widgets.Button;
  46 import org.eclipse.swt.widgets.Composite;
  47 import org.eclipse.swt.widgets.Event;
  48 import org.eclipse.swt.widgets.Listener;
  49 import org.eclipse.swt.widgets.Shell;
  50 import org.openjdk.jmc.common.item.ItemFilters;
  51 import org.openjdk.jmc.flightrecorder.ui.common.LaneEditor.LaneDefinition;
  52 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
  53 import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
  54 
  55 public class DropdownLaneFilter extends Composite {
  56         private static final int EXTRA_SHELL_WIDTH = 100;
  57         private static final int SHELL_HEIGHT = 400;
  58         private Button dropdownButton;
  59         private GridLayout layout;
  60         private MCContextMenuManager mm;
  61         private Shell shell;
  62         private ShellAdapter shellDisposeAdapter;
  63         private ThreadGraphLanes lanes;
  64         private TypeFilterBuilder filterEditor;
  65 
  66         public DropdownLaneFilter(Composite parent, ThreadGraphLanes lanes, MCContextMenuManager mm) {
  67                 super(parent, SWT.NO_BACKGROUND);
  68                 this.lanes = lanes;
  69                 this.mm = mm;
  70                 this.layout = createGridLayout();
  71                 this.shellDisposeAdapter = createDisposeAdapter();
  72                 setLayout(layout);
  73                 dropdownButton = new Button(this, SWT.TOGGLE);
  74                 dropdownButton.setLayoutData(new GridData(GridData.FILL_BOTH));
  75                 dropdownButton.setText(Messages.DropdownLaneFilter_THREAD_STATE_SELECTION);
  76                 dropdownButton.addListener(SWT.MouseDown, new Listener() {
  77                         @Override
  78                         public void handleEvent(Event event) {
  79                                 if (!dropdownButton.getSelection()) {
  80                                         displayDropdown();
  81                                 } else {
  82                                         disposeDropdown();
  83                                 }
  84                         }
  85                 });
  86         }
  87 
  88         /**
  89          * Creates a new shell which is positioned underneath the dropdown button.
  90          * This new shell creates the appearance of a dropdown component, and it's
  91          * contents will be the TypeFilterBuilder as found in the Edit Thread Lanes
  92          * dialog.
  93          */
  94         private void displayDropdown() {
  95                 Point p = dropdownButton.getParent().toDisplay(dropdownButton.getLocation());
  96                 Point size = dropdownButton.getSize();
  97                 Rectangle shellRect = new Rectangle(p.x, p.y + size.y, size.x, 0);
  98 
  99                 shell = new Shell(DropdownLaneFilter.this.getShell(), SWT.BORDER);
 100                 shell.setLayout(this.layout);
 101                 shell.setSize(shellRect.width + EXTRA_SHELL_WIDTH, SHELL_HEIGHT);
 102                 shell.setLocation(shellRect.x, shellRect.y);
 103                 shell.addShellListener(this.shellDisposeAdapter);
 104 
 105                 filterEditor = new TypeFilterBuilder(shell, this::onTypeFilterChange);
 106                 filterEditor.setInput(lanes.getTypeTree());
 107                 filterEditor.selectTypes(lanes.getEnabledLanes());
 108                 filterEditor.getControl().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
 109                 shell.open();
 110         }
 111 
 112         private void disposeDropdown() {
 113                 if (shell != null && !shell.isDisposed()) {
 114                         shell.dispose();
 115                 }
 116         }
 117 
 118         private GridLayout createGridLayout() {
 119                 GridLayout layout = new GridLayout();
 120                 layout.marginHeight = 0;
 121                 layout.marginWidth = 0;
 122                 return layout;
 123         }
 124 
 125         /**
 126          * Prepares the ShellAdapter which will be used when the dropdown
 127          * is to be disposed of.
 128          * @return ShellAdapter to be used by the dropdown shell
 129          */
 130         private ShellAdapter createDisposeAdapter() {
 131                 return new ShellAdapter() {
 132                         public void shellDeactivated(ShellEvent event) {
 133                                 if (dropdownButton.getSelection()) {
 134                                         disposeDropdown();
 135                                         dropdownButton.setSelection(false);
 136                                 }
 137                         }
 138                 };
 139         }
 140 
 141         /**
 142          * Creates a new filter, Quick Filter, to be used by the dropdown lane filter.
 143          * Sets the quick filter to be active, and update the context menu.
 144          */
 145         private void onTypeFilterChange() {
 146                 LaneDefinition quickLaneDef = new LaneDefinition(Messages.DropdownLaneFilter_QUICK_FILTER, true,
 147                                         ItemFilters.type(filterEditor.getCheckedTypeIds().collect(Collectors.toSet())), false);
 148                 lanes.useDropdownFilter(quickLaneDef);
 149                 lanes.updateContextMenu(mm, false);
 150         }
 151 }