From 0a08e17dc6c08b51fa06e296a8980ed983b94c86 Mon Sep 17 00:00:00 2001 From: Patrick Tasse Date: Mon, 7 Mar 2016 17:12:18 -0500 Subject: [PATCH] tmf: Add collapsible event table header bar with applied filter labels - The manual toggling of the event table header mode (search/filter) is removed. - When a search filter is applied, the header row icon is changed to a "Add as Filter" icon. Tool tip support is added for this icon. Clicking the icon will apply the search as a filter, and clear the search filter. - Ctrl+Enter shortcut can be used to trigger the 'Add as Filter' action. It can also be used instead of Enter while in a header cell 'search' text editor to apply the regex immediately as a filter instead of as a search filter. - The 'Show Filter Bar' and 'Show Search Bar' actions in the header row context menu are removed, and are replaced with the 'Add as Filter' action when there is a search filter applied. - If search filters are currently applied on multiple columns, they will each be applied as separate filters. - The 'Apply Preset Filter...' and 'Collapse Events' actions now add a filter to the currently applied filters instead of replacing them. - Implement TmfFilterObjectNode so that any external filter object can be included in the filter tree model. - The collapse filter is handled separately from other filters. It must be invoked only on events which first match every other applied filter. This allows for filtered events to be collapsed even if they are not contiguous as unfiltered events. - Implement TmfCollapseFilter.toString(). - Implement TmfEventsTableHeader. This is a header bar composite displayed above the event table when any filter is applied. - The header bar has one label for every filter that is currently applied. The label name describes the filter. If the filter was created from the table search/filter row, then clicking the label will set the table highlighting to this label's regex (if no search filter is currently applied). - Clicking the 'remove' icon on any label will remove this particular filter. If no filter remains, the header bar will be hidden. - Pressing the Delete key will clear all filter highlighting (if no search filter is currently applied). - When the header bar is visible, clicking its background or collapse icon will toggle its collapsed/expanded state to save space. Each filter label name will be set as a tool tip for the smaller label. - A NullPointerException is fixed when cancelling a search before its event request has been created. Change-Id: I40cc3e3389f1ce43145e2a6025ed1c2a99d029e8 Signed-off-by: Patrick Tasse Reviewed-on: https://git.eclipse.org/r/67928 Reviewed-by: Hudson CI Reviewed-by: Marc-Andre Laperle Tested-by: Marc-Andre Laperle --- .../tmf/core/filter/TmfCollapseFilter.java | 7 +- .../filter/model/TmfFilterObjectNode.java | 89 +++++ .../viewers/events/FilterColorEditorTest.java | 39 +- .../icons/elcl16/filter_add.gif | Bin 0 -> 342 bytes .../icons/ovr16/delete_ovr.gif | Bin 0 -> 242 bytes .../internal/tmf/ui/Messages.java | 4 +- .../internal/tmf/ui/messages.properties | 4 +- .../tmf/ui/viewers/events/TmfEventsCache.java | 43 +- .../tmf/ui/viewers/events/TmfEventsTable.java | 377 +++++++++++------- .../viewers/events/TmfEventsTableHeader.java | 249 ++++++++++++ 10 files changed, 640 insertions(+), 172 deletions(-) create mode 100644 tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/filter/model/TmfFilterObjectNode.java create mode 100644 tmf/org.eclipse.tracecompass.tmf.ui/icons/elcl16/filter_add.gif create mode 100644 tmf/org.eclipse.tracecompass.tmf.ui/icons/ovr16/delete_ovr.gif create mode 100644 tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTableHeader.java diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/filter/TmfCollapseFilter.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/filter/TmfCollapseFilter.java index e937676e40..f37f1042f3 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/filter/TmfCollapseFilter.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/filter/TmfCollapseFilter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2014 Ericsson + * Copyright (c) 2014, 2016 Ericsson * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which @@ -113,6 +113,11 @@ public class TmfCollapseFilter implements ITmfFilterTreeNode { throw new UnsupportedOperationException(); } + @Override + public String toString() { + return COLLAPSE_NODE_NAME; + } + @Override public String toString(boolean explicit) { return COLLAPSE_NODE_NAME + " [" + fPrevEvent + ']'; //$NON-NLS-1$ diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/filter/model/TmfFilterObjectNode.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/filter/model/TmfFilterObjectNode.java new file mode 100644 index 0000000000..090c4266b0 --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/filter/model/TmfFilterObjectNode.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2016 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Patrick Tasse - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.tracecompass.tmf.core.filter.model; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; +import org.eclipse.tracecompass.tmf.core.filter.ITmfFilter; + +/** + * Filter node that contains its own filter object + * + * @version 1.0 + * @author Patrick Tasse + * @since 2.0 + */ +public class TmfFilterObjectNode extends TmfFilterTreeNode { + + /** filter node name */ + public static final String NODE_NAME = "FILTEROBJECT"; //$NON-NLS-1$ + /** name attribute name */ + public static final String NAME_ATTR = "name"; //$NON-NLS-1$ + + private final ITmfFilter fFilter; + + /** + * @param filter the filter object + */ + public TmfFilterObjectNode(ITmfFilter filter) { + super(null); + fFilter = filter; + } + + /** + * @param parent the parent node + * @param filter the filter object + */ + public TmfFilterObjectNode(ITmfFilterTreeNode parent, ITmfFilter filter) { + super(parent); + fFilter = filter; + } + + /** + * @return the filter object + */ + public ITmfFilter getFilter() { + return fFilter; + } + + @Override + public String getNodeName() { + return NODE_NAME; + } + + @Override + public boolean matches(ITmfEvent event) { + // There should be at most one child + for (ITmfFilterTreeNode node : getChildren()) { + if (node.matches(event)) { + return true; + } + } + return false; + } + + @Override + public List getValidChildren() { + return new ArrayList<>(0); + } + + @Override + public String toString(boolean explicit) { + if (fFilter instanceof ITmfFilterTreeNode) { + return ((ITmfFilterTreeNode) fFilter).toString(explicit); + } + return fFilter.toString(); + } +} diff --git a/tmf/org.eclipse.tracecompass.tmf.ui.swtbot.tests/src/org/eclipse/tracecompass/tmf/ui/swtbot/tests/viewers/events/FilterColorEditorTest.java b/tmf/org.eclipse.tracecompass.tmf.ui.swtbot.tests/src/org/eclipse/tracecompass/tmf/ui/swtbot/tests/viewers/events/FilterColorEditorTest.java index 08eb62c902..e24c65d292 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui.swtbot.tests/src/org/eclipse/tracecompass/tmf/ui/swtbot/tests/viewers/events/FilterColorEditorTest.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui.swtbot.tests/src/org/eclipse/tracecompass/tmf/ui/swtbot/tests/viewers/events/FilterColorEditorTest.java @@ -39,6 +39,7 @@ import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swtbot.eclipse.finder.SWTWorkbenchBot; import org.eclipse.swtbot.eclipse.finder.widgets.SWTBotEditor; import org.eclipse.swtbot.swt.finder.junit.SWTBotJunit4ClassRunner; +import org.eclipse.swtbot.swt.finder.keyboard.Keystrokes; import org.eclipse.swtbot.swt.finder.utils.SWTBotPreferences; import org.eclipse.swtbot.swt.finder.widgets.SWTBotTable; import org.eclipse.tracecompass.tmf.core.tests.TmfCoreTestPlugin; @@ -243,42 +244,52 @@ public class FilterColorEditorTest { */ @Test public void testSwitchToFilter() { - final Rectangle cellBounds = SWTBotUtils.getCellBounds(fTableBot.widget, ROW, TIMESTAMP_COLUMN); + Rectangle cellBounds = SWTBotUtils.getCellBounds(fTableBot.widget, ROW, TIMESTAMP_COLUMN); ImageHelper before = ImageHelper.grabImage(cellBounds); - // enter regex in message column + // enter regex in Timestamp column fTableBot.click(0, TIMESTAMP_COLUMN); fBot.text().typeText("00\n", 100); // make sure matching column is not selected fTableBot.select(ROW - 1); - ImageHelper after = ImageHelper.grabImage(cellBounds); - // toggle filter mode + ImageHelper afterSearch = ImageHelper.grabImage(cellBounds); + // click Add as Filter fTableBot.click(0, 0); - fBot.waitUntil(ConditionHelpers.isTableCellFilled(fTableBot, "", 0, 1)); + fBot.waitUntil(ConditionHelpers.isTableCellFilled(fTableBot, "", 0, TIMESTAMP_COLUMN)); //TODO: We need a better way to make sure that the table is done updating SWTBotUtils.delay(2000); + // the bounds have changed after applying the filter + cellBounds = SWTBotUtils.getCellBounds(fTableBot.widget, ROW, TIMESTAMP_COLUMN); ImageHelper afterFilter = ImageHelper.grabImage(cellBounds); + // press DEL to clear highlighting + fTableBot.pressShortcut(Keystrokes.DELETE); + ImageHelper afterClear = ImageHelper.grabImage(cellBounds); List beforeLine = before.getPixelRow(2); - List afterLine = after.getPixelRow(2); + List afterSearchLine = afterSearch.getPixelRow(2); List afterFilterLine = afterFilter.getPixelRow(2); + List afterClearLine = afterClear.getPixelRow(2); - assertEquals(beforeLine.size(), afterLine.size()); + assertEquals(beforeLine.size(), afterSearchLine.size()); assertEquals(beforeLine.size(), afterFilterLine.size()); + assertEquals(beforeLine.size(), afterClearLine.size()); for (int i = 0; i < beforeLine.size(); i++) { - RGB afterFilterPixel = afterFilterLine.get(i); RGB beforePixel = beforeLine.get(i); - RGB afterPixel = afterLine.get(i); + RGB afterSearchPixel = afterSearchLine.get(i); + RGB afterFilterPixel = afterFilterLine.get(i); + RGB afterClearPixel = afterClearLine.get(i); - assertEquals(beforePixel, afterFilterPixel); - if (!afterPixel.equals(fHighlight)) { - assertEquals(beforePixel, afterPixel); + assertEquals(afterSearchPixel, afterFilterPixel); + assertEquals(beforePixel, afterClearPixel); + if (!afterSearchPixel.equals(fHighlight)) { + assertEquals(beforePixel, afterSearchPixel); } else { assertNotEquals(fHighlight, beforePixel); } } - assertEquals(beforeLine, afterFilterLine); - assertNotEquals(afterLine, beforeLine); + assertEquals(afterSearchLine, afterFilterLine); + assertEquals(beforeLine, afterClearLine); + assertNotEquals(afterSearchLine, beforeLine); } /** diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/icons/elcl16/filter_add.gif b/tmf/org.eclipse.tracecompass.tmf.ui/icons/elcl16/filter_add.gif new file mode 100644 index 0000000000000000000000000000000000000000..a8810360075dd6fdca17150f69d5f1711fe2d9cc GIT binary patch literal 342 zcmZ?wbhEHb6krfwSgOfj;S%c`6&s#XV_FtsT^F@1Tfm_;Ze_mUs(iuxvX*rvLhC_D zIJI_)YfsX;QlXgErCX{*Lb^AXx6InvAi4{P8b$qQJMTW(9J@d9L>vuXbSOWlmoUiBr literal 0 HcmV?d00001 diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/icons/ovr16/delete_ovr.gif b/tmf/org.eclipse.tracecompass.tmf.ui/icons/ovr16/delete_ovr.gif new file mode 100644 index 0000000000000000000000000000000000000000..8c72897d016b9d355e7de21b94a74fb8a466d9e4 GIT binary patch literal 242 zcmZ?wbhEHbWMN=oSgOx(+}QL>Vab=`lFvmYpNmUxXz4%DF}bf}d|cDunVH>FGrOY- z+D{EEKZPcINzVS9ob?$uHn=&y~n$8D`1 v2l;<(sQXVkQ2fcl$i-mKpu+$JAU`p%@gMNcEO{wov^eNlfKZv9BZD;nkVJ!D literal 0 HcmV?d00001 diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java index 5ca1d440a5..b40b3a812c 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java @@ -41,6 +41,7 @@ public class Messages extends NLS { public static String MarkerEvent_Bookmarks; public static String MarkerEvent_LostEvents; + public static String TmfEventsTable_AddAsFilterText; public static String TmfEventsTable_AddBookmarkActionText; public static String TmfEventsTable_ApplyPresetFilterMenuName; public static String TmfEventsTable_ClearFiltersActionText; @@ -48,7 +49,6 @@ public class Messages extends NLS { public static String TmfEventsTable_ContentColumnHeader; public static String TmfEventsTable_CopyToClipboardActionText; public static String TmfEventsTable_Export_to_text; - public static String TmfEventsTable_FilterHint; public static String TmfEventsTable_HideRawActionText; public static String TmfEventsTable_HideTableActionText; public static String TmfEventsTable_MultipleBookmarksToolTip; @@ -62,9 +62,7 @@ public class Messages extends NLS { public static String TmfEventsTable_SearchHint; public static String TmfEventsTable_SearchingJobName; public static String TmfEventsTable_ShowAll; - public static String TmfEventsTable_ShowFilterBarActionText; public static String TmfEventsTable_ShowRawActionText; - public static String TmfEventsTable_ShowSearchBarActionText; public static String TmfEventsTable_ShowTableActionText; public static String TmfEventsTable_SourceColumnHeader; public static String TmfEventsTable_TimestampColumnHeader; diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties index 4dbccf37a8..862f4cc989 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties @@ -31,6 +31,7 @@ ManageCustomParsersDialog_NewButtonLabel=New... ManageCustomParsersDialog_TextButtonLabel=Text # org.eclipse.tracecompass.tmf.ui.viewers.events +TmfEventsTable_AddAsFilterText=Add as Filter (Ctrl+Enter) TmfEventsTable_AddBookmarkActionText=Add Bookmark... TmfEventsTable_ApplyPresetFilterMenuName=Apply Preset Filter... TmfEventsTable_ClearFiltersActionText=Clear Filters @@ -38,7 +39,6 @@ TmfEventsTable_CollapseFilterMenuName=Collapse Events TmfEventsTable_ContentColumnHeader=Content TmfEventsTable_CopyToClipboardActionText=Copy to Clipboard TmfEventsTable_Export_to_text=Export To Text... -TmfEventsTable_FilterHint= TmfEventsTable_HideRawActionText=Hide Raw TmfEventsTable_HideTableActionText=Hide Table TmfEventsTable_MultipleBookmarksToolTip=Multiple bookmarks at this location @@ -52,9 +52,7 @@ TmfEventsTable_RemoveBookmarkActionText=Remove Bookmark TmfEventsTable_SearchHint= TmfEventsTable_SearchingJobName=Searching... TmfEventsTable_ShowAll=Show All -TmfEventsTable_ShowFilterBarActionText=Show Filter Bar TmfEventsTable_ShowRawActionText=Show Raw -TmfEventsTable_ShowSearchBarActionText=Show Search Bar TmfEventsTable_ShowTableActionText=Show Table TmfEventsTable_SourceColumnHeader=Source TmfEventsTable_TimestampColumnHeader=Timestamp diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsCache.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsCache.java index 7736b388de..4be89df16f 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsCache.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsCache.java @@ -130,6 +130,7 @@ public class TmfEventsCache { private final TmfEventsTable fTable; private ITmfFilter fFilter; private final List fFilterIndex = new ArrayList<>(); // contains the event rank at each 'cache size' filtered events + private boolean fCollapseFilterEnabled = false; /** * Constructor for the event cache @@ -176,9 +177,13 @@ public class TmfEventsCache { * * @param filter * The ITmfFilter to apply. + * @param collapseFilterEnabled + * true if the collapse filter is enabled + * @since 2.0 */ - public void applyFilter(ITmfFilter filter) { + public void applyFilter(ITmfFilter filter, boolean collapseFilterEnabled) { fFilter = filter; + fCollapseFilterEnabled = collapseFilterEnabled; clear(); } @@ -188,6 +193,7 @@ public class TmfEventsCache { */ public void clearFilter() { fFilter = null; + fCollapseFilterEnabled = false; clear(); } @@ -309,6 +315,7 @@ public class TmfEventsCache { class DataRequest extends TmfEventRequest { ITmfFilter requestFilter; + TmfCollapseFilter requestCollapsedFilter; int requestRank; int requestIndex; @@ -318,6 +325,7 @@ public class TmfEventsCache { requestFilter = reqFilter; requestRank = start; requestIndex = index; + requestCollapsedFilter = fCollapseFilterEnabled ? new TmfCollapseFilter() : null; } @Override @@ -332,7 +340,9 @@ public class TmfEventsCache { } requestRank++; if (requestFilter.matches(event)) { - requestIndex++; + if (requestCollapsedFilter == null || requestCollapsedFilter.matches(event)) { + requestIndex++; + } } } @@ -411,6 +421,7 @@ public class TmfEventsCache { TmfEventRequest.ExecutionType.FOREGROUND) { private int count = 0; private long rank = startIndex; + private TmfCollapseFilter collapseFilter = fCollapseFilterEnabled ? new TmfCollapseFilter() : null; @Override public void handleData(ITmfEvent event) { // If the job is canceled, cancel the request so waitForCompletion() will unlock @@ -419,20 +430,22 @@ public class TmfEventsCache { return; } super.handleData(event); - if (((fFilter == null) || fFilter.matches(event)) && (skipCount-- <= 0)) { - synchronized (TmfEventsCache.this) { - if (monitor.isCanceled()) { - return; + if ((fFilter == null) || fFilter.matches(event)) { + if (collapseFilter == null || collapseFilter.matches(event)) { + if (skipCount-- <= 0) { + synchronized (TmfEventsCache.this) { + if (monitor.isCanceled()) { + return; + } + fCache[count] = new CachedEvent(event, rank); + count++; + fCacheEndIndex++; + } + if (fFilter != null) { + fTable.cacheUpdated(false); + } } - fCache[count] = new CachedEvent(event, rank); - count++; - fCacheEndIndex++; - } - if (fFilter != null) { - fTable.cacheUpdated(false); - } - } else if (((fFilter != null) && !fFilter.matches(event)) && (skipCount <= 0)) { // TODO fix duplicated call to matches() - if ((count > 0) && (fFilter instanceof TmfCollapseFilter)) { + } else if ((count > 0) && (skipCount <= 0)) { fCache[count - 1].repeatCount++; } } diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTable.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTable.java index 43f831eea4..1b4287e27a 100644 --- a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTable.java +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTable.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2010, 2015 Ericsson + * Copyright (c) 2010, 2016 Ericsson and others. * * All rights reserved. This program and the accompanying materials are * made available under the terms of the Eclipse Public License v1.0 which @@ -138,9 +138,10 @@ import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfModelLookup; import org.eclipse.tracecompass.tmf.core.event.lookup.ITmfSourceLookup; import org.eclipse.tracecompass.tmf.core.filter.ITmfFilter; import org.eclipse.tracecompass.tmf.core.filter.model.ITmfFilterTreeNode; -import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterAndNode; import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterMatchesNode; import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterNode; +import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterObjectNode; +import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterRootNode; import org.eclipse.tracecompass.tmf.core.request.ITmfEventRequest.ExecutionType; import org.eclipse.tracecompass.tmf.core.request.TmfEventRequest; import org.eclipse.tracecompass.tmf.core.resources.ITmfMarker; @@ -161,6 +162,7 @@ import org.eclipse.tracecompass.tmf.core.trace.TmfTraceManager; import org.eclipse.tracecompass.tmf.core.trace.location.ITmfLocation; import org.eclipse.tracecompass.tmf.core.util.Pair; import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsCache.CachedEvent; +import org.eclipse.tracecompass.tmf.ui.viewers.events.TmfEventsTableHeader.IEventsTableHeaderListener; import org.eclipse.tracecompass.tmf.ui.viewers.events.columns.TmfEventTableColumn; import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSetting; import org.eclipse.tracecompass.tmf.ui.views.colors.ColorSettingsManager; @@ -220,9 +222,9 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS private static final Image SEARCH_MATCH_BOOKMARK_IMAGE = Activator.getDefault().getImageFromPath( "icons/elcl16/search_match_bookmark.gif"); //$NON-NLS-1$ private static final Image FILTER_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_items.gif"); //$NON-NLS-1$ + private static final Image FILTER_ADD_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/filter_add.gif"); //$NON-NLS-1$ private static final Image STOP_IMAGE = Activator.getDefault().getImageFromPath("icons/elcl16/stop.gif"); //$NON-NLS-1$ private static final String SEARCH_HINT = Messages.TmfEventsTable_SearchHint; - private static final String FILTER_HINT = Messages.TmfEventsTable_FilterHint; private static final int MAX_CACHE_SIZE = 1000; private static final int MARGIN_COLUMN_INDEX = 0; @@ -515,15 +517,25 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS if (item == null) { return; } - final Long rank = (Long) item.getData(Key.RANK); - if (rank == null) { - return; - } - final String tooltipText = (String) item.getData(Key.BOOKMARK); - final Rectangle bounds = item.getImageBounds(0); - bounds.width = BOOKMARK_IMAGE.getBounds().width; - if (!bounds.contains(event.x, event.y)) { - return; + String text; + if (fTable.indexOf(item) == 0) { + if (fHeaderState == HeaderState.SEARCH && item.getBounds(0).contains(event.x, event.y)) { + text = Messages.TmfEventsTable_AddAsFilterText; + } else { + return; + } + } else { + final Long rank = (Long) item.getData(Key.RANK); + if (rank == null) { + return; + } + final String tooltipText = (String) item.getData(Key.BOOKMARK); + final Rectangle bounds = item.getImageBounds(0); + bounds.width = BOOKMARK_IMAGE.getBounds().width; + if (!bounds.contains(event.x, event.y)) { + return; + } + text = rank.toString() + (tooltipText != null ? ": " + tooltipText : EMPTY_STRING); //$NON-NLS-1$ } if ((tooltipShell != null) && !tooltipShell.isDisposed()) { tooltipShell.dispose(); @@ -534,7 +546,6 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS layout.marginWidth = 2; tooltipShell.setLayout(layout); final Label label = new Label(tooltipShell, SWT.WRAP); - String text = rank.toString() + (tooltipText != null ? ": " + tooltipText : EMPTY_STRING); //$NON-NLS-1$ label.setForeground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); label.setBackground(PlatformUI.getWorkbench().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); label.setText(text); @@ -630,11 +641,12 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS * @author Patrick Tasse */ public static enum HeaderState { - /** A search is being run */ - SEARCH, + /** No search filter is applied + * @since 2.0*/ + NO_SEARCH, - /** A filter is applied */ - FILTER + /** A search filter is applied */ + SEARCH } interface Direction { @@ -646,6 +658,9 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS // Table data // ------------------------------------------------------------------------ + /** The header bar */ + private TmfEventsTableHeader fHeaderBar; + /** The virtual event table */ protected TmfVirtualTable fTable; @@ -654,7 +669,7 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS private TmfRawEventViewer fRawViewer; private ITmfTrace fTrace; private volatile boolean fPackDone = false; - private HeaderState fHeaderState = HeaderState.SEARCH; + private HeaderState fHeaderState = HeaderState.NO_SEARCH; private long fSelectedRank = -1; private long fSelectedBeginRank = -1; private ITmfTimestamp fSelectedBeginTimestamp = null; @@ -668,6 +683,7 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS private final Object fFilterSyncObj = new Object(); private SearchThread fSearchThread; private final Object fSearchSyncObj = new Object(); + private boolean fCollapseFilterEnabled = false; /** * List of selection change listeners (element type: @@ -788,7 +804,7 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS super("TmfEventsTable"); //$NON-NLS-1$ fComposite = new Composite(parent, SWT.NONE); - final GridLayout gl = new GridLayout(1, false); + GridLayout gl = new GridLayout(1, false); gl.marginHeight = 0; gl.marginWidth = 0; gl.verticalSpacing = 0; @@ -797,9 +813,45 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS fSashForm = new SashForm(fComposite, SWT.HORIZONTAL); fSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + // Create a composite for the table and its header bar + Composite tableComposite = new Composite(fSashForm, SWT.NONE); + tableComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + gl = new GridLayout(1, false); + gl.marginHeight = 0; + gl.marginWidth = 0; + gl.verticalSpacing = 0; + tableComposite.setLayout(gl); + + // Create an events table header bar + fHeaderBar = new TmfEventsTableHeader(tableComposite, SWT.NONE, new IEventsTableHeaderListener() { + @Override + public void filterSelected(ITmfFilter filter) { + if (filter instanceof TmfFilterMatchesNode) { + TmfFilterMatchesNode matchFilter = (TmfFilterMatchesNode) filter; + for (TableColumn col : fTable.getColumns()) { + if (col.getData(Key.ASPECT) == matchFilter.getEventAspect()) { + col.setData(Key.FILTER_TXT, matchFilter.getRegex()); + } else { + col.setData(Key.FILTER_TXT, null); + } + } + fTable.refresh(); + fTable.redraw(); + } + } + + @Override + public void filterRemoved(ITmfFilter filter) { + for (TableColumn col : fTable.getColumns()) { + col.setData(Key.FILTER_TXT, null); + } + removeFilter(filter); + } + }); + // Create a virtual table final int style = SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI | SWT.FULL_SELECTION; - fTable = new TmfVirtualTable(fSashForm, style); + fTable = new TmfVirtualTable(tableComposite, style); // Set the table layout final GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true); @@ -1213,21 +1265,10 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS } }; - final IAction showSearchBarAction = new Action(Messages.TmfEventsTable_ShowSearchBarActionText) { + final IAction addAsFilterAction = new Action(Messages.TmfEventsTable_AddAsFilterText) { @Override public void run() { - fHeaderState = HeaderState.SEARCH; - fTable.refresh(); - fTable.redraw(); - } - }; - - final IAction showFilterBarAction = new Action(Messages.TmfEventsTable_ShowFilterBarActionText) { - @Override - public void run() { - fHeaderState = HeaderState.FILTER; - fTable.refresh(); - fTable.redraw(); + applySearchAsFilter(); } }; @@ -1281,10 +1322,8 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS public void menuAboutToShow(final IMenuManager manager) { if (fTable.getSelectionIndices().length == 1 && fTable.getSelectionIndices()[0] == 0) { // Right-click on header row - if (fHeaderState == HeaderState.FILTER) { - fTablePopupMenuManager.add(showSearchBarAction); - } else { - fTablePopupMenuManager.add(showFilterBarAction); + if (fHeaderState == HeaderState.SEARCH) { + fTablePopupMenuManager.add(addAsFilterAction); } return; } @@ -1364,7 +1403,7 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS } } - if (isCollapsible && !(fTable.getData(Key.FILTER_OBJ) instanceof TmfCollapseFilter)) { + if (isCollapsible && !fCollapseFilterEnabled) { fTablePopupMenuManager.add(collapseAction); fTablePopupMenuManager.add(new Separator()); } @@ -1575,10 +1614,12 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS for (int index = 0; index < fTable.getColumns().length; index++) { TableColumn column = fTable.getColumns()[index]; String regex = null; - if (fHeaderState == HeaderState.FILTER) { + if (fHeaderState == HeaderState.SEARCH) { + if (searchMatch) { + regex = (String) column.getData(Key.SEARCH_TXT); + } + } else { regex = (String) column.getData(Key.FILTER_TXT); - } else if (searchMatch) { - regex = (String) column.getData(Key.SEARCH_TXT); } if (regex != null) { String text = item.getText(index); @@ -1618,25 +1659,18 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS * The item to use as table header */ protected void setHeaderRowItemData(final TableItem item) { - String txtKey = null; - if (fHeaderState == HeaderState.SEARCH) { + if (fHeaderState == HeaderState.NO_SEARCH) { item.setImage(SEARCH_IMAGE); - txtKey = Key.SEARCH_TXT; - } else if (fHeaderState == HeaderState.FILTER) { - item.setImage(FILTER_IMAGE); - txtKey = Key.FILTER_TXT; + } else if (fHeaderState == HeaderState.SEARCH) { + item.setImage(FILTER_ADD_IMAGE); } item.setForeground(fGrayColor); // Ignore collapse and image column for (int i = EVENT_COLUMNS_START_INDEX; i < fTable.getColumns().length; i++) { final TableColumn column = fTable.getColumns()[i]; - final String filter = (String) column.getData(txtKey); + final String filter = (String) column.getData(Key.SEARCH_TXT); if (filter == null) { - if (fHeaderState == HeaderState.SEARCH) { - item.setText(i, SEARCH_HINT); - } else if (fHeaderState == HeaderState.FILTER) { - item.setText(i, FILTER_HINT); - } + item.setText(i, SEARCH_HINT); item.setForeground(i, fGrayColor); item.setFont(i, fFont); } else { @@ -1709,13 +1743,8 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS // Margin column selected if (item.getBounds(0).contains(point)) { if (fHeaderState == HeaderState.SEARCH) { - fHeaderState = HeaderState.FILTER; - } else if (fHeaderState == HeaderState.FILTER) { - fHeaderState = HeaderState.SEARCH; + applySearchAsFilter(); } - fTable.setSelection(0); - fTable.refresh(); - fTable.redraw(); return; } @@ -1735,19 +1764,12 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS column = fTable.getColumns()[columnIndex]; - String txtKey = null; - if (fHeaderState == HeaderState.SEARCH) { - txtKey = Key.SEARCH_TXT; - } else if (fHeaderState == HeaderState.FILTER) { - txtKey = Key.FILTER_TXT; - } - /* * The control that will be the editor must be a child of * the Table */ final Text newEditor = (Text) fTable.createTableEditorControl(Text.class); - final String headerString = (String) column.getData(txtKey); + final String headerString = (String) column.getData(Key.SEARCH_TXT); if (headerString != null) { newEditor.setText(headerString); } @@ -1766,12 +1788,15 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS if (e.character == SWT.CR) { updateHeader(newEditor.getText()); applyHeader(); - - /* - * Set focus on the table so that the next - * carriage return goes to the next result - */ - TmfEventsTable.this.getTable().setFocus(); + if ((e.stateMask & SWT.CTRL) != 0) { + applySearchAsFilter(); + } else { + /* + * Set focus on the table so that the next + * carriage return goes to the next result + */ + TmfEventsTable.this.getTable().setFocus(); + } } else if (e.character == SWT.ESC) { tableEditor.getEditor().dispose(); } @@ -1787,19 +1812,10 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS * returns true is value was changed */ private boolean updateHeader(final String regex) { - String objKey = null; - String txtKey = null; - if (fHeaderState == HeaderState.SEARCH) { - objKey = Key.SEARCH_OBJ; - txtKey = Key.SEARCH_TXT; - } else if (fHeaderState == HeaderState.FILTER) { - objKey = Key.FILTER_OBJ; - txtKey = Key.FILTER_TXT; - } if (regex.length() > 0) { try { Pattern.compile(regex); - if (regex.equals(column.getData(txtKey))) { + if (regex.equals(column.getData(Key.SEARCH_TXT))) { tableEditor.getEditor().dispose(); return false; } @@ -1807,8 +1823,8 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS ITmfEventAspect aspect = (ITmfEventAspect) column.getData(Key.ASPECT); filter.setEventAspect(aspect); filter.setRegex(regex); - column.setData(objKey, filter); - column.setData(txtKey, regex); + column.setData(Key.SEARCH_OBJ, filter); + column.setData(Key.SEARCH_TXT, regex); } catch (final PatternSyntaxException ex) { tableEditor.getEditor().dispose(); MessageDialog.openError(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), @@ -1816,50 +1832,37 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS return false; } } else { - if (column.getData(txtKey) == null) { + if (column.getData(Key.SEARCH_TXT) == null) { tableEditor.getEditor().dispose(); return false; } - column.setData(objKey, null); - column.setData(txtKey, null); + column.setData(Key.SEARCH_OBJ, null); + column.setData(Key.SEARCH_TXT, null); } return true; } private void applyHeader() { - if (fHeaderState == HeaderState.SEARCH) { - stopSearchThread(); - final TmfFilterAndNode filter = new TmfFilterAndNode(null); - for (final TableColumn col : fTable.getColumns()) { - final Object filterObj = col.getData(Key.SEARCH_OBJ); - if (filterObj instanceof ITmfFilterTreeNode) { - filter.addChild((ITmfFilterTreeNode) filterObj); - } - } - if (filter.getChildrenCount() > 0) { - fTable.setData(Key.SEARCH_OBJ, filter); - fTable.refresh(); - searchNext(); - fireSearchApplied(filter); - } else { - fTable.setData(Key.SEARCH_OBJ, null); - fTable.refresh(); - fireSearchApplied(null); - } - } else if (fHeaderState == HeaderState.FILTER) { - final TmfFilterAndNode filter = new TmfFilterAndNode(null); - for (final TableColumn col : fTable.getColumns()) { - final Object filterObj = col.getData(Key.FILTER_OBJ); - if (filterObj instanceof ITmfFilterTreeNode) { - filter.addChild((ITmfFilterTreeNode) filterObj); - } - } - if (filter.getChildrenCount() > 0) { - applyFilter(filter); - } else { - clearFilters(); + stopSearchThread(); + final TmfFilterRootNode filter = new TmfFilterRootNode(); + for (final TableColumn col : fTable.getColumns()) { + final Object filterObj = col.getData(Key.SEARCH_OBJ); + if (filterObj instanceof ITmfFilterTreeNode) { + filter.addChild((ITmfFilterTreeNode) filterObj); } } + if (filter.getChildrenCount() > 0) { + fHeaderState = HeaderState.SEARCH; + fTable.setData(Key.SEARCH_OBJ, filter); + fTable.refresh(); + searchNext(); + fireSearchApplied(filter); + } else { + fHeaderState = HeaderState.NO_SEARCH; + fTable.setData(Key.SEARCH_OBJ, null); + fTable.refresh(); + fireSearchApplied(null); + } tableEditor.getEditor().dispose(); } @@ -1875,19 +1878,28 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS fTable.refresh(); } else if (e.character == SWT.DEL) { if (fHeaderState == HeaderState.SEARCH) { + fHeaderState = HeaderState.NO_SEARCH; stopSearchThread(); for (final TableColumn column : fTable.getColumns()) { column.setData(Key.SEARCH_OBJ, null); column.setData(Key.SEARCH_TXT, null); + column.setData(Key.FILTER_TXT, null); } fTable.setData(Key.SEARCH_OBJ, null); fTable.refresh(); fireSearchApplied(null); - } else if (fHeaderState == HeaderState.FILTER) { - clearFilters(); + } else { + for (final TableColumn column : fTable.getColumns()) { + column.setData(Key.FILTER_TXT, null); + } + fTable.refresh(); } } else if (e.character == SWT.CR) { - if ((e.stateMask & SWT.SHIFT) == 0) { + if ((e.stateMask & SWT.CTRL) != 0) { + if (fHeaderState == HeaderState.SEARCH) { + applySearchAsFilter(); + } + } else if ((e.stateMask & SWT.SHIFT) == 0) { searchNext(); } else { searchPrevious(); @@ -1897,6 +1909,27 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS }); } + /** + * Apply the current search condition as a new filter. + * + * @since 2.0 + */ + protected void applySearchAsFilter() { + Object searchObj = fTable.getData(Key.SEARCH_OBJ); + if (searchObj instanceof ITmfFilter) { + ITmfFilter filter = (ITmfFilter) searchObj; + fTable.setData(Key.SEARCH_OBJ, null); + fireSearchApplied(null); + fHeaderState = HeaderState.NO_SEARCH; + for (final TableColumn col : fTable.getColumns()) { + col.setData(Key.FILTER_TXT, col.getData(Key.SEARCH_TXT)); + col.setData(Key.SEARCH_TXT, null); + col.setData(Key.SEARCH_OBJ, null); + } + applyFilter(filter); + } + } + /** * Send an event indicating a filter has been applied. * @@ -1950,7 +1983,7 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS } /** - * Apply a filter. + * Apply a filter. It is added to the existing filters. * * @param filter * The filter to apply @@ -1960,13 +1993,78 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS stopSearchThread(); fFilterMatchCount = 0; fFilterCheckCount = 0; - fCache.applyFilter(filter); + ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ); + if (rootFilter == null) { + rootFilter = new TmfFilterRootNode(); + } + if (filter instanceof TmfFilterRootNode) { + TmfFilterRootNode parentFilter = (TmfFilterRootNode) filter; + for (ITmfFilterTreeNode child : parentFilter.getChildren()) { + rootFilter.addChild(child); + } + } else if (filter instanceof TmfCollapseFilter) { + fCollapseFilterEnabled = true; + } else if (filter instanceof ITmfFilterTreeNode) { + rootFilter.addChild((ITmfFilterTreeNode) filter); + } else { + rootFilter.addChild(new TmfFilterObjectNode(filter)); + } + fCache.applyFilter(rootFilter, fCollapseFilterEnabled); + fHeaderBar.addFilter(filter); fTable.clearAll(); - fTable.setData(Key.FILTER_OBJ, filter); + fTable.setData(Key.FILTER_OBJ, rootFilter); /* +1 for header row, +2 for top and bottom filter status rows */ fTable.setItemCount(3); startFilterThread(); - fireFilterApplied(filter); + fireFilterApplied(rootFilter); + } + + /** + * Remove a filter. Any other existing filters remain applied. + * + * @param filter + * The filter to remove + * @since 2.0 + */ + protected void removeFilter(ITmfFilter filter) { + ITmfFilterTreeNode rootFilter = (ITmfFilterTreeNode) fTable.getData(Key.FILTER_OBJ); + if (rootFilter == null) { + return; + } + stopFilterThread(); + stopSearchThread(); + fFilterMatchCount = 0; + fFilterCheckCount = 0; + if (filter instanceof TmfCollapseFilter) { + fCollapseFilterEnabled = false; + } else if (filter instanceof ITmfFilterTreeNode) { + rootFilter.removeChild((ITmfFilterTreeNode) filter); + } else { + for (ITmfFilterTreeNode child : rootFilter.getChildren()) { + if (child instanceof TmfFilterObjectNode) { + if (((TmfFilterObjectNode) child).getFilter().equals(filter)) { + rootFilter.removeChild(child); + break; + } + } + } + } + if (!rootFilter.hasChildren() && !fCollapseFilterEnabled) { + clearFilters(); + return; + } + fCache.applyFilter(rootFilter, fCollapseFilterEnabled); + fHeaderBar.removeFilter(filter); + fTable.clearAll(); + fTable.setData(Key.FILTER_OBJ, rootFilter); + /* +1 for header row, +2 for top and bottom filter status rows */ + fTable.setItemCount(3); + startFilterThread(); + fireFilterApplied(rootFilter); + + // Set original width + fTable.getColumns()[MARGIN_COLUMN_INDEX].setWidth(0); + packMarginColumn(); } /** @@ -1979,6 +2077,8 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS stopFilterThread(); stopSearchThread(); fCache.clearFilter(); + fHeaderBar.clearFilters(); + fCollapseFilterEnabled = false; fTable.clearAll(); for (final TableColumn column : fTable.getColumns()) { column.setData(Key.FILTER_OBJ, null); @@ -2013,6 +2113,7 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS */ protected class FilterThread extends Thread { private final ITmfFilterTreeNode filter; + private TmfCollapseFilter collapseFilter = null; private TmfEventRequest request; private boolean refreshBusy = false; private boolean refreshPending = false; @@ -2034,6 +2135,9 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS if (fTrace == null) { return; } + if (fCollapseFilterEnabled) { + collapseFilter = new TmfCollapseFilter(); + } final int nbRequested = (int) (fTrace.getNbEvents() - fFilterCheckCount); if (nbRequested <= 0) { return; @@ -2048,15 +2152,15 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS } boolean refresh = false; if (filter.matches(event)) { - final long rank = fFilterCheckCount; - final int index = (int) fFilterMatchCount; - fFilterMatchCount++; - fCache.storeEvent(event, rank, index); - refresh = true; - } else { - if (filter instanceof TmfCollapseFilter) { + if (collapseFilter == null || collapseFilter.matches(event)) { + final long rank = fFilterCheckCount; + final int index = (int) fFilterMatchCount; + fFilterMatchCount++; + fCache.storeEvent(event, rank, index); + } else if (collapseFilter != null) { fCache.updateCollapsedEvent((int) fFilterMatchCount - 1); } + refresh = true; } if (refresh || (fFilterCheckCount % 100) == 0) { @@ -2394,7 +2498,9 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS @Override protected void canceling() { - request.cancel(); + if (request != null) { + request.cancel(); + } synchronized (fSearchSyncObj) { fSearchThread = null; } @@ -2482,8 +2588,7 @@ public class TmfEventsTable extends TmfComponent implements IGotoMarker, IColorS * search/filter row in TableColumn.pack() after having executed * TableItem.setImage(null) for other rows than search/filter row. */ - boolean isCollapseFilter = fTable.getData(Key.FILTER_OBJ) instanceof TmfCollapseFilter; - if (IS_LINUX && (i == 0) && isCollapseFilter) { + if (IS_LINUX && (i == 0) && fCollapseFilterEnabled) { column.setWidth(column.getWidth() + SEARCH_IMAGE.getBounds().width); } diff --git a/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTableHeader.java b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTableHeader.java new file mode 100644 index 0000000000..9703061fe5 --- /dev/null +++ b/tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTableHeader.java @@ -0,0 +1,249 @@ +/******************************************************************************* + * Copyright (c) 2016 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v1.0 which + * accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Patrick Tasse - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.tracecompass.tmf.ui.viewers.events; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.tracecompass.internal.tmf.ui.Activator; +import org.eclipse.tracecompass.tmf.core.filter.ITmfFilter; +import org.eclipse.tracecompass.tmf.core.filter.model.ITmfFilterTreeNode; +import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterNode; +import org.eclipse.tracecompass.tmf.core.filter.model.TmfFilterRootNode; + +/** + * Header bar for the events table. + * + * @since 2.0 + */ +public class TmfEventsTableHeader extends Composite { + + private static final Image COLLAPSED = Activator.getDefault().getImageFromPath("icons/ovr16/collapsed_ovr.gif"); //$NON-NLS-1$ + private static final Image EXPANDED = Activator.getDefault().getImageFromPath("icons/ovr16/expanded_ovr.gif"); //$NON-NLS-1$ + private static final Image DELETE = Activator.getDefault().getImageFromPath("icons/elcl16/delete_button.gif"); //$NON-NLS-1$ + private static final Image DELETE_SMALL = Activator.getDefault().getImageFromPath("icons/ovr16/delete_ovr.gif"); //$NON-NLS-1$ + private static final int DEFAULT_MARGIN = 3; + private static final int COLLAPSED_IMAGE_MARGIN = 2; + private static final int COLLAPSED_RIGHT_MARGIN = 32; + private static final RGB LABEL_BACKGROUND = new RGB(255, 255, 192); + private static final String TOOLTIP_KEY = "toolTip"; //$NON-NLS-1$ + + /** + * Interface for header bar call-backs. + */ + public interface IEventsTableHeaderListener { + /** + * A filter has been selected. + * + * @param filter + * the selected filter + */ + void filterSelected(ITmfFilter filter); + + /** + * A filter has been removed. + * + * @param filter + * the removed filter + */ + void filterRemoved(ITmfFilter filter); + } + + private final IEventsTableHeaderListener fListener; + private final RowLayout fLayout; + private final Color fLabelBackground; + private boolean fCollapsed = false; + + /** + * Constructor + * + * @param parent + * the parent composite + * @param style + * the style of widget to construct + * @param listener + * the listener to the header bar events + */ + public TmfEventsTableHeader(Composite parent, int style, IEventsTableHeaderListener listener) { + super(parent, style); + fListener = listener; + fLayout = new RowLayout(); + fLayout.marginTop = 0; + fLayout.marginBottom = 0; + fLayout.marginLeft = EXPANDED.getBounds().width; + setLayout(fLayout); + fLabelBackground = new Color(getDisplay(), LABEL_BACKGROUND); + getParent().addControlListener(new ControlAdapter() { + @Override + public void controlResized(ControlEvent e) { + getParent().layout(); + } + }); + addPaintListener(e -> { + if (fCollapsed) { + e.gc.drawImage(COLLAPSED, 0, 0); + } else { + e.gc.drawImage(EXPANDED, 0, 0); + } + }); + addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent e) { + toggle(); + } + }); + } + + @Override + public void dispose() { + super.dispose(); + fLabelBackground.dispose(); + } + + @Override + public Point computeSize(int wHint, int hHint, boolean changed) { + int height = fCollapsed && getChildren().length > 0 ? EXPANDED.getBounds().height : hHint; + return super.computeSize(getParent().getSize().x, height, changed); + } + + /** + * Add a filter to the header. + * + * @param filter + * the filter to add + */ + public void addFilter(ITmfFilter filter) { + if (filter instanceof TmfFilterRootNode) { + TmfFilterRootNode parentFilter = (TmfFilterRootNode) filter; + for (ITmfFilterTreeNode childFilter : parentFilter.getChildren()) { + addNewFilter(childFilter); + } + } else { + addNewFilter(filter); + } + fLayout.marginTop = 1; + fLayout.marginBottom = 1; + getParent().layout(true, true); + } + + /** + * Remove a filter from the header. + * + * @param filter + * the filter to remove + */ + public void removeFilter(ITmfFilter filter) { + for (Control control : getChildren()) { + if (filter.equals(control.getData())) { + control.dispose(); + break; + } + } + if (getChildren().length == 0) { + fLayout.marginTop = 0; + fLayout.marginBottom = 0; + } + getParent().layout(true, true); + } + + /** + * Clear all filters in the header. + */ + public void clearFilters() { + for (Control control : getChildren()) { + control.dispose(); + } + fLayout.marginTop = 0; + fLayout.marginBottom = 0; + getParent().layout(true, true); + } + + private void addNewFilter(ITmfFilter filter) { + CLabel label = new CLabel(this, SWT.SHADOW_OUT); + label.setBackground(fLabelBackground); + String text; + if (filter instanceof TmfFilterNode) { + text = ((TmfFilterNode) filter).getFilterName(); + label.setData(TOOLTIP_KEY, filter.toString()); + } else { + text = filter.toString(); + } + if (fCollapsed) { + label.setToolTipText(text); + label.setTopMargin(0); + label.setBottomMargin(0); + label.setRightMargin(COLLAPSED_RIGHT_MARGIN); + } else { + label.setImage(DELETE); + label.setText(text); + label.setToolTipText((String) label.getData(TOOLTIP_KEY)); + } + label.setData(filter); + label.addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent e) { + Rectangle bounds; + if (fCollapsed) { + bounds = new Rectangle(0, 0, 2 * COLLAPSED_IMAGE_MARGIN + DELETE_SMALL.getBounds().width, label.getBounds().height); + } else { + bounds = DELETE.getBounds(); + bounds.x += label.getLeftMargin(); + bounds.y = (label.getSize().y - bounds.height) / 2; + } + if (bounds.contains(e.x, e.y)) { + fListener.filterRemoved((ITmfFilter) label.getData()); + } else { + fListener.filterSelected((ITmfFilter) label.getData()); + getParent().layout(true, true); + } + } + }); + label.addPaintListener(e -> { + if (fCollapsed) { + e.gc.drawImage(DELETE_SMALL, COLLAPSED_IMAGE_MARGIN, COLLAPSED_IMAGE_MARGIN); + } + }); + } + + private void toggle() { + fCollapsed = !fCollapsed; + for (Control child : getChildren()) { + if (child instanceof CLabel) { + CLabel label = (CLabel) child; + if (fCollapsed) { + label.setImage(null); + label.setToolTipText(label.getText()); + label.setText(null); + label.setMargins(DEFAULT_MARGIN, 0, COLLAPSED_RIGHT_MARGIN, 0); + } else { + label.setImage(DELETE); + label.setText(label.getToolTipText()); + label.setToolTipText((String) label.getData(TOOLTIP_KEY)); + label.setMargins(DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN, DEFAULT_MARGIN); + } + } + } + getParent().layout(); + } +} -- 2.34.1