1 /*
   2  * Copyright (c) 2018, 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.awt.Color;
  37 import java.util.ArrayList;
  38 import java.util.Collection;
  39 import java.util.Collections;
  40 import java.util.List;
  41 import java.util.stream.Collectors;
  42 import java.util.stream.Stream;
  43 
  44 import org.eclipse.jface.action.IAction;
  45 import org.eclipse.jface.layout.GridDataFactory;
  46 import org.eclipse.jface.layout.GridLayoutFactory;
  47 import org.eclipse.jface.viewers.ArrayContentProvider;
  48 import org.eclipse.jface.viewers.CellEditor;
  49 import org.eclipse.jface.viewers.CheckboxTableViewer;
  50 import org.eclipse.jface.viewers.ColumnLabelProvider;
  51 import org.eclipse.jface.viewers.EditingSupport;
  52 import org.eclipse.jface.viewers.IStructuredSelection;
  53 import org.eclipse.jface.viewers.StructuredSelection;
  54 import org.eclipse.jface.viewers.TableViewerColumn;
  55 import org.eclipse.jface.viewers.TextCellEditor;
  56 import org.eclipse.jface.viewers.ViewerCell;
  57 import org.eclipse.jface.window.ToolTip;
  58 import org.eclipse.jface.window.Window;
  59 import org.eclipse.jface.wizard.WizardPage;
  60 import org.eclipse.osgi.util.NLS;
  61 import org.eclipse.swt.SWT;
  62 import org.eclipse.swt.graphics.Point;
  63 import org.eclipse.swt.widgets.Composite;
  64 import org.eclipse.swt.widgets.Control;
  65 import org.eclipse.swt.widgets.Event;
  66 import org.eclipse.swt.widgets.Label;
  67 import org.eclipse.ui.IWorkbenchCommandConstants;
  68 import org.eclipse.ui.forms.widgets.FormText;
  69 
  70 import org.openjdk.jmc.common.IDescribable;
  71 import org.openjdk.jmc.common.IPredicate;
  72 import org.openjdk.jmc.common.IState;
  73 import org.openjdk.jmc.common.IStateful;
  74 import org.openjdk.jmc.common.IWritableState;
  75 import org.openjdk.jmc.common.item.IItem;
  76 import org.openjdk.jmc.common.item.IItemFilter;
  77 import org.openjdk.jmc.common.item.IType;
  78 import org.openjdk.jmc.common.item.ItemFilters;
  79 import org.openjdk.jmc.common.item.ItemFilters.Types;
  80 import org.openjdk.jmc.common.item.PersistableItemFilter;
  81 import org.openjdk.jmc.common.util.PredicateToolkit;
  82 import org.openjdk.jmc.common.util.StateToolkit;
  83 import org.openjdk.jmc.flightrecorder.ui.EventTypeFolderNode;
  84 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
  85 import org.openjdk.jmc.ui.UIPlugin;
  86 import org.openjdk.jmc.ui.handlers.ActionToolkit;
  87 import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
  88 import org.openjdk.jmc.ui.misc.ActionUiToolkit;
  89 import org.openjdk.jmc.ui.misc.CompositeToolkit;
  90 import org.openjdk.jmc.ui.misc.DialogToolkit;
  91 import org.openjdk.jmc.ui.misc.SWTColorToolkit;
  92 import org.openjdk.jmc.ui.wizards.IPerformFinishable;
  93 import org.openjdk.jmc.ui.wizards.OnePageWizardDialog;
  94 
  95 public class LaneEditor {
  96 
  97         private static final IItemFilter TYPE_HAS_THREAD_AND_DURATION = new IItemFilter() {
  98                 @Override
  99                 public IPredicate<IItem> getPredicate(IType<IItem> type) {
 100                         if (DataPageToolkit.isTypeWithThreadAndDuration(type)) {
 101                                 return PredicateToolkit.truePredicate();
 102                         }
 103                         return PredicateToolkit.falsePredicate();
 104                 }
 105         };
 106 
 107         private static class EditLanesWizardPage extends WizardPage implements IPerformFinishable {
 108 
 109                 private final EventTypeFolderNode root;
 110                 private final List<LaneDefinition> lanes;
 111                 private LaneDefinition restLane;
 112                 private TypeFilterBuilder filterEditor;
 113                 private CheckboxTableViewer lanesViewer;
 114                 private Object selected;
 115 
 116                 private EditLanesWizardPage(EventTypeFolderNode root, Collection<LaneDefinition> lanesInput) {
 117                         super("EditFilterLanesPage"); //$NON-NLS-1$
 118                         this.root = root;
 119                         this.lanes = new ArrayList<>(lanesInput);
 120                         restLane = ensureRestLane(lanes);
 121                 }
 122 
 123                 @Override
 124                 public void createControl(Composite parent) {
 125                         // FIXME: Do we want to group under categories somehow, or just hide the filters that don't have any existing event types.
 126                         Composite container = new Composite(parent, SWT.NONE);
 127                         container.setLayout(GridLayoutFactory.swtDefaults().numColumns(2).create());
 128 
 129                         Composite laneHeaderContainer = new Composite(container, SWT.NONE);
 130                         laneHeaderContainer.setLayout(GridLayoutFactory.swtDefaults().create());
 131                         laneHeaderContainer.setLayoutData(GridDataFactory.fillDefaults().create());
 132 
 133                         // FIXME: Add a duplicate action?
 134                         IAction moveUpAction = ActionToolkit.action(() -> moveSelected(true), Messages.LANES_MOVE_UP_ACTION,
 135                                         UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_NAV_UP));
 136                         IAction moveDownAction = ActionToolkit.action(() -> moveSelected(false), Messages.LANES_MOVE_DOWN_ACTION,
 137                                         UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_NAV_DOWN));
 138                         IAction addAction = ActionToolkit.action(this::addLane, Messages.LANES_ADD_LANE_ACTION,
 139                                         UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_ADD));
 140                         IAction removeAction = ActionToolkit.commandAction(this::deleteSelected,
 141                                         IWorkbenchCommandConstants.EDIT_DELETE);
 142                         Control toolbar = ActionUiToolkit.buildToolBar(laneHeaderContainer,
 143                                         Stream.of(moveUpAction, moveDownAction, addAction, removeAction), false);
 144                         toolbar.setLayoutData(GridDataFactory.fillDefaults().create());
 145 
 146                         Label lanesTitle = new Label(laneHeaderContainer, SWT.NONE);
 147                         lanesTitle.setText(Messages.LANES_EDITOR_LABEL);
 148                         lanesTitle.setLayoutData(GridDataFactory.fillDefaults().create());
 149                         Label filterTitle = new Label(container, SWT.NONE);
 150                         filterTitle.setText(Messages.LANES_FILTER_LABEL);
 151                         filterTitle.setLayoutData(
 152                                         GridDataFactory.fillDefaults().grab(true, false).align(SWT.BEGINNING, SWT.END).create());
 153 
 154                         lanesViewer = CheckboxTableViewer.newCheckList(container, SWT.BORDER | SWT.V_SCROLL);
 155                         TableViewerColumn viewerColumn = new TableViewerColumn(lanesViewer, SWT.NONE);
 156                         viewerColumn.getColumn().setText(Messages.LANES_LANE_COLUMN);
 157                         viewerColumn.getColumn().setWidth(200);
 158                         // FIXME: Would like to enable editing by some other means than single-clicking, but seems a bit tricky.
 159                         viewerColumn.setEditingSupport(new EditingSupport(lanesViewer) {
 160 
 161                                 private String currentName;
 162 
 163                                 @Override
 164                                 protected void setValue(Object element, Object value) {
 165                                         String newName = value.toString();
 166                                         if (currentName != null && currentName.equals(newName)) {
 167                                                 return;
 168                                         }
 169                                         LaneDefinition oldLd = (LaneDefinition) element;
 170                                         LaneDefinition newLane = new LaneDefinition(value.toString(), oldLd.enabled, oldLd.filter,
 171                                                         oldLd.isRestLane);
 172                                         int elementIndex = lanes.indexOf(element);
 173                                         lanes.set(elementIndex, newLane);
 174                                         lanesViewer.replace(newLane, elementIndex);
 175                                         getViewer().update(element, null);
 176                                 }
 177 
 178                                 @Override
 179                                 protected Object getValue(Object element) {
 180                                         currentName = ((LaneDefinition) element).getName();
 181                                         return currentName;
 182                                 }
 183 
 184                                 @Override
 185                                 protected CellEditor getCellEditor(Object element) {
 186                                         return new TextCellEditor((Composite) getViewer().getControl());
 187                                 }
 188 
 189                                 @Override
 190                                 protected boolean canEdit(Object element) {
 191                                         return true;
 192 
 193                                 }
 194                         });
 195 
 196                         lanesViewer.setLabelProvider(new ColumnLabelProvider() {
 197 
 198                                 @Override
 199                                 public String getText(Object element) {
 200                                         if (element instanceof LaneDefinition) {
 201                                                 if (element == selected) {
 202                                                         return ((LaneDefinition) element).getNameOrCount(filterEditor.getCheckedTypeIds().count());
 203                                                 } else {
 204                                                         return ((LaneDefinition) element).getName();
 205                                                 }
 206                                         }
 207                                         return super.getText(element);
 208                                 };
 209 
 210                                 // FIXME: Do we want to use italics for empty lanes?
 211 //                              @Override
 212 //                              public Font getFont(Object element) {
 213 //                                      if (getTypesCount(element) > 0) {
 214 //                                              return JFaceResources.getFontRegistry().get(JFaceResources.DEFAULT_FONT);
 215 //                                      } else {
 216 //                                              return JFaceResources.getFontRegistry().getItalic(JFaceResources.DEFAULT_FONT);
 217 //                                      }
 218 //                              }
 219 //
 220 //                              private long getTypesCount(Object element) {
 221 //                                      if (element == selected) {
 222 //                                              return filterEditor.getCheckedTypeIds().count();
 223 //                                      } else if (element instanceof LaneDefinition) {
 224 //                                              return ((LaneDefinition)element).getTypesCount();
 225 //                                      }
 226 //                                      return 0;
 227 //                              }
 228                         });
 229                         lanesViewer.setContentProvider(ArrayContentProvider.getInstance());
 230                         // FIXME: Can we potentially reuse this tooltip in the legend as well?
 231                         new ToolTip(lanesViewer.getControl(), ToolTip.NO_RECREATE, false) {
 232 
 233                                 @Override
 234                                 protected ViewerCell getToolTipArea(Event event) {
 235                                         return lanesViewer.getCell(new Point(event.x, event.y));
 236                                 }
 237 
 238                                 @Override
 239                                 protected Composite createToolTipContentArea(Event event, Composite parent) {
 240                                         FormText formText = CompositeToolkit.createInfoFormText(parent);
 241                                         Object element = getToolTipArea(event).getElement();
 242                                         Stream<String> ids = Stream.empty();
 243                                         if (element == selected) {
 244                                                 ids = filterEditor.getCheckedTypeIds();
 245                                         } else if (element instanceof LaneDefinition
 246                                                         && ((LaneDefinition) element).filter instanceof Types) {
 247                                                 ids = ((Types) ((LaneDefinition) element).filter).getTypes().stream();
 248                                         }
 249                                         StringBuilder sb = new StringBuilder();
 250                                         ids.forEach(typeId -> {
 251                                                 Color color = TypeLabelProvider.getColorOrDefault(typeId);
 252                                                 formText.setImage(typeId, SWTColorToolkit.getColorThumbnail(SWTColorToolkit.asRGB(color)));
 253                                                 sb.append("<li style='image' value='" + typeId + "'>" + typeId + "</li>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 254                                         });
 255                                         if (sb.length() > 0) {
 256                                                 sb.insert(0, "<form>"); //$NON-NLS-1$
 257                                                 sb.append("</form>"); //$NON-NLS-1$
 258                                                 formText.setText(sb.toString(), true, false);
 259                                         } else {
 260                                                 formText.setText(Messages.LANES_CHECK_TO_INCLUDE, false, false);
 261                                         }
 262                                         return formText;
 263                                 }
 264                         };
 265                         lanesViewer.setInput(lanes);
 266                         lanesViewer.setCheckedElements(lanes.stream().filter(ld -> ld.isEnabled()).toArray());
 267                         MCContextMenuManager mm = MCContextMenuManager.create(lanesViewer.getControl());
 268                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, moveUpAction);
 269                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, moveDownAction);
 270                         // FIXME: Add icon
 271                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, addAction);
 272 
 273                         mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, removeAction);
 274 
 275                         filterEditor = new TypeFilterBuilder(container, this::onTypeFilterChange);
 276                         filterEditor.setInput(root);
 277                         filterEditor.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
 278                         lanesViewer.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(false, true).create());
 279 
 280                         lanesViewer.addSelectionChangedListener(
 281                                         e -> laneSelectionChanges(((IStructuredSelection) e.getSelection()).getFirstElement()));
 282                         LaneDefinition firstLane = lanes.get(0);
 283                         lanesViewer.setSelection(new StructuredSelection(firstLane));
 284 
 285                         setControl(container);
 286                 }
 287 
 288                 private void addLane() {
 289                         int selectIndex = Math.max(0, lanes.indexOf(selected));
 290                         IItemFilter emptyFilter = ItemFilters.type(Collections.emptySet());
 291                         LaneDefinition newEmpty = new LaneDefinition(null, false, emptyFilter, false);
 292                         lanes.add(selectIndex + 1, newEmpty);
 293                         lanesViewer.insert(newEmpty, selectIndex + 1);
 294                         lanesViewer.setSelection(new StructuredSelection(newEmpty));
 295                 }
 296 
 297                 private void onTypeFilterChange() {
 298                         if (selected instanceof LaneDefinition) {
 299                                 LaneDefinition selectedLane = (LaneDefinition) selected;
 300                                 if (selectedLane.isRestLane()) {
 301                                         DialogToolkit.showWarningDialogAsync(lanesViewer.getControl().getDisplay(),
 302                                                         Messages.LANES_EDIT_NOT_ALLOWED_WARNING,
 303                                                         NLS.bind(Messages.LANES_EDIT_NOT_ALLOWED_WARNING_DESC, selectedLane.getName()));
 304                                         // FIXME: Can we refresh the filter editor to show that nothing has changed?
 305                                 }
 306                         }
 307                         lanesViewer.update(selected, null);
 308                 }
 309 
 310                 private void deleteSelected() {
 311                         // FIXME: It's currently not possible to delete the last lane
 312                         int selectIndex = Math.max(0, lanes.indexOf(selected) - 1);
 313                         if (selected instanceof LaneDefinition && ((LaneDefinition) selected).isRestLane()) {
 314                                 lanes.remove(selected);
 315                                 lanesViewer.setSelection(new StructuredSelection(lanes.get(selectIndex)));
 316                                 lanesViewer.refresh();
 317                         } else {
 318                                 DialogToolkit.showWarningDialogAsync(lanesViewer.getControl().getDisplay(),
 319                                                 Messages.LANES_DELETE_NOT_ALLOWED_WARNING, NLS.bind(
 320                                                                 Messages.LANES_DELETE_NOT_ALLOWED_WARNING_DESC, ((LaneDefinition) selected).getName()));
 321                         }
 322                 }
 323 
 324                 private void moveSelected(boolean up) {
 325                         int fromIndex = lanes.indexOf(selected);
 326                         int toIndex = fromIndex + (up ? -1 : 1);
 327                         if (fromIndex >= 0 && toIndex >= 0 && toIndex < lanes.size()) {
 328                                 LaneDefinition removed = lanes.remove(fromIndex);
 329                                 lanes.add(toIndex, removed);
 330                                 lanesViewer.refresh();
 331                         }
 332                 }
 333 
 334                 private void laneSelectionChanges(Object newSelected) {
 335                         int selectedIndex = lanes.indexOf(newSelected);
 336                         if (this.selected != newSelected) {
 337                                 saveFilter();
 338                                 this.selected = lanes.get(selectedIndex);
 339                                 if (selected instanceof LaneDefinition) {
 340                                         Types typesFilter;
 341                                         if (((LaneDefinition) selected).getFilter() instanceof Types) {
 342                                                 typesFilter = ((Types) ((LaneDefinition) selected).getFilter());
 343                                         } else {
 344                                                 typesFilter = (Types) ItemFilters.convertToTypes(((LaneDefinition) selected).getFilter(),
 345                                                                 filterEditor.getAllTypes());
 346                                         }
 347                                         filterEditor.selectTypes(typesFilter.getTypes());
 348                                 }
 349                         }
 350                 }
 351 
 352                 private void saveFilter() {
 353                         int selectedIndex = lanes.indexOf(selected);
 354                         if (selectedIndex >= 0) {
 355                                 LaneDefinition ld = lanes.get(selectedIndex);
 356                                 if (!ld.isRestLane()) {
 357                                         IItemFilter newFilter = ItemFilters
 358                                                         .type(filterEditor.getCheckedTypeIds().collect(Collectors.toSet()));
 359                                         LaneDefinition newLd = new LaneDefinition(ld.name, lanesViewer.getChecked(ld), newFilter,
 360                                                         ld.isRestLane);
 361                                         lanes.set(selectedIndex, newLd);
 362                                         lanesViewer.replace(newLd, selectedIndex);
 363                                         if (restLane != null) {
 364                                                 LaneDefinition newRest = new LaneDefinition(restLane.name, restLane.enabled,
 365                                                                 getRestFilter(lanes), true);
 366                                                 int restIndex = lanes.indexOf(restLane);
 367                                                 lanes.set(restIndex, newRest);
 368                                                 lanesViewer.replace(newRest, restIndex);
 369                                                 restLane = newRest;
 370                                         }
 371                                         lanesViewer.refresh();
 372                                 }
 373                         }
 374                 }
 375 
 376                 @Override
 377                 public boolean performFinish() {
 378                         saveFilter();
 379                         for (int i = 0; i < lanes.size(); i++) {
 380                                 LaneDefinition ld = lanes.get(i);
 381                                 if (ld.isEnabled() != lanesViewer.getChecked(ld)) {
 382                                         lanes.set(i, new LaneDefinition(ld.name, lanesViewer.getChecked(ld), ld.filter, ld.isRestLane));
 383                                 }
 384                         }
 385                         return true;
 386                 }
 387         }
 388 
 389         public static class LaneDefinition implements IDescribable, IStateful {
 390 
 391                 private static final String FILTER = "filter"; //$NON-NLS-1$
 392                 private static final String NAME = "name"; //$NON-NLS-1$
 393                 private static final String ENABLED = "enabled"; //$NON-NLS-1$
 394                 private static final String IS_REST_LANE = "isRestLane"; //$NON-NLS-1$
 395 
 396                 private final String name;
 397                 private final IItemFilter filter;
 398                 private final boolean enabled;
 399                 private final boolean isRestLane;
 400 
 401                 public LaneDefinition(String name, boolean enabled, IItemFilter filter, boolean isRestLane) {
 402                         this.name = name;
 403                         this.enabled = enabled;
 404                         this.filter = filter;
 405                         this.isRestLane = isRestLane;
 406                 }
 407 
 408                 @Override
 409                 public String getName() {
 410                         long count = filter instanceof Types ? ((Types) filter).getTypes().size() : 0;
 411                         return getNameOrCount(count);
 412                 }
 413 
 414                 public String getNameOrCount(long count) {
 415                         return name != null ? name
 416                                         : count == 1 && ((Types) filter).getTypes().iterator().hasNext()
 417                                                         ? ((Types) filter).getTypes().iterator().next()
 418                                                         : count > 0 ? NLS.bind(Messages.LANES_DEFINITION_NAME, count) : Messages.LANES_EMPTY_LANE;
 419                 }
 420 
 421                 @Override
 422                 public String getDescription() {
 423                         return NLS.bind(Messages.LANES_DEFINITION_DESC, getName());
 424                 }
 425 
 426                 public IItemFilter getFilter() {
 427                         return filter;
 428                 }
 429 
 430                 public boolean isEnabled() {
 431                         return enabled;
 432                 }
 433 
 434                 public boolean isRestLane() {
 435                         return isRestLane;
 436                 }
 437 
 438                 public boolean isEnabledAndNotRestLane() {
 439                         return enabled && !isRestLane;
 440                 }
 441 
 442                 @Override
 443                 public void saveTo(IWritableState writableState) {
 444                         writableState.putString(NAME, name);
 445                         StateToolkit.writeBoolean(writableState, ENABLED, enabled);
 446                         StateToolkit.writeBoolean(writableState, IS_REST_LANE, isRestLane);
 447                         if (!isRestLane && filter != null) {
 448                                 ((PersistableItemFilter) filter).saveTo(writableState.createChild(FILTER));
 449                         }
 450                 }
 451 
 452                 public static LaneDefinition readFrom(IState memento) {
 453                         String name = memento.getAttribute(NAME);
 454                         boolean enabled = StateToolkit.readBoolean(memento, ENABLED, false);
 455                         boolean isRestLane = StateToolkit.readBoolean(memento, IS_REST_LANE, false);
 456                         IState filterState = memento.getChild(FILTER);
 457                         IItemFilter filter;
 458                         if (isRestLane) {
 459                                 filter = null;
 460                         } else if (filterState != null) {
 461                                 filter = PersistableItemFilter.readFrom(filterState);
 462                         } else {
 463                                 throw new UnsupportedOperationException("Null filter not allowed for thread lane: " + name); //$NON-NLS-1$
 464                         }
 465                         // FIXME: Should probably warn if filter is not an instance of Types, and possibly handle other type filter variants as well, like TypeMatches.
 466                         return new LaneDefinition(name, enabled, filter, isRestLane);
 467                 }
 468 
 469                 @Override
 470                 public String toString() {
 471                         return getName() + "(" + enabled + ")"; //$NON-NLS-1$ //$NON-NLS-2$
 472                 }
 473         }
 474 
 475         public static List<LaneDefinition> openDialog(
 476                 EventTypeFolderNode root, List<LaneDefinition> lanes, String title, String description) {
 477                 EditLanesWizardPage page = new EditLanesWizardPage(root, lanes);
 478                 page.setTitle(title);
 479                 page.setDescription(description);
 480                 if (OnePageWizardDialog.open(page, 500, 600) == Window.OK) {
 481                         return page.lanes.stream().filter(LaneEditor::laneIncludesTypes).collect(Collectors.toList());
 482                 }
 483                 return lanes;
 484         }
 485 
 486         private static boolean laneIncludesTypes(LaneDefinition ld) {
 487                 return ld.isRestLane() || ld.getFilter() instanceof Types && ((Types) ld.getFilter()).getTypes().size() > 0;
 488         }
 489 
 490         private static IItemFilter getRestFilter(List<LaneDefinition> lanesInput) {
 491                 List<IItemFilter> laneFilters = lanesInput.stream().filter(ld -> !ld.isRestLane).map(ld -> ld.getFilter())
 492                                 .collect(Collectors.toList());
 493                 IItemFilter laneFilter = ItemFilters.or(laneFilters.toArray(new IItemFilter[laneFilters.size()]));
 494                 return ItemFilters.and(ItemFilters.not(laneFilter), TYPE_HAS_THREAD_AND_DURATION);
 495         }
 496 
 497         public static LaneDefinition ensureRestLane(List<LaneDefinition> lanesInput) {
 498                 // FIXME: Should we react if there are several rest lanes specified, or just ignore the other ones?
 499                 LaneDefinition oldRestLane = lanesInput.stream().filter(ld -> ld.isRestLane).findAny().orElse(null);
 500                 LaneDefinition newRestLane;
 501                 IItemFilter restFilter = getRestFilter(lanesInput);
 502                 if (oldRestLane == null) {
 503                         newRestLane = new LaneDefinition(Messages.LANES_OTHER_TYPES, false, restFilter, true);
 504                         lanesInput.add(newRestLane);
 505                 } else {
 506                         newRestLane = new LaneDefinition(oldRestLane.name, oldRestLane.enabled, restFilter, true);
 507                         lanesInput.set(lanesInput.indexOf(oldRestLane), newRestLane);
 508                 }
 509                 return newRestLane;
 510         }
 511 }