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.pages.itemhandler;
  34 
  35 import java.util.Arrays;
  36 import java.util.List;
  37 import java.util.function.Consumer;
  38 
  39 import org.eclipse.jface.action.Action;
  40 import org.eclipse.jface.action.IContributionManager;
  41 import org.eclipse.jface.action.IMenuListener;
  42 import org.eclipse.jface.action.ToolBarManager;
  43 import org.eclipse.jface.layout.GridLayoutFactory;
  44 import org.eclipse.jface.resource.ImageDescriptor;
  45 import org.eclipse.osgi.util.NLS;
  46 import org.eclipse.swt.SWT;
  47 import org.eclipse.swt.custom.CTabFolder;
  48 import org.eclipse.swt.custom.CTabItem;
  49 import org.eclipse.swt.widgets.Composite;
  50 import org.eclipse.swt.widgets.Control;
  51 import org.eclipse.ui.forms.widgets.FormToolkit;
  52 
  53 import org.openjdk.jmc.common.IState;
  54 import org.openjdk.jmc.common.IWritableState;
  55 import org.openjdk.jmc.common.item.IAttribute;
  56 import org.openjdk.jmc.common.item.IItem;
  57 import org.openjdk.jmc.common.item.IItemCollection;
  58 import org.openjdk.jmc.common.item.IItemFilter;
  59 import org.openjdk.jmc.common.item.IMemberAccessor;
  60 import org.openjdk.jmc.common.item.ItemToolkit;
  61 import org.openjdk.jmc.common.unit.ContentType;
  62 import org.openjdk.jmc.common.unit.IQuantity;
  63 import org.openjdk.jmc.common.unit.IRange;
  64 import org.openjdk.jmc.common.unit.LinearKindOfQuantity;
  65 import org.openjdk.jmc.common.util.StateToolkit;
  66 import org.openjdk.jmc.flightrecorder.ui.FlightRecorderUI;
  67 import org.openjdk.jmc.flightrecorder.ui.IPageContainer;
  68 import org.openjdk.jmc.flightrecorder.ui.ItemCollectionToolkit;
  69 import org.openjdk.jmc.flightrecorder.ui.StreamModel;
  70 import org.openjdk.jmc.flightrecorder.ui.common.FilterComponent;
  71 import org.openjdk.jmc.flightrecorder.ui.common.ImageConstants;
  72 import org.openjdk.jmc.flightrecorder.ui.common.ItemHistogram.HistogramSelection;
  73 import org.openjdk.jmc.flightrecorder.ui.common.ItemList;
  74 import org.openjdk.jmc.flightrecorder.ui.common.ItemList.ItemListBuilder;
  75 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
  76 import org.openjdk.jmc.flightrecorder.ui.selection.SelectionStoreActionToolkit;
  77 import org.openjdk.jmc.ui.column.ColumnManager;
  78 import org.openjdk.jmc.ui.column.ColumnManager.SelectionState;
  79 import org.openjdk.jmc.ui.column.ColumnMenusFactory;
  80 import org.openjdk.jmc.ui.column.TableSettings;
  81 import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
  82 
  83 class ItemListAndChart {
  84 
  85         private static final String LIST_SETTINGS = "listSettings"; //$NON-NLS-1$
  86         private static final String CHART_SETTINGS = "chartSettings"; //$NON-NLS-1$
  87         private static final String SELECTED_SUBTAB = "selectedSubTab"; //$NON-NLS-1$
  88         private static final String ITEM_LIST_FILTER = "itemListFilter"; //$NON-NLS-1$
  89         private ItemList itemList;
  90         private final ItemChart itemChart;
  91         private MCContextMenuManager listContextMenuManager;
  92         private final CTabFolder tabFolder;
  93         private final Consumer<IItemCollection> selectionListener;
  94         private final CTabItem listTab;
  95         private final IPageContainer controller;
  96         private String pageName;
  97         private ToolBarManager toolBarManager;
  98         private FilterComponent itemListFilterComponent;
  99         private StreamModel streamModel;
 100         private FormToolkit toolkit;
 101         private IItemFilter inputFilter;
 102         private IItemFilter itemListFilter;
 103 
 104         private IItemCollection listItems;
 105 
 106         public ItemListAndChart(FormToolkit toolkit, IPageContainer controller, StreamModel streamModel, IItemFilter filter,
 107                         IItemFilter itemListFilter, String pageName, Composite parent, IState state,
 108                         AttributeComponentConfiguration acc, Consumer<IItemCollection> selectionListener) {
 109 
 110                 this.toolkit = toolkit;
 111                 this.controller = controller;
 112                 this.streamModel = streamModel;
 113                 this.inputFilter = filter;
 114                 this.itemListFilter = itemListFilter;
 115                 this.pageName = pageName;
 116                 this.selectionListener = selectionListener;
 117                 tabFolder = new CTabFolder(parent, SWT.NONE);
 118                 toolkit.adapt(tabFolder);
 119                 toolBarManager = new ToolBarManager(SWT.HORIZONTAL);
 120                 tabFolder.setTopRight(toolBarManager.createControl(tabFolder));
 121 
 122                 listTab = new CTabItem(tabFolder, SWT.NONE);
 123                 listTab.setText(Messages.ITEMHANDLER_LIST_TITLE);
 124                 listTab.setImage(FlightRecorderUI.getDefault().getImage(ImageConstants.ICON_TABLE));
 125                 listTab.setToolTipText(Messages.ITEMHANDLER_LIST_DESCRIPTION);
 126                 buildList(state, acc);
 127 
 128                 CTabItem chartTab = new CTabItem(tabFolder, SWT.NONE);
 129                 chartTab.setText(Messages.ITEMHANDLER_CHART_TITLE);
 130                 chartTab.setImage(FlightRecorderUI.getDefault().getImage(ImageConstants.ICON_CHART_BAR));
 131                 chartTab.setToolTipText(Messages.ITEMHANDLER_CHART_DESCRIPTION);
 132                 itemChart = new ItemChart(tabFolder, toolkit, pageName, acc,
 133                                 state != null ? state.getChild(CHART_SETTINGS) : null, controller);
 134                 chartTab.setControl(itemChart.getControl());
 135 
 136                 tabFolder.setSelection(StateToolkit.readInt(state, SELECTED_SUBTAB, 0));
 137         }
 138 
 139         private void buildList(IState state, AttributeComponentConfiguration acc) {
 140                 listItems = streamModel.getItems().apply(inputFilter);
 141 
 142                 ItemListBuilder itemListBuilder = new ItemListBuilder();
 143 
 144                 acc.getAllAttributes().entrySet().forEach(entry -> {
 145                         String combinedId = entry.getKey();
 146                         IAttribute<?> a = entry.getValue();
 147                         ContentType<?> contentType = a.getContentType();
 148                         IMemberAccessor<?, IItem> accessor = ItemToolkit.accessor(a);
 149                         itemListBuilder.addColumn(combinedId, a.getName(), a.getDescription(),
 150                                         contentType instanceof LinearKindOfQuantity, accessor);
 151 
 152                 });
 153 
 154                 // FIXME: Should we use the state here, if the columns have been updated?
 155                 // FIXME: Should we change the column state if the user explicitly has configured the columns?
 156                 TableSettings itemListSettings = TableSettings.forStateAndColumns(
 157                                 state != null ? state.getChild(LIST_SETTINGS) : null, acc.getAllAttributes().keySet(),
 158                                 acc.getCommonAttributes().keySet());
 159 
 160                 Composite listComposite = toolkit.createComposite(tabFolder);
 161                 listComposite.setLayout(GridLayoutFactory.swtDefaults().create());
 162                 itemList = itemListBuilder.buildWithoutBorder(listComposite, itemListSettings);
 163                 listTab.setControl(listComposite);
 164                 itemList.getManager().getViewer()
 165                                 .addSelectionChangedListener(e -> selectionListener.accept(getListSelection()));
 166                 ColumnManager columnsManager = itemList.getManager();
 167                 listContextMenuManager = MCContextMenuManager.create(columnsManager.getViewer().getControl());
 168 
 169                 ColumnMenusFactory.addDefaultMenus(columnsManager, listContextMenuManager);
 170                 SelectionStoreActionToolkit.addSelectionStoreActions(controller.getSelectionStore(), itemList,
 171                                 NLS.bind(Messages.ITEMHANDLER_LOG_SELECTION, pageName), listContextMenuManager);
 172 
 173                 itemListFilterComponent = FilterComponent.createFilterComponent(itemList, itemListFilter, listItems,
 174                                 controller.getSelectionStore()::getSelections, this::onFilterChange);
 175                 listContextMenuManager.add(itemListFilterComponent.getShowFilterAction());
 176                 listContextMenuManager.add(itemListFilterComponent.getShowSearchAction());
 177                 if (state != null) {
 178                         itemListFilterComponent.loadState(state.getChild(ITEM_LIST_FILTER));
 179                 }
 180                 onFilterChange(itemListFilter);
 181         }
 182 
 183         private void onFilterChange(IItemFilter itemListFilter) {
 184                 this.itemListFilter = itemListFilter;
 185                 itemListFilterComponent.filterChangeHelper(itemListFilter, itemList, listItems.apply(inputFilter));
 186         }
 187 
 188         private IItemCollection getListSelection() {
 189                 return ItemCollectionToolkit.build(itemList.getSelection().get());
 190         }
 191 
 192         public void saveState(IWritableState state) {
 193                 StateToolkit.writeInt(state, SELECTED_SUBTAB, tabFolder.getSelectionIndex());
 194                 itemList.getManager().getSettings().saveState(state.createChild(LIST_SETTINGS));
 195                 itemChart.saveState(state.createChild(CHART_SETTINGS));
 196                 itemListFilterComponent.saveState(state.createChild(ITEM_LIST_FILTER));
 197         }
 198 
 199         public Control getControl() {
 200                 return tabFolder;
 201         }
 202 
 203         void setVisibleRange(IRange<IQuantity> visibleRange) {
 204                 itemChart.setVisibleRange(visibleRange);
 205         }
 206 
 207         IRange<IQuantity> getVisibleRange() {
 208                 return itemChart.getVisibleRange();
 209         }
 210 
 211         void setListSelectionState(SelectionState state) {
 212                 itemList.getManager().setSelectionState(state);
 213         }
 214 
 215         SelectionState getListSelectionState() {
 216                 return itemList.getManager().getSelectionState();
 217         }
 218 
 219         void setTabFolderIndex(int index) {
 220                 tabFolder.setSelection(index);
 221         }
 222 
 223         int getTabFolderIndex() {
 224                 return tabFolder.getSelectionIndex();
 225         }
 226 
 227         IItemFilter getItemListFilter() {
 228                 return itemListFilter;
 229         }
 230 
 231         // FIXME: Would like to merge the menu managers and the menu consumers.
 232         List<IContributionManager> getMenuManagers() {
 233                 return Arrays.asList(listContextMenuManager, itemChart.getMenuManager());
 234         }
 235 
 236         List<TriConsumer<String, ImageDescriptor, IMenuListener>> getMenuConsumers() {
 237                 // NOTE: Not inlined because some versions of Eclipse complain otherwise.
 238                 TriConsumer<String, ImageDescriptor, IMenuListener> menuConsumer = this::addMenuListenerAction;
 239                 return Arrays.asList(menuConsumer);
 240         }
 241 
 242         private void addMenuListenerAction(String text, ImageDescriptor image, IMenuListener menuListener) {
 243                 MCContextMenuManager bm = MCContextMenuManager.create(tabFolder);
 244                 bm.setRemoveAllWhenShown(true);
 245                 bm.addMenuListener(menuListener);
 246                 Action a = new Action(text, image) {
 247                         @Override
 248                         public void run() {
 249                                 tabFolder.getMenu().setVisible(true);
 250                         }
 251                 };
 252                 toolBarManager.add(a);
 253                 toolBarManager.update(true);
 254         }
 255 
 256         public void update(
 257                 IItemCollection filteredItems, IRange<IQuantity> currentRange, HistogramSelection histogramSelection,
 258                 Boolean grouped) {
 259                 listItems = histogramSelection != null && histogramSelection.getRowCount() > 0 ? histogramSelection.getItems()
 260                                 : filteredItems;
 261                 itemList.show(ItemCollectionToolkit.filterIfNotNull(listItems, itemListFilter));
 262                 itemChart.update(filteredItems, currentRange, histogramSelection, grouped);
 263         }
 264 
 265         public void onUseRange(Boolean useRange) {
 266                 itemChart.onUseRange(useRange);
 267         }
 268 }