1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * 
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  * 
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  * 
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  * 
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  * 
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.flightrecorder.ui;
  34 
  35 import static org.eclipse.ui.IWorkbenchCommandConstants.EDIT_COPY;
  36 import static org.eclipse.ui.IWorkbenchCommandConstants.EDIT_DELETE;
  37 import static org.eclipse.ui.IWorkbenchCommandConstants.EDIT_PASTE;
  38 
  39 import java.util.List;
  40 
  41 import org.eclipse.jface.action.Action;
  42 import org.eclipse.jface.action.IAction;
  43 import org.eclipse.jface.action.IMenuManager;
  44 import org.eclipse.jface.action.MenuManager;
  45 import org.eclipse.jface.dialogs.MessageDialog;
  46 import org.eclipse.jface.resource.ImageDescriptor;
  47 import org.eclipse.jface.util.IPropertyChangeListener;
  48 import org.eclipse.jface.util.LocalSelectionTransfer;
  49 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
  50 import org.eclipse.jface.viewers.ISelection;
  51 import org.eclipse.jface.viewers.IStructuredSelection;
  52 import org.eclipse.jface.viewers.ITreeContentProvider;
  53 import org.eclipse.jface.viewers.SelectionChangedEvent;
  54 import org.eclipse.jface.viewers.StructuredSelection;
  55 import org.eclipse.jface.viewers.TreeViewer;
  56 import org.eclipse.jface.viewers.Viewer;
  57 import org.eclipse.jface.viewers.ViewerDropAdapter;
  58 import org.eclipse.swt.SWT;
  59 import org.eclipse.swt.dnd.DND;
  60 import org.eclipse.swt.dnd.DragSourceAdapter;
  61 import org.eclipse.swt.dnd.DragSourceEvent;
  62 import org.eclipse.swt.dnd.Transfer;
  63 import org.eclipse.swt.dnd.TransferData;
  64 import org.eclipse.swt.graphics.Image;
  65 import org.eclipse.swt.widgets.Composite;
  66 import org.eclipse.swt.widgets.Display;
  67 import org.eclipse.ui.IActionBars;
  68 import org.eclipse.ui.PlatformUI;
  69 import org.eclipse.ui.part.IPageSite;
  70 import org.eclipse.ui.views.contentoutline.ContentOutlinePage;
  71 
  72 import org.openjdk.jmc.flightrecorder.ui.messages.internal.Messages;
  73 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage;
  74 import org.openjdk.jmc.flightrecorder.ui.pages.itemhandler.ItemHandlerPage.ItemHandlerUiStandIn;
  75 import org.openjdk.jmc.flightrecorder.ui.preferences.PreferenceKeys;
  76 import org.openjdk.jmc.ui.UIPlugin;
  77 import org.openjdk.jmc.ui.handlers.ActionToolkit;
  78 import org.openjdk.jmc.ui.handlers.MCContextMenuManager;
  79 import org.openjdk.jmc.ui.misc.AdaptingLabelProvider;
  80 import org.openjdk.jmc.ui.misc.ClipboardManager;
  81 import org.openjdk.jmc.ui.misc.DisplayToolkit;
  82 
  83 /**
  84  * Outline page for the JFR editor. Tightly coupled with {@link AbstractJfrEditor}.
  85  */
  86 public class JfrOutlinePage extends ContentOutlinePage {
  87 
  88         private static final String HELP_CONTEXT_ID = FlightRecorderUI.PLUGIN_ID + ".JfrOutlinePage"; //$NON-NLS-1$
  89         static final ITreeContentProvider CONTENT_PROVIDER = new ITreeContentProvider() {
  90 
  91                 @Override
  92                 public void dispose() {
  93                 }
  94 
  95                 @Override
  96                 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
  97                 }
  98 
  99                 @SuppressWarnings("unchecked")
 100                 @Override
 101                 public Object[] getElements(Object inputElement) {
 102                         return ((List<DataPageDescriptor>) inputElement).toArray();
 103                 }
 104 
 105                 @Override
 106                 public Object[] getChildren(Object parentElement) {
 107                         return ((DataPageDescriptor) parentElement).getChildren();
 108                 }
 109 
 110                 @Override
 111                 public Object getParent(Object element) {
 112                         return ((DataPageDescriptor) element).getParent();
 113                 }
 114 
 115                 @Override
 116                 public boolean hasChildren(Object element) {
 117                         return ((DataPageDescriptor) element).hasChildren();
 118                 }
 119 
 120         };
 121 
 122         private final class OutlineDragListener extends DragSourceAdapter {
 123 
 124                 @Override
 125                 public void dragStart(DragSourceEvent event) {
 126                         if (PAGE_STRUCTURE_LOCK_ACTION.isChecked()) {
 127                                 event.doit = false;
 128                         } else {
 129                                 LocalSelectionTransfer.getTransfer().setSelection(getTreeViewer().getSelection());
 130                         }
 131                 }
 132 
 133                 @Override
 134                 public void dragSetData(DragSourceEvent event) {
 135                         if (LocalSelectionTransfer.getTransfer().isSupportedType(event.dataType)) {
 136                                 event.data = LocalSelectionTransfer.getTransfer().getSelection();
 137                         }
 138                 }
 139 
 140                 @Override
 141                 public void dragFinished(DragSourceEvent event) {
 142                         LocalSelectionTransfer.getTransfer().setSelection(null);
 143                 }
 144         }
 145 
 146         private static final class OutlineDropListener extends ViewerDropAdapter {
 147 
 148                 public OutlineDropListener(TreeViewer viewer) {
 149                         super(viewer);
 150                 }
 151 
 152                 @Override
 153                 public boolean performDrop(Object data) {
 154                         if (data instanceof IStructuredSelection) {
 155                                 Object selected = ((IStructuredSelection) data).getFirstElement();
 156                                 Object target = getCurrentTarget();
 157                                 if (selected instanceof DataPageDescriptor
 158                                                 && (target instanceof DataPageDescriptor || target == null)) {
 159                                         DataPageDescriptor sDPD = (DataPageDescriptor) selected;
 160                                         DataPageDescriptor tDPD = (DataPageDescriptor) target;
 161                                         switch (getCurrentOperation()) {
 162                                         case DND.DROP_COPY:
 163                                                 sDPD = new DataPageDescriptor(sDPD);
 164                                                 // Fall through
 165                                         case DND.DROP_MOVE:
 166                                                 switch (getCurrentLocation()) {
 167                                                 case LOCATION_AFTER:
 168                                                         FlightRecorderUI.getDefault().getPageManager().makeSibling(sDPD, tDPD, 1);
 169                                                         return true;
 170                                                 case LOCATION_BEFORE:
 171                                                         FlightRecorderUI.getDefault().getPageManager().makeSibling(sDPD, tDPD, 0);
 172                                                         return true;
 173                                                 case LOCATION_ON:
 174                                                         FlightRecorderUI.getDefault().getPageManager().makeChild(sDPD, tDPD, 0);
 175                                                         return true;
 176                                                 case LOCATION_NONE:
 177                                                         FlightRecorderUI.getDefault().getPageManager().makeRoot(sDPD);
 178                                                         return true;
 179                                                 }
 180                                         }
 181                                 }
 182                         }
 183                         return false;
 184                 }
 185 
 186                 @Override
 187                 public boolean validateDrop(Object target, int operation, TransferData transferType) {
 188                         ISelection s = LocalSelectionTransfer.getTransfer().getSelection();
 189                         if (s instanceof IStructuredSelection && !PAGE_STRUCTURE_LOCK_ACTION.isChecked()) {
 190                                 Object selected = ((IStructuredSelection) s).getFirstElement();
 191                                 if (selected instanceof DataPageDescriptor) {
 192                                         boolean cyclic = target instanceof DataPageDescriptor
 193                                                         && ((DataPageDescriptor) selected).contains((DataPageDescriptor) target);
 194                                         return !cyclic;
 195                                 }
 196                         }
 197                         return false;
 198                 }
 199         }
 200 
 201         private class OutlineLabelProvider extends AdaptingLabelProvider {
 202 
 203                 @Override
 204                 public String getText(Object element) {
 205                         if (element instanceof DataPageDescriptor) {
 206                                 return editor.getDisplayablePage((DataPageDescriptor) element).getName();
 207                         }
 208                         return super.getText(element);
 209                 }
 210 
 211                 @Override
 212                 public String getToolTipText(Object element) {
 213                         if (element instanceof DataPageDescriptor) {
 214                                 if (FlightRecorderUI.getDefault().isAnalysisEnabled()) {
 215                                         return editor.getDisplayablePage((DataPageDescriptor) element).getDescription();
 216                                 } else {
 217                                         return null;
 218                                 }
 219                         }
 220                         return super.getToolTipText(element);
 221                 }
 222 
 223                 @Override
 224                 public Image getImage(Object element) {
 225                         if (element instanceof DataPageDescriptor) {
 226                                 ImageDescriptor imageDescriptor = editor.getDisplayablePage((DataPageDescriptor) element)
 227                                                 .getImageDescriptor();
 228                                 return imageDescriptor == null ? null : (Image) getResourceManager().get(imageDescriptor);
 229                         }
 230                         return super.getImage(element);
 231                 }
 232         }
 233 
 234         private final class NewPageAction extends Action {
 235 
 236                 private final DataPageDescriptor page;
 237 
 238                 NewPageAction(DataPageDescriptor page) {
 239                         super(page.getName(), page.getImageDescriptor());
 240                         this.page = page;
 241                 }
 242 
 243                 @Override
 244                 public void run() {
 245                         addChildToSelected(page);
 246                 }
 247         }
 248 
 249         public static final String Outline_TREE_NAME = "org.openjdk.jmc.flightrecorder.ui.editor.JfrOutlineTree"; //$NON-NLS-1$
 250 
 251         private static final int DND_OPERATIONS = DND.DROP_MOVE | DND.DROP_COPY;
 252         private static final Transfer[] DND_TRANSFER = new Transfer[] {LocalSelectionTransfer.getTransfer()};
 253         private static final ImageDescriptor RESET_ICON = UIPlugin.getDefault()
 254                         .getMCImageDescriptor(UIPlugin.ICON_RESET_TO_DEFAULTS);
 255         private static final IAction RESET_ALL_PAGES_ACTION = new Action(Messages.JFR_OUTLINE_RESET_ALL_ACTION,
 256                         RESET_ICON) {
 257 
 258                 @Override
 259                 public void run() {
 260                         if (MessageDialog.openConfirm(Display.getCurrent().getActiveShell(),
 261                                         Messages.JFR_OUTLINE_RESET_ALL_CONFIRM_TITLE, Messages.JFR_OUTLINE_RESET_ALL_CONFIRM_MESSAGE)) {
 262                                 FlightRecorderUI.getDefault().getPageManager().reset();
 263                         }
 264                 };
 265         };
 266         private static final IAction PAGE_STRUCTURE_LOCK_ACTION = createPageStructureLockAction();
 267 
 268         private final JfrEditor editor;
 269         private IPropertyChangeListener analysisEnabledListener;
 270 
 271         public JfrOutlinePage(JfrEditor editor) {
 272                 this.editor = editor;
 273 
 274                 analysisEnabledListener = e -> {
 275                         if (e.getProperty().equals(PreferenceKeys.PROPERTY_ENABLE_RECORDING_ANALYSIS)) {
 276                                 DisplayToolkit.safeAsyncExec(() -> refresh(true));
 277                         }
 278                 };
 279                 FlightRecorderUI.getDefault().getPreferenceStore().addPropertyChangeListener(analysisEnabledListener);
 280         }
 281 
 282         @Override
 283         public void init(IPageSite pageSite) {
 284                 super.init(pageSite);
 285                 pageSite.setSelectionProvider(editor.getSite().getSelectionProvider());
 286         }
 287 
 288         @Override
 289         public void createControl(Composite parent) {
 290                 super.createControl(parent);
 291                 getTreeViewer().getTree().setData("name", Outline_TREE_NAME); //$NON-NLS-1$
 292 
 293                 getTreeViewer().setContentProvider(CONTENT_PROVIDER);
 294                 getTreeViewer().setLabelProvider(new OutlineLabelProvider());
 295                 PlatformUI.getWorkbench().getHelpSystem().setHelp(getTreeViewer().getControl(), HELP_CONTEXT_ID);
 296 
 297                 IAction copyAction = ActionToolkit.commandAction(this::copyToClipboard, EDIT_COPY);
 298                 IAction pasteAction = ActionToolkit.commandAction(this::pasteFromClipboard, EDIT_PASTE);
 299                 IAction deleteAction = ActionToolkit.commandAction(this::deleteSelected, EDIT_DELETE);
 300 
 301                 IActionBars ab = getSite().getActionBars();
 302                 ab.setGlobalActionHandler(copyAction.getActionDefinitionId(), copyAction);
 303                 ab.setGlobalActionHandler(pasteAction.getActionDefinitionId(), pasteAction);
 304                 ab.setGlobalActionHandler(deleteAction.getActionDefinitionId(), deleteAction);
 305                 ab.updateActionBars();
 306 
 307                 MCContextMenuManager mm = MCContextMenuManager.create(getTreeViewer().getControl());
 308                 mm.appendToGroup(MCContextMenuManager.GROUP_NEW, createNewPageMenuManager());
 309                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT,
 310                                 ActionToolkit.action(() -> moveSelectedLeft(true), Messages.JFR_OUTLINE_MOVE_LEFT));
 311                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT,
 312                                 ActionToolkit.action(() -> moveSelectedLeft(false), Messages.JFR_OUTLINE_MOVE_RIGHT));
 313                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT,
 314                                 ActionToolkit.action(() -> moveSelectedUp(true), Messages.JFR_OUTLINE_MOVE_UP));
 315                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT,
 316                                 ActionToolkit.action(() -> moveSelectedUp(false), Messages.JFR_OUTLINE_MOVE_DOWN));
 317                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, copyAction);
 318                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, pasteAction);
 319                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT, deleteAction);
 320                 mm.appendToGroup(MCContextMenuManager.GROUP_EDIT,
 321                                 ActionToolkit.action(this::resetSelected, Messages.JFR_OUTLINE_RESET_ACTION, RESET_ICON));
 322 
 323                 ab.getMenuManager().add(createNewPageMenuManager());
 324                 ab.getToolBarManager().add(PAGE_STRUCTURE_LOCK_ACTION);
 325                 ab.getToolBarManager().add(RESET_ALL_PAGES_ACTION);
 326 
 327                 getTreeViewer().addDoubleClickListener(e -> expandSelected());
 328 
 329                 ColumnViewerToolTipSupport.enableFor(getTreeViewer());
 330 
 331                 getTreeViewer().addDragSupport(DND_OPERATIONS, DND_TRANSFER, new OutlineDragListener());
 332                 getTreeViewer().addDropSupport(DND_OPERATIONS, DND_TRANSFER, new OutlineDropListener(getTreeViewer()));
 333 
 334                 /*
 335                  * Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=438919
 336                  *
 337                  * This bug was introduced in Eclipse 3.6 M6, fixed in 3.6 RC4, reintroduced in 4.4.0, fixed
 338                  * in 4.4.1.
 339                  * 
 340                  * While ContentViewer keeps its input in a private field, AbstractTreeViewer.inputChanged()
 341                  * also stores the root tree element derived from (and often the same as) the input using
 342                  * Widget.setData() on the tree, without using a key. This allows unified translation from a
 343                  * widget (Tree or TreeItem) to the corresponding tree element by using getData(),
 344                  * simplifying code.
 345                  * 
 346                  * However, in Eclipse 4.4.0, PageBookView.createPage() also uses Widget.setData() without
 347                  * key on the topmost control of the page to store a ContributionInfo (used by the
 348                  * Alt+Shift+F3 introspection). Since this control typically is the tree, this would
 349                  * overwrite the root element of the tree. In particular, this would happen if the tree
 350                  * input is set during Page.createControl(). This could cause a ContributionInfo to be sent
 351                  * to ITreeContentProvider.getChildren().
 352                  * 
 353                  * The current workaround is to do a refresh asynchronously, but this might not be optimal.
 354                  */
 355                 DisplayToolkit.safeAsyncExec(this::refresh);
 356         }
 357 
 358         private MenuManager createNewPageMenuManager() {
 359                 MenuManager newPageMenu = new MenuManager(Messages.JFR_OUTLINE_NEW_PAGE);
 360                 newPageMenu.setRemoveAllWhenShown(true);
 361                 newPageMenu.addMenuListener(this::fillNewPageMenu);
 362                 return newPageMenu;
 363         }
 364 
 365         private void fillNewPageMenu(IMenuManager manager) {
 366                 manager.add(ActionToolkit.action(this::openCreateCustomPageDialog, Messages.JFR_OUTLINE_CUSTOM_PAGE));
 367                 FlightRecorderUI.getDefault().getPageManager().getInitialPages().map(NewPageAction::new).forEach(manager::add);
 368         }
 369 
 370         private void copyToClipboard() {
 371                 Object selected = ((IStructuredSelection) getTreeViewer().getSelection()).getFirstElement();
 372                 if (selected != null) {
 373                         ClipboardManager.setClipboardContents(new Object[] {selected},
 374                                         new Transfer[] {ClipboardManager.getClipboardLocalTransfer()});
 375                 }
 376         }
 377 
 378         private void pasteFromClipboard() {
 379                 Object onClipboard = ClipboardManager.getClipboardContents(ClipboardManager.getClipboardLocalTransfer());
 380                 if (onClipboard instanceof DataPageDescriptor) {
 381                         addChildToSelected((DataPageDescriptor) onClipboard);
 382                 }
 383         }
 384 
 385         private void addChildToSelected(DataPageDescriptor child) {
 386                 Object selected = ((IStructuredSelection) getTreeViewer().getSelection()).getFirstElement();
 387                 if (selected == null) {
 388                         FlightRecorderUI.getDefault().getPageManager().makeRoot(new DataPageDescriptor(child));
 389                 } else if (selected instanceof DataPageDescriptor) {
 390                         FlightRecorderUI.getDefault().getPageManager().makeChild(new DataPageDescriptor(child),
 391                                         (DataPageDescriptor) selected, 0);
 392                         getTreeViewer().setExpandedState(selected, true);
 393                 }
 394         }
 395 
 396         private void deleteSelected() {
 397                 Object selected = ((IStructuredSelection) getTreeViewer().getSelection()).getFirstElement();
 398                 if (selected instanceof DataPageDescriptor && MessageDialog.openConfirm(Display.getCurrent().getActiveShell(),
 399                                 Messages.JFR_OUTLINE_DELETE_CONFIRM_TITLE, Messages.JFR_OUTLINE_DELETE_CONFIRM_MESSAGE)) {
 400                         DataPageDescriptor dpd = (DataPageDescriptor) selected;
 401                         DataPageDescriptor parent = dpd.getParent();
 402                         FlightRecorderUI.getDefault().getPageManager().deletePage(dpd);
 403                         getTreeViewer().refresh(parent);
 404                 }
 405         }
 406 
 407         private void resetSelected() {
 408                 Object selected = ((IStructuredSelection) getTreeViewer().getSelection()).getFirstElement();
 409                 if (selected instanceof DataPageDescriptor && MessageDialog.openConfirm(Display.getCurrent().getActiveShell(),
 410                                 Messages.JFR_OUTLINE_RESET_CONFIRM_TITLE, Messages.JFR_OUTLINE_RESET_CONFIRM_MESSAGE)) {
 411                         DataPageDescriptor dpd = (DataPageDescriptor) selected;
 412                         FlightRecorderUI.getDefault().getPageManager().resetPage(dpd);
 413                         editor.displayPage(dpd);
 414                         getTreeViewer().update(dpd, null);
 415                 }
 416         }
 417 
 418         private void moveSelectedUp(boolean up) {
 419                 Object selected = ((IStructuredSelection) getTreeViewer().getSelection()).getFirstElement();
 420                 if (selected instanceof DataPageDescriptor) {
 421                         DataPageDescriptor dpd = (DataPageDescriptor) selected;
 422                         FlightRecorderUI.getDefault().getPageManager().makeSibling(dpd, dpd, up ? -1 : 2);
 423                 }
 424         }
 425 
 426         private void moveSelectedLeft(boolean left) {
 427                 Object selected = ((IStructuredSelection) getTreeViewer().getSelection()).getFirstElement();
 428                 if (selected instanceof DataPageDescriptor) {
 429                         DataPageDescriptor dpd = (DataPageDescriptor) selected;
 430                         if (!left) {
 431                                 FlightRecorderUI.getDefault().getPageManager().makeChild(dpd, dpd, -1);
 432                         } else if (dpd.getParent() != null) {
 433                                 FlightRecorderUI.getDefault().getPageManager().makeSibling(dpd, dpd.getParent(), 1);
 434                         }
 435                 }
 436         }
 437 
 438         private void expandSelected() {
 439                 Object selected = ((IStructuredSelection) getTreeViewer().getSelection()).getFirstElement();
 440                 if (selected != null) {
 441                         getTreeViewer().setExpandedState(selected, !getTreeViewer().getExpandedState(selected));
 442                 }
 443         }
 444 
 445         @Override
 446         public void selectionChanged(SelectionChangedEvent event) {
 447                 Object selected = (((IStructuredSelection) event.getSelection()).getFirstElement());
 448                 if (selected != null) {
 449                         editor.navigateTo((DataPageDescriptor) selected);
 450                 }
 451         }
 452 
 453         @Override
 454         protected int getTreeStyle() {
 455                 return SWT.VIRTUAL | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER;
 456         }
 457 
 458         @Override
 459         public void dispose() {
 460                 FlightRecorderUI.getDefault().getPreferenceStore().removePropertyChangeListener(analysisEnabledListener);
 461                 super.dispose();
 462         }
 463 
 464         void refresh() {
 465                 refresh(false);
 466         }
 467 
 468         private void refresh(boolean forceRefreshPages) {
 469                 TreeViewer tv = getTreeViewer();
 470                 DataPageDescriptor editorPage = editor.getCurrentPage();
 471                 if (editorPage != null && tv != null && !tv.getControl().isDisposed()) {
 472                         if (tv.getInput() == null || forceRefreshPages) {
 473                                 tv.setInput(FlightRecorderUI.getDefault().getPageManager().getRootPages());
 474                                 tv.expandToLevel(2);
 475                         } else {
 476                                 tv.refresh();
 477                         }
 478                         if (((IStructuredSelection) tv.getSelection()).getFirstElement() != editorPage) {
 479                                 tv.setSelection(new StructuredSelection(editorPage));
 480                         }
 481                 }
 482         }
 483 
 484         private static IAction createPageStructureLockAction() {
 485                 ImageDescriptor lockIcon = UIPlugin.getDefault().getMCImageDescriptor(UIPlugin.ICON_LOCK_TREE);
 486                 IAction lockAction = ActionToolkit.checkAction(FlightRecorderUI.getDefault()::setPageStructureLocked,
 487                                 Messages.JFR_OUTLINE_LOCK_PAGES_ACTION, lockIcon);
 488                 lockAction.setChecked(FlightRecorderUI.getDefault().isPageStructureLocked());
 489                 return lockAction;
 490         }
 491 
 492         private void openCreateCustomPageDialog() {
 493                 TypeSelectorWizardPage.openDialog(editor.getModel().getTypeTree(), types -> {
 494                         PageManager pm = FlightRecorderUI.getDefault().getPageManager();
 495                         addChildToSelected(pm.createPage(ItemHandlerPage.Factory.class, new ItemHandlerUiStandIn(types)));
 496                 }, Messages.JFR_OUTLINE_CREATE_CUSTOM_TITLE, Messages.JFR_OUTLINE_CREATE_CUSTOM_MESSAGE);
 497         }
 498 }