1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * 
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  * 
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  * 
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  * 
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  * 
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.flightrecorder.ui.common;
  34 
  35 import java.util.HashMap;
  36 import java.util.Iterator;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Map.Entry;
  40 import java.util.Optional;
  41 import java.util.Set;
  42 import java.util.function.BiConsumer;
  43 import java.util.function.Consumer;
  44 import java.util.stream.Collectors;
  45 
  46 import org.eclipse.jface.layout.GridDataFactory;
  47 import org.eclipse.jface.layout.GridLayoutFactory;
  48 import org.eclipse.jface.viewers.ComboViewer;
  49 import org.eclipse.jface.viewers.ISelection;
  50 import org.eclipse.jface.viewers.ISelectionChangedListener;
  51 import org.eclipse.jface.viewers.IStructuredContentProvider;
  52 import org.eclipse.jface.viewers.IStructuredSelection;
  53 import org.eclipse.jface.viewers.LabelProvider;
  54 import org.eclipse.jface.viewers.SelectionChangedEvent;
  55 import org.eclipse.jface.viewers.StructuredSelection;
  56 import org.eclipse.jface.viewers.Viewer;
  57 import org.eclipse.swt.SWT;
  58 import org.eclipse.swt.events.PaintEvent;
  59 import org.eclipse.swt.events.PaintListener;
  60 import org.eclipse.swt.events.SelectionAdapter;
  61 import org.eclipse.swt.events.SelectionEvent;
  62 import org.eclipse.swt.graphics.Point;
  63 import org.eclipse.swt.graphics.RGB;
  64 import org.eclipse.swt.widgets.Button;
  65 import org.eclipse.swt.widgets.Canvas;
  66 import org.eclipse.swt.widgets.Composite;
  67 import org.eclipse.swt.widgets.Control;
  68 import org.eclipse.swt.widgets.Label;
  69 import org.eclipse.ui.forms.widgets.Form;
  70 
  71 import org.openjdk.jmc.common.IDisplayable;
  72 import org.openjdk.jmc.common.IMCThread;
  73 import org.openjdk.jmc.common.item.IAttribute;
  74 import org.openjdk.jmc.common.item.IItemCollection;
  75 import org.openjdk.jmc.common.item.IItemFilter;
  76 import org.openjdk.jmc.common.unit.IQuantity;
  77 import org.openjdk.jmc.common.unit.IRange;
  78 import org.openjdk.jmc.flightrecorder.ui.IPageContainer;
  79 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
  80 import org.openjdk.jmc.flightrecorder.ui.selection.FlavorToolkit;
  81 import org.openjdk.jmc.flightrecorder.ui.selection.IFlavoredSelection;
  82 import org.openjdk.jmc.flightrecorder.ui.selection.IItemStreamFlavor;
  83 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore;
  84 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore.SelectionStoreEntry;
  85 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStore.SelectionStoreListener;
  86 import org.openjdk.jmc.ui.charts.SubdividedQuantityRange;
  87 import org.openjdk.jmc.ui.misc.SWTColorToolkit;
  88 
  89 /**
  90  * Class for creating a flavor chooser.
  91  */
  92 public class FlavorSelector implements SelectionStoreListener {
  93 
  94         public static class FlavorSelectorState {
  95 
  96                 private boolean showConcurrent = false;
  97                 private boolean concurrentContained = false;
  98                 private boolean sameThreads = true;
  99                 private Map<IFlavoredSelection, IItemStreamFlavor[]> calculatedFlavorsMap = new HashMap<>();
 100                 private Map<IFlavoredSelection, IItemStreamFlavor> selectedFlavorMap = new HashMap<>();
 101 
 102                 public void clearFlavorMaps() {
 103                         calculatedFlavorsMap.clear();
 104                         selectedFlavorMap.clear();
 105                 }
 106         }
 107 
 108         private final IItemFilter filter;
 109         private final IItemCollection items;
 110         private final IPageContainer pageContainer;
 111         private final SelectionWithThreadAndRangeConsumer onSelectWithThreads;
 112 
 113         private final Composite container;
 114         // FIXME: We can remove all references to useSelectionButton if we decide that we are not going to use it
 115 //      private final Button useSelectionButton;
 116         private final ComboViewer selectionCombo;
 117         private final ComboViewer flavorCombo;
 118         private final Canvas canvas;
 119         private final RangePainter painter;
 120         private Button showButton;
 121         private Button resetButton;
 122         private Button showConcurrentButton;
 123         private Button rangeStyleButton;
 124         private Button sameThreadsButton;
 125 
 126         private boolean callbackActive = false;
 127         private List<IAttribute<?>> attributes;
 128         private FlavorSelectorState flavorSelectorState;
 129 
 130         /**
 131          * Creates a flavor selector.
 132          *
 133          * @param form
 134          *            Form to create selector in
 135          * @param filter
 136          *            Filter to use when choosing flavors
 137          * @param items
 138          *            Items to use when choosing flavors
 139          * @param pageContainer
 140          *            Page container used for providing selection store and time range
 141          * @param onSelect
 142          *            Called when a flavor is chosen. Arguments are the items from evaluating the flavor
 143          *            and the calculated time range for them.
 144          * @return A flavor selector
 145          */
 146         public static FlavorSelector itemsWithTimerange(
 147                 Form form, IItemFilter filter, IItemCollection items, IPageContainer pageContainer,
 148                 BiConsumer<IItemCollection, IRange<IQuantity>> onSelect, Consumer<Boolean> onShow,
 149                 FlavorSelectorState flavorSelectorState) {
 150                 return new FlavorSelector(form, filter, null, items, pageContainer,
 151                                 (itemCollection, threadNames, range) -> onSelect.accept(itemCollection, range),
 152                                 Optional.ofNullable(onShow), flavorSelectorState);
 153         }
 154 
 155         /**
 156          * Creates a flavor selector.
 157          *
 158          * @param form
 159          *            Form to create selector in
 160          * @param filter
 161          *            Filter to use when choosing flavors
 162          * @param items
 163          *            Items to use when choosing flavors
 164          * @param attributes
 165          *            Attributes to use when choosing flavors
 166          * @param pageContainer
 167          *            Page container used for providing selection store and time range
 168          * @param onSelectWithThreads
 169          *            Called when a flavor is chosen. Arguments are the items from evaluating the flavor
 170          *            (or null), the calculated thread names and time range
 171          * @return A flavor selector
 172          */
 173         public static FlavorSelector itemsWithTimerange(
 174                 Form form, IItemFilter filter, List<IAttribute<?>> attributes, IItemCollection items,
 175                 IPageContainer pageContainer, SelectionWithThreadAndRangeConsumer onSelectWithThreads,
 176                 FlavorSelectorState flavorSelectorState) {
 177                 return new FlavorSelector(form, filter, attributes, items, pageContainer, onSelectWithThreads,
 178                                 Optional.ofNullable(null), flavorSelectorState);
 179         }
 180 
 181         /**
 182          * Creates a flavor selector.
 183          *
 184          * @param form
 185          *            Form to create selector in
 186          * @param filter
 187          *            Filter to use when choosing flavors
 188          * @param attributes
 189          *            Attributes to use when choosing flavors
 190          * @param items
 191          *            Items to use when choosing flavors
 192          * @param pageContainer
 193          *            Page container used for providing selection store and time range
 194          * @param onSelect
 195          *            Called when a flavor is chosen. Arguments are the items from evaluating the flavor
 196          *            (or null) and the calculated time range for them.
 197          * @return A flavor selector
 198          */
 199         public static FlavorSelector itemsWithTimerange(
 200                 Form form, IItemFilter filter, List<IAttribute<?>> attributes, IItemCollection items,
 201                 IPageContainer pageContainer, BiConsumer<IItemCollection, IRange<IQuantity>> onSelect,
 202                 FlavorSelectorState flavorSelectorState) {
 203                 return new FlavorSelector(form, filter, attributes, items, pageContainer,
 204                                 (itemCollection, threadNames, range) -> onSelect.accept(itemCollection, range),
 205                                 Optional.ofNullable(null), flavorSelectorState);
 206         }
 207 
 208         /**
 209          * Creates a flavor selector.
 210          *
 211          * @param form
 212          *            Form to create selector in
 213          * @param filter
 214          *            Filter to use when choosing flavors
 215          * @param items
 216          *            Items to use when choosing flavors
 217          * @param pageContainer
 218          *            Page container used for providing selection store and time range
 219          * @param onSelect
 220          *            Called when a flavor is chosen. Arguments are the items from evaluating the flavor
 221          *            (or null) and the calculated time range for them.
 222          * @return A flavor selector
 223          */
 224         public static FlavorSelector itemsWithTimerange(
 225                 Form form, IItemFilter filter, IItemCollection items, IPageContainer pageContainer,
 226                 BiConsumer<IItemCollection, IRange<IQuantity>> onSelect, FlavorSelectorState flavorSelectorState) {
 227                 return new FlavorSelector(form, filter, null, items, pageContainer,
 228                                 (itemCollection, threadNames, range) -> onSelect.accept(itemCollection, range),
 229                                 Optional.ofNullable(null), flavorSelectorState);
 230         }
 231 
 232         private FlavorSelector(Form form, IItemFilter filter, List<IAttribute<?>> attributes, IItemCollection items,
 233                         IPageContainer pageContainer, SelectionWithThreadAndRangeConsumer onSelectWithThreads,
 234                         Optional<Consumer<Boolean>> onShow, FlavorSelectorState flavorSelectorState) {
 235                 this.filter = filter;
 236                 this.attributes = attributes;
 237                 this.items = items;
 238                 this.pageContainer = pageContainer;
 239                 this.onSelectWithThreads = onSelectWithThreads;
 240                 flavorSelectorState = flavorSelectorState != null ? flavorSelectorState : new FlavorSelectorState();
 241                 this.flavorSelectorState = flavorSelectorState;
 242 
 243                 container = new Composite(form.getHead(), SWT.NONE);
 244                 container.setLayout(GridLayoutFactory.fillDefaults().margins(0, 0).spacing(2, 2).create());
 245 
 246                 Composite selectorRow = new Composite(container, SWT.NONE);
 247                 selectorRow
 248                                 .setLayoutData(GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).create());
 249                 selectorRow.setLayout(GridLayoutFactory.swtDefaults().numColumns(9).create());
 250 
 251 //              useSelectionButton = new Button(selectorRow, SWT.CHECK);
 252 //              useSelectionButton.setLayoutData(GridDataFactory.swtDefaults().create());
 253 //              useSelectionButton.setText("Filter on:");
 254 //              useSelectionButton.setEnabled(pageContainer.getSelectionStore().getSelections().count() > 0);
 255 //              useSelectionButton.setSelection(pageContainer.getSelectionStore().isCurrentActive());
 256 //              useSelectionButton.addSelectionListener(new SelectionCheckboxSelectionListener());
 257 
 258                 selectionCombo = new ComboViewer(selectorRow);
 259                 selectionCombo.getCombo().setLayoutData(GridDataFactory.swtDefaults().hint(200, SWT.DEFAULT)
 260                                 .minSize(100, SWT.DEFAULT).grab(false, false).create());
 261                 selectionCombo.setContentProvider(new SelectionComboContentProvider());
 262                 selectionCombo.setLabelProvider(new SelectionComboLabelProvider());
 263                 selectionCombo.addSelectionChangedListener(new SelectionComboSelectionListener());
 264 //              selectionCombo.getControl().setEnabled(pageContainer.getSelectionStore().isCurrentActive());
 265 
 266                 Label flavorLabel = new Label(selectorRow, SWT.NONE);
 267                 flavorLabel.setLayoutData(GridDataFactory.swtDefaults().create());
 268                 flavorLabel.setText(Messages.FlavorSelector_LABEL_ASPECT);
 269 
 270                 flavorCombo = new ComboViewer(selectorRow);
 271                 flavorCombo.getCombo().setLayoutData(GridDataFactory.swtDefaults().hint(300, SWT.DEFAULT)
 272                                 .minSize(100, SWT.DEFAULT).grab(true, false).create());
 273                 flavorCombo.setContentProvider(new FlavorComboContentProvider());
 274                 flavorCombo.setLabelProvider(new FlavorComboLabelProvider());
 275                 flavorCombo.addSelectionChangedListener(new FlavorComboSelectionListener());
 276 //              flavorCombo.getControl().setEnabled(pageContainer.getSelectionStore().isCurrentActive());
 277 
 278                 showConcurrentButton = new Button(selectorRow, SWT.CHECK);
 279                 showConcurrentButton.setLayoutData(GridDataFactory.swtDefaults().create());
 280                 showConcurrentButton.setText(Messages.FlavorSelector_BUTTON_SHOW_CONCURRENT);
 281                 showConcurrentButton.setToolTipText(Messages.FlavorSelector_BUTTON_SHOW_CONCURRENT_TOOLTIP);
 282                 showConcurrentButton.setSelection(this.flavorSelectorState.showConcurrent);
 283                 showConcurrentButton.addSelectionListener(new ShowConcurrentSelectionListener());
 284 
 285                 // FIXME: Instead use radio buttons with images?
 286                 rangeStyleButton = new Button(selectorRow, SWT.CHECK);
 287                 rangeStyleButton.setLayoutData(GridDataFactory.swtDefaults().create());
 288                 rangeStyleButton.setText(Messages.FlavorSelector_BUTTON_CONTAINED);
 289                 rangeStyleButton.setToolTipText(Messages.FlavorSelector_BUTTON_CONTAINED_TOOLTIP);
 290                 rangeStyleButton.setEnabled(showConcurrentButton.getSelection());
 291                 rangeStyleButton.setSelection(flavorSelectorState.concurrentContained);
 292                 rangeStyleButton.addSelectionListener(new RangeStyleSelectionListener());
 293 
 294                 // FIXME: Instead use radio buttons with images?
 295                 sameThreadsButton = new Button(selectorRow, SWT.CHECK);
 296                 sameThreadsButton.setLayoutData(GridDataFactory.swtDefaults().create());
 297                 sameThreadsButton.setText(Messages.FlavorSelector_BUTTON_SAME_THREADS);
 298                 sameThreadsButton.setToolTipText(Messages.FlavorSelector_BUTTON_SAME_THREADS_TOOLTIP);
 299                 sameThreadsButton.setEnabled(showConcurrentButton.getSelection());
 300                 sameThreadsButton.setSelection(flavorSelectorState.sameThreads);
 301                 sameThreadsButton.addSelectionListener(new SameThreadsSelectionListener());
 302 
 303                 // FIXME: Persist state for above checkboxes?
 304 
 305                 onShow.ifPresent(on -> {
 306                         Label rangeLabel = new Label(selectorRow, SWT.NONE);
 307                         rangeLabel.setLayoutData(GridDataFactory.swtDefaults().create());
 308                         rangeLabel.setText(Messages.FlavorSelector_LABEL_TIMERANGE);
 309                         showButton = new Button(selectorRow, SWT.PUSH);
 310                         showButton.setText(Messages.FlavorSelector_BUTTON_TIMERANGE_SET);
 311                         showButton.setToolTipText(Messages.FlavorSelector_BUTTON_TIMERANGE_SET_TOOLTIP);
 312                         showButton.setLayoutData(GridDataFactory.swtDefaults().create());
 313                         resetButton = new Button(selectorRow, SWT.PUSH);
 314                         resetButton.setText(Messages.FlavorSelector_BUTTON_TIMERANGE_CLEAR);
 315                         resetButton.setToolTipText(Messages.FlavorSelector_BUTTON_TIMERANGE_CLEAR_TOOLTIP);
 316                         resetButton.addListener(SWT.Selection, e -> on.accept(false));
 317                         resetButton.setLayoutData(GridDataFactory.swtDefaults().create());
 318                         showButton.addListener(SWT.Selection, e -> on.accept(true));
 319                 });
 320 
 321                 canvas = new Canvas(container, SWT.NO_BACKGROUND);
 322                 canvas.setLayoutData(GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).hint(SWT.DEFAULT, 7)
 323                                 .grab(true, false).create());
 324                 painter = new RangePainter(canvas, pageContainer.getRecordingRange());
 325 
 326                 selectionCombo.setInput(pageContainer.getSelectionStore());
 327                 selectionCombo.setSelection(getCurrentSelection());
 328                 callbackActive = true;
 329 
 330                 enableSelection();
 331 
 332                 IItemStreamFlavor currentFlavor = null;
 333                 if (pageContainer.getSelectionStore().isCurrentActive()) {
 334                         currentFlavor = getSelectedFlavor();
 335                 }
 336                 useFlavor(currentFlavor);
 337 
 338                 pageContainer.getSelectionStore().setListener(this);
 339 
 340                 form.setHeadClient(container);
 341         }
 342 
 343         private ISelection getCurrentSelection() {
 344                 return pageContainer.getSelectionStore().getCurrentSelection() != null
 345                                 ? new StructuredSelection(pageContainer.getSelectionStore().getCurrentSelection())
 346                                 : new StructuredSelection(selectionCombo.getElementAt(0));
 347         }
 348 
 349         @Override
 350         public void selectionActive(boolean active) {
 351 //              useSelectionButton.setSelection(active);
 352                 selectionCombo.setSelection(getCurrentSelection());
 353         }
 354 
 355         @Override
 356         public void selectionAdded(SelectionStoreEntry selection) {
 357 //              useSelectionButton.setEnabled(true);
 358                 if (!selectionCombo.getControl().isDisposed()) {
 359                         selectionCombo.refresh();
 360                 }
 361         }
 362 
 363         private static String formatRange(IRange<IQuantity> range) {
 364                 return range.getStart().displayUsing(IDisplayable.AUTO) + " - ( " //$NON-NLS-1$
 365                                 + range.getExtent().displayUsing(IDisplayable.AUTO) + " ) - " //$NON-NLS-1$
 366                                 + range.getEnd().displayUsing(IDisplayable.AUTO);
 367         }
 368 
 369         public void enableSelection() {
 370                 boolean enabled = true;
 371                 pageContainer.getSelectionStore().setCurrentActive(enabled);
 372                 selectionCombo.getCombo().setEnabled(enabled);
 373                 flavorCombo.getCombo().setEnabled(enabled);
 374                 // FIXME: Make sure not to call useFlavor twice during initialization.
 375 //              IItemStreamFlavor flavor = null;
 376 //              if (enabled) {
 377 //                      flavor = getSelectedFlavor();
 378 //              }
 379 //              useFlavor(flavor);
 380         }
 381 
 382         public FlavorSelectorState getFlavorSelectorState() {
 383                 return flavorSelectorState;
 384         }
 385 
 386         private IItemStreamFlavor getSelectedFlavor() {
 387                 IItemStreamFlavor flavor = null;
 388                 ISelection s = flavorCombo.getSelection();
 389                 if (s instanceof IStructuredSelection) {
 390                         Object obj = ((IStructuredSelection) s).getFirstElement();
 391                         if (obj instanceof IItemStreamFlavor) {
 392                                 flavor = (IItemStreamFlavor) obj;
 393                         }
 394                 }
 395                 return flavor;
 396         }
 397 
 398         private static final class SelectionComboContentProvider implements IStructuredContentProvider {
 399                 private SelectionStore store;
 400 
 401                 @Override
 402                 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
 403                         if (newInput instanceof SelectionStore) {
 404                                 store = (SelectionStore) newInput;
 405                         }
 406                 }
 407 
 408                 @Override
 409                 public void dispose() {
 410                 }
 411 
 412                 @Override
 413                 public Object[] getElements(Object inputElement) {
 414                         // FIXME: This is for if we enable/disable selection usage outside of the combo
 415 //                      if (store.getSelections().count() == 0) {
 416 //                              return new String[] { "<No selection stored>" };
 417 //                      }
 418                         return store.getSelections().toArray();
 419                 }
 420         }
 421 
 422         public interface SelectionWithThreadAndRangeConsumer {
 423                 public void accept(IItemCollection items, Set<String> threadNames, IRange<IQuantity> range);
 424         }
 425 
 426         private static final class SelectionComboLabelProvider extends LabelProvider {
 427                 @Override
 428                 public String getText(Object element) {
 429                         if (element instanceof SelectionStoreEntry) {
 430                                 SelectionStoreEntry entry = (SelectionStoreEntry) element;
 431                                 return entry.getName();
 432                         }
 433                         return super.getText(element);
 434                 }
 435         }
 436 
 437         private final class SelectionComboSelectionListener implements ISelectionChangedListener {
 438                 @Override
 439                 public void selectionChanged(SelectionChangedEvent event) {
 440                         flavorCombo.getCombo().removeAll();
 441                         if (event.getSelection() instanceof IStructuredSelection) {
 442                                 Object obj = ((IStructuredSelection) (event.getSelection())).getFirstElement();
 443                                 if (obj instanceof SelectionStoreEntry) {
 444                                         SelectionStoreEntry entry = (SelectionStoreEntry) obj;
 445                                         IFlavoredSelection selection = entry.getSelection();
 446                                         pageContainer.getSelectionStore().setCurrent(selection);
 447 
 448                                         IItemStreamFlavor[] flavors = flavorSelectorState.calculatedFlavorsMap.get(selection);
 449                                         if (flavors == null) {
 450                                                 flavors = selection.getFlavors(filter, items, attributes).toArray(IItemStreamFlavor[]::new);
 451                                                 flavorSelectorState.calculatedFlavorsMap.put(selection, flavors);
 452                                         }
 453                                         flavorCombo.setInput(flavors);
 454 
 455                                         IItemStreamFlavor selectedFlavor = flavorSelectorState.selectedFlavorMap.get(selection);
 456                                         if (selectedFlavor == null) {
 457                                                 if (flavors.length > 0) {
 458                                                         selectedFlavor = flavors[0];
 459                                                         flavorSelectorState.selectedFlavorMap.put(selection, selectedFlavor);
 460                                                 }
 461                                         }
 462                                         if (selectedFlavor != null) {
 463                                                 flavorCombo.setSelection(new StructuredSelection(selectedFlavor));
 464                                         }
 465 
 466                                         trimFlavorMaps();
 467                                 }
 468                         }
 469                 }
 470         }
 471 
 472         private static final class FlavorComboContentProvider implements IStructuredContentProvider {
 473                 private IItemStreamFlavor[] flavors;
 474 
 475                 @Override
 476                 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
 477                         if (newInput instanceof IItemStreamFlavor[]) {
 478                                 flavors = (IItemStreamFlavor[]) newInput;
 479                         }
 480                 }
 481 
 482                 @Override
 483                 public void dispose() {
 484 
 485                 }
 486 
 487                 @Override
 488                 public Object[] getElements(Object inputElement) {
 489                         if (flavors == null || flavors.length == 0) {
 490                                 return new String[] {"<" + Messages.FlavorSelector_LABEL_NO_SELECTION + ">"}; //$NON-NLS-1$ //$NON-NLS-2$
 491                         }
 492                         return flavors;
 493                 }
 494         }
 495 
 496         private static final class FlavorComboLabelProvider extends LabelProvider {
 497                 @Override
 498                 public String getText(Object element) {
 499                         if (element instanceof IItemStreamFlavor) {
 500                                 IItemStreamFlavor sel = (IItemStreamFlavor) element;
 501                                 return sel.getName();
 502                         }
 503                         return super.getText(element);
 504                 }
 505         }
 506 
 507         private final class FlavorComboSelectionListener implements ISelectionChangedListener {
 508                 @Override
 509                 public void selectionChanged(SelectionChangedEvent event) {
 510                         IItemStreamFlavor flavor = null;
 511                         if (event.getSelection() instanceof IStructuredSelection) {
 512                                 Object obj = ((IStructuredSelection) event.getSelection()).getFirstElement();
 513                                 if (obj instanceof IItemStreamFlavor) {
 514                                         flavor = ((IItemStreamFlavor) obj);
 515                                         SelectionStoreEntry entry = pageContainer.getSelectionStore().getCurrentSelection();
 516                                         if (entry != null) {
 517                                                 flavorSelectorState.selectedFlavorMap.put(entry.getSelection(), flavor);
 518                                         }
 519                                 }
 520                         }
 521                         useFlavor(flavor);
 522                 }
 523         }
 524 
 525         private void useFlavor(IItemStreamFlavor flavor) {
 526                 if (callbackActive) {
 527                         Optional<IRange<IQuantity>> range = FlavorToolkit.getRange(flavor);
 528                         painter.current = range.orElse(null);
 529                         canvas.setVisible(painter.current != null);
 530                         canvas.setToolTipText(range.map(FlavorSelector::formatRange).orElse(null));
 531                         container.layout();
 532 
 533                         // FIXME: Always use concurrent if (all?) items can't be displayed on page?
 534                         IItemCollection itemsToUse = null;
 535                         Set<IMCThread> threads = FlavorToolkit.getThreads(getSelectedFlavor(), flavorSelectorState.showConcurrent,
 536                                         flavorSelectorState.sameThreads);
 537                         if (flavor != null && !flavor.isEmpty()) {
 538                                 IItemFilter rangeAndThreadFilter = FlavorToolkit.getRangeAndThreadFilter(range, threads,
 539                                                 flavorSelectorState.showConcurrent, flavorSelectorState.concurrentContained,
 540                                                 flavorSelectorState.sameThreads);
 541                                 if (rangeAndThreadFilter != null) {
 542                                         itemsToUse = items.apply(rangeAndThreadFilter);
 543                                 } else {
 544                                         itemsToUse = flavor.evaluate();
 545                                 }
 546                         }
 547                         Set<String> threadNames = FlavorToolkit.getThreadNames(threads, flavor);
 548                         onSelectWithThreads.accept(itemsToUse, threadNames, range.orElse(pageContainer.getRecordingRange()));
 549                 }
 550         }
 551 
 552         public void trimFlavorMaps() {
 553                 // NOTE: It should be enough to keep the map sizes below 2 * storesize
 554                 if (flavorSelectorState.calculatedFlavorsMap
 555                                 .size() > (2 * pageContainer.getSelectionStore().getSelections().count())) {
 556 
 557                         List<IFlavoredSelection> storedSelections = pageContainer.getSelectionStore().getSelections()
 558                                         .map(sse -> sse.getSelection()).collect(Collectors.toList());
 559 
 560                         for (Iterator<Entry<IFlavoredSelection, IItemStreamFlavor[]>> iterator = flavorSelectorState.calculatedFlavorsMap
 561                                         .entrySet().iterator(); iterator.hasNext();) {
 562                                 IFlavoredSelection selection = iterator.next().getKey();
 563                                 if (!storedSelections.contains(selection)) {
 564                                         iterator.remove();
 565                                 }
 566                         }
 567 
 568                         for (Iterator<Entry<IFlavoredSelection, IItemStreamFlavor>> iterator = flavorSelectorState.selectedFlavorMap
 569                                         .entrySet().iterator(); iterator.hasNext();) {
 570                                 IFlavoredSelection selection = iterator.next().getKey();
 571                                 if (!storedSelections.contains(selection)) {
 572                                         iterator.remove();
 573                                 }
 574                         }
 575                 }
 576         }
 577 
 578         public class ShowConcurrentSelectionListener extends SelectionAdapter {
 579                 @Override
 580                 public void widgetSelected(SelectionEvent e) {
 581                         flavorSelectorState.showConcurrent = showConcurrentButton.getSelection();
 582                         rangeStyleButton.setEnabled(flavorSelectorState.showConcurrent);
 583                         sameThreadsButton.setEnabled(flavorSelectorState.showConcurrent);
 584                         useFlavor(getSelectedFlavor());
 585                 }
 586         }
 587 
 588         public class RangeStyleSelectionListener extends SelectionAdapter {
 589                 @Override
 590                 public void widgetSelected(SelectionEvent e) {
 591                         flavorSelectorState.concurrentContained = rangeStyleButton.getSelection();
 592                         useFlavor(getSelectedFlavor());
 593                 }
 594         }
 595 
 596         public class SameThreadsSelectionListener extends SelectionAdapter {
 597                 @Override
 598                 public void widgetSelected(SelectionEvent e) {
 599                         flavorSelectorState.sameThreads = sameThreadsButton.getSelection();
 600                         useFlavor(getSelectedFlavor());
 601                 }
 602         }
 603 
 604         private static class RangePainter implements PaintListener {
 605 
 606                 private final Control canvas;
 607                 private final IQuantity start;
 608                 private final IQuantity end;
 609 
 610                 IRange<IQuantity> current;
 611 
 612                 RangePainter(Control canvas, IRange<IQuantity> fullRange) {
 613                         this.canvas = canvas;
 614                         start = fullRange.getStart();
 615                         end = fullRange.getEnd();
 616                         canvas.addPaintListener(this);
 617                 }
 618 
 619                 @Override
 620                 public void paintControl(PaintEvent e) {
 621                         if (current != null) {
 622                                 Point size = canvas.getSize();
 623 
 624                                 e.gc.setBackground(SWTColorToolkit.getColor(new RGB(200, 200, 200)));
 625                                 e.gc.setForeground(SWTColorToolkit.getColor(new RGB(120, 120, 120)));
 626                                 e.gc.fillRectangle(0, 0, size.x, size.y);
 627                                 e.gc.drawRectangle(0, 0, size.x - 1, size.y - 1);
 628 
 629                                 SubdividedQuantityRange fullRangeAxis = new SubdividedQuantityRange(start, end, size.x, 25);
 630                                 int x1 = (int) fullRangeAxis.getPixel(current.getStart());
 631                                 int x2 = (int) Math.ceil(fullRangeAxis.getPixel(current.getEnd()));
 632                                 e.gc.setForeground(SWTColorToolkit.getColor(new RGB(221, 58, 22)));
 633                                 e.gc.setBackground(SWTColorToolkit.getColor(new RGB(252, 128, 3)));
 634                                 e.gc.fillGradientRectangle(x1, 0, x2 - x1, size.y, true);
 635                                 e.gc.setForeground(SWTColorToolkit.getColor(new RGB(0, 0, 0)));
 636                                 e.gc.drawRectangle(x1, 0, x2 - x1 - 1, size.y - 1);
 637                         }
 638                 }
 639         }
 640 }