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