| /******************************************************************************* |
| * Copyright (c) 2018 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| package org.eclipse.tracecompass.internal.tmf.ui.views.timegraph; |
| |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| |
| import org.eclipse.jdt.annotation.NonNull; |
| import org.eclipse.jdt.annotation.Nullable; |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CLabel; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.KeyEvent; |
| import org.eclipse.swt.events.KeyListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.layout.RowLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.tracecompass.common.core.NonNullUtils; |
| import org.eclipse.tracecompass.tmf.ui.views.timegraph.AbstractTimeGraphView; |
| import org.eclipse.tracecompass.tmf.ui.views.timegraph.Messages; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphColorScheme; |
| import org.eclipse.tracecompass.tmf.ui.widgets.timegraph.widgets.TimeGraphControl; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.PlatformUI; |
| |
| /** |
| * This class implements a time event filter dialog. |
| * |
| * @author Jean-Christian Kouame |
| */ |
| public class TimeEventFilterDialog extends Dialog { |
| |
| /** |
| * The empty string |
| */ |
| public static final String EMPTY_STRING = ""; //$NON-NLS-1$ |
| |
| private static final int FIND_X_WIDTH_HINT = 200; |
| |
| private static final int MAX_FILTER_REGEX_SIZE = 4; |
| |
| private static final int Y_OFFSET = -15; |
| |
| /** The time event filter regex */ |
| private String fRegex; |
| |
| /** The time event filter regex */ |
| private Set<@NonNull String> fFilterRegexes = new LinkedHashSet<>(MAX_FILTER_REGEX_SIZE); |
| |
| private final TimeGraphControl fControl; |
| |
| private final AbstractTimeGraphView fView; |
| |
| private final ControlListener fControlListener = new ControlMovedListener(); |
| |
| /** |
| * Constructor |
| * |
| * @param parentShell |
| * The parent shell of the dialog |
| * @param view |
| * The timegraph this dialog belongs to |
| * @param control The timegraph control |
| */ |
| public TimeEventFilterDialog(Shell parentShell, AbstractTimeGraphView view, TimeGraphControl control) { |
| super(parentShell); |
| fView = view; |
| fControl = control; |
| } |
| |
| @Override |
| protected Control createDialogArea(Composite parent) { |
| getShell().setText(Messages.AbstractTimeGraphView_TimeEventFilterDialogTitle); |
| |
| Composite container = new Composite(parent, SWT.NONE); |
| GridLayout layout = new GridLayout(3, false); |
| layout.horizontalSpacing = 0; |
| layout.marginHeight = 0; |
| layout.marginWidth = 0; |
| container.setLayout(layout); |
| |
| Composite labels = createCLabelsArea(container); |
| createFilterTextArea(parent, container, labels); |
| createCloseButton(container); |
| |
| // support close on escape button |
| getShell().addListener(SWT.Traverse, e -> { |
| if (e.detail == SWT.TRAVERSE_ESCAPE) { |
| clearFilter(); |
| } |
| }); |
| |
| for (String label : fFilterRegexes) { |
| createCLabels(parent, labels, label); |
| } |
| |
| fControl.addControlListener(fControlListener); |
| fControl.getShell().addControlListener(fControlListener); |
| |
| return parent; |
| } |
| |
| private static Composite createCLabelsArea(Composite container) { |
| Composite labels = new Composite(container, SWT.NONE); |
| GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false); |
| labels.setLayoutData(gd); |
| RowLayout rl = new RowLayout(SWT.HORIZONTAL); |
| rl.marginTop = 0; |
| rl.marginBottom = 0; |
| rl.marginLeft = 3; |
| rl.marginRight = 0; |
| labels.setLayout(rl); |
| return labels; |
| } |
| |
| private Text createFilterTextArea(Composite parent, Composite container, Composite labels) { |
| Text filterText = new Text(container, SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH); |
| GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false); |
| gridData.widthHint = FIND_X_WIDTH_HINT; |
| filterText.setLayoutData(gridData); |
| if (fRegex != null) { |
| filterText.setText(fRegex); |
| } |
| filterText.addModifyListener(e -> { |
| fRegex = filterText.getText(); |
| fView.restartZoomThread(); |
| }); |
| |
| filterText.addKeyListener(new KeyListener() { |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| // Do nothing |
| } |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.character == SWT.CR) { |
| handleEnterPressed(parent, labels, filterText); |
| } |
| } |
| }); |
| return filterText; |
| } |
| |
| private void clearFilter() { |
| fRegex = EMPTY_STRING; |
| fFilterRegexes.clear(); |
| fView.restartZoomThread(); |
| } |
| |
| private void handleEnterPressed(Composite parent, Composite labels, Text filterText) { |
| String currentRegex = filterText.getText(); |
| if (currentRegex.isEmpty() || fFilterRegexes.size() == MAX_FILTER_REGEX_SIZE || fFilterRegexes.contains(currentRegex)) { |
| filterText.setBackground(fControl.getColorScheme().getColor(TimeGraphColorScheme.TOOL_BACKGROUND)); |
| return; |
| } |
| boolean added = fFilterRegexes.add(currentRegex); |
| if (added) { |
| filterText.setText(EMPTY_STRING); |
| fRegex = EMPTY_STRING; |
| |
| createCLabels(parent, labels, currentRegex); |
| } |
| } |
| |
| private Button createCloseButton(Composite composite) { |
| Button closeButton = new Button(composite, SWT.NONE); |
| closeButton.setToolTipText(Messages.TimeEventFilterDialog_CloseButton); |
| closeButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false)); |
| closeButton.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_DELETE)); |
| closeButton.addSelectionListener(new SelectionAdapter() { |
| |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| clearFilter(); |
| close(); |
| } |
| }); |
| return closeButton; |
| } |
| |
| private void createCLabels(Composite parent, Composite labels, String currentRegex) { |
| CLabel filter = new CLabel(labels, SWT.BORDER); |
| filter.setText(currentRegex); |
| filter.setImage(PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_TOOL_DELETE)); |
| filter.setBackground(fControl.getColorScheme().getColor(TimeGraphColorScheme.TOOL_BACKGROUND)); |
| filter.addMouseListener(new MouseAdapter() { |
| @Override |
| public void mouseDown(MouseEvent e2) { |
| deleteCLabel(parent, filter, e2); |
| } |
| }); |
| |
| parent.layout(); |
| Rectangle bounds = parent.getShell().getBounds(); |
| Point size = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| Rectangle trim = parent.getShell().computeTrim(0, 0, size.x, size.y); |
| parent.getShell().setBounds(bounds.x + bounds.width - trim.width, bounds.y + bounds.height - trim.height, trim.width, trim.height); |
| } |
| |
| private void deleteCLabel(Composite parent, CLabel cLabel, MouseEvent e) { |
| Rectangle imageBounds; |
| imageBounds = cLabel.getImage().getBounds(); |
| imageBounds.x += cLabel.getLeftMargin(); |
| imageBounds.y = (cLabel.getSize().y - imageBounds.height) / 2; |
| if (imageBounds.contains(e.x, e.y)) { |
| fFilterRegexes.removeIf(regex -> regex.equals(cLabel.getText())); |
| e.widget.dispose(); |
| parent.layout(); |
| Rectangle bounds = parent.getShell().getBounds(); |
| Point size = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| Rectangle trim = parent.getShell().computeTrim(0, 0, size.x, size.y); |
| int x = bounds.x + (bounds.width - trim.width); |
| parent.getShell().setSize(trim.width, trim.height); |
| Display.getDefault().asyncExec(() -> { |
| parent.getShell().setLocation(x, bounds.y); |
| }); |
| fView.restartZoomThread(); |
| } |
| } |
| |
| @Override |
| protected void setShellStyle(int newShellStyle) { |
| super.setShellStyle(SWT.NO_TRIM); |
| } |
| |
| @Override |
| protected void createButtonsForButtonBar(final Composite parent) { |
| GridLayout layout = (GridLayout) parent.getLayout(); |
| layout.marginHeight = 0; |
| } |
| |
| @Override |
| protected Point getInitialLocation(Point initialSize) { |
| Point p = getFilterLocation(); |
| if (p != null) { |
| return p; |
| } |
| return super.getInitialLocation(initialSize); |
| } |
| |
| @Override |
| public boolean close() { |
| if (!fControl.isDisposed()) { |
| fControl.removeControlListener(fControlListener); |
| fControl.getShell().removeControlListener(fControlListener); |
| } |
| return super.close(); |
| } |
| |
| /** |
| * Get the time event filter dialog location |
| * |
| * @return the filter dialog location |
| */ |
| @Nullable public Point getFilterLocation() { |
| if (!fControl.isDisposed()) { |
| Rectangle bounds = fControl.getBounds(); |
| int width = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT).x; |
| return fControl.toDisplay(new Point(bounds.x + bounds.width - width, bounds.y + bounds.height + Y_OFFSET)); |
| } |
| return null; |
| } |
| |
| /** |
| * Tells whether the dialog has active filters |
| * |
| * @return true if there is active filter, false otherwise |
| */ |
| public boolean isFilterActive() { |
| return !NonNullUtils.nullToEmptyString(fRegex).isEmpty() || hasActiveSavedFilters(); |
| } |
| |
| /** |
| * Tells whether the dialog has active saved filters |
| * |
| * @return true if there is active saved filters, false otherwise |
| */ |
| public boolean hasActiveSavedFilters() { |
| return !fFilterRegexes.isEmpty(); |
| } |
| |
| /** |
| * Get the dialog text box regex |
| * |
| * @return The current text box regex |
| */ |
| public @NonNull String getTextBoxRegex() { |
| return NonNullUtils.nullToEmptyString(fRegex); |
| } |
| |
| /** |
| * Get the set of saved filters |
| * |
| * @return the set of saved filters |
| */ |
| public Set<@NonNull String> getSavedFilters() { |
| return fFilterRegexes; |
| } |
| |
| /** |
| * Listener that handles move and resize events of the chart. |
| */ |
| private class ControlMovedListener implements ControlListener { |
| |
| @Override |
| public void controlMoved(@Nullable ControlEvent e) { |
| resize(); |
| } |
| |
| @Override |
| public void controlResized(@Nullable ControlEvent e) { |
| resize(); |
| } |
| |
| private void resize() { |
| if (getShell() == null) { |
| return; |
| } |
| Point size = getInitialSize(); |
| Point location = getFilterLocation(); |
| if (location != null) { |
| getShell().setBounds(getConstrainedShellBounds(new Rectangle(location.x, |
| location.y, size.x, size.y))); |
| } |
| } |
| } |
| |
| } |