tmf: Add collapsible event table header bar with applied filter labels
authorPatrick Tasse <patrick.tasse@gmail.com>
Mon, 7 Mar 2016 22:12:18 +0000 (17:12 -0500)
committerPatrick Tasse <patrick.tasse@gmail.com>
Fri, 18 Mar 2016 03:00:21 +0000 (23:00 -0400)
- 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 <patrick.tasse@gmail.com>
Reviewed-on: https://git.eclipse.org/r/67928
Reviewed-by: Hudson CI
Reviewed-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
Tested-by: Marc-Andre Laperle <marc-andre.laperle@ericsson.com>
tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/internal/tmf/core/filter/TmfCollapseFilter.java
tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/filter/model/TmfFilterObjectNode.java [new file with mode: 0644]
tmf/org.eclipse.tracecompass.tmf.ui.swtbot.tests/src/org/eclipse/tracecompass/tmf/ui/swtbot/tests/viewers/events/FilterColorEditorTest.java
tmf/org.eclipse.tracecompass.tmf.ui/icons/elcl16/filter_add.gif [new file with mode: 0644]
tmf/org.eclipse.tracecompass.tmf.ui/icons/ovr16/delete_ovr.gif [new file with mode: 0644]
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/Messages.java
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/internal/tmf/ui/messages.properties
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsCache.java
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTable.java
tmf/org.eclipse.tracecompass.tmf.ui/src/org/eclipse/tracecompass/tmf/ui/viewers/events/TmfEventsTableHeader.java [new file with mode: 0644]

index e937676e405a4072b003a3c06fb9e463ac718b97..f37f1042f3fd75f2bd50192487e0d0be70c50e2a 100644 (file)
@@ -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 (file)
index 0000000..090c426
--- /dev/null
@@ -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<String> getValidChildren() {
+        return new ArrayList<>(0);
+    }
+
+    @Override
+    public String toString(boolean explicit) {
+        if (fFilter instanceof ITmfFilterTreeNode) {
+            return ((ITmfFilterTreeNode) fFilter).toString(explicit);
+        }
+        return fFilter.toString();
+    }
+}
index 08eb62c902045725fba345c59b5c7a1815bd7773..e24c65d292f729fe41f4f76d54ea49f7b18802d2 100644 (file)
@@ -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, "<filter>", 0, 1));
+        fBot.waitUntil(ConditionHelpers.isTableCellFilled(fTableBot, "<srch>", 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<RGB> beforeLine = before.getPixelRow(2);
-        List<RGB> afterLine = after.getPixelRow(2);
+        List<RGB> afterSearchLine = afterSearch.getPixelRow(2);
         List<RGB> afterFilterLine = afterFilter.getPixelRow(2);
+        List<RGB> 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 (file)
index 0000000..a881036
Binary files /dev/null and b/tmf/org.eclipse.tracecompass.tmf.ui/icons/elcl16/filter_add.gif differ
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 (file)
index 0000000..8c72897
Binary files /dev/null and b/tmf/org.eclipse.tracecompass.tmf.ui/icons/ovr16/delete_ovr.gif differ
index 5ca1d440a5abe819406fc7a526b6aa1862337404..b40b3a812cf4a14863868f71006c0edf55d9d5a6 100644 (file)
@@ -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;
index 4dbccf37a857b89db3abfce3806d4a9a0df227da..862f4cc989a330a79a31979af7c71feacff70dd6 100644 (file)
@@ -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=<filter>
 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=<srch>
 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
index 7736b388de599181d352917ac7da98744f4a3293..4be89df16f95ccce4a392cfbf1962bf3065595fe 100644 (file)
@@ -130,6 +130,7 @@ public class TmfEventsCache {
     private final TmfEventsTable fTable;
     private ITmfFilter fFilter;
     private final List<Integer> 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++;
                             }
                         }
index 43f831eea4500fce8b5f8422ea2e670f57f56be4..1b4287e27ab9d7b47254a4e985c38c0dc90bdc4e 100644 (file)
@@ -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 (file)
index 0000000..9703061
--- /dev/null
@@ -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();
+    }
+}
This page took 0.043323 seconds and 5 git commands to generate.