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 }