gt:             fStatus = new StatusInfo();
context: /*******************************************************************************
   * Copyright (c) 2000, 2011 IBM Corporation 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 accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   *
   * Contributors:
   *     IBM Corporation - initial API and implementation
   *     Mark Leone - Modifications for PyDev
   *******************************************************************************/
  package org.python.pydev.editor.hover;
  
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.StringTokenizer;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.jface.action.Action;
  import org.eclipse.jface.dialogs.Dialog;
  import org.eclipse.jface.layout.GridDataFactory;
  import org.eclipse.jface.layout.GridLayoutFactory;
  import org.eclipse.jface.layout.PixelConverter;
  import org.eclipse.jface.preference.PreferencePage;
  import org.eclipse.jface.viewers.CellEditor;
  import org.eclipse.jface.viewers.CheckStateChangedEvent;
  import org.eclipse.jface.viewers.CheckboxCellEditor;
  import org.eclipse.jface.viewers.CheckboxTableViewer;
  import org.eclipse.jface.viewers.ColumnViewer;
  import org.eclipse.jface.viewers.ColumnWeightData;
  import org.eclipse.jface.viewers.EditingSupport;
  import org.eclipse.jface.viewers.ICellEditorValidator;
  import org.eclipse.jface.viewers.ICheckStateListener;
  import org.eclipse.jface.viewers.ILabelProviderListener;
  import org.eclipse.jface.viewers.IStructuredContentProvider;
  import org.eclipse.jface.viewers.IStructuredSelection;
  import org.eclipse.jface.viewers.StructuredSelection;
  import org.eclipse.jface.viewers.TableLayout;
  import org.eclipse.jface.viewers.TableViewer;
  import org.eclipse.jface.viewers.TableViewerColumn;
  import org.eclipse.jface.viewers.TextCellEditor;
  import org.eclipse.jface.viewers.Viewer;
  import org.eclipse.jface.viewers.ViewerCell;
  import org.eclipse.jface.viewers.ViewerSorter;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.custom.StyleRange;
  import org.eclipse.swt.custom.StyledText;
  import org.eclipse.swt.events.KeyEvent;
  import org.eclipse.swt.events.KeyListener;
  import org.eclipse.swt.events.ModifyEvent;
  import org.eclipse.swt.events.ModifyListener;
  import org.eclipse.swt.events.SelectionAdapter;
  import org.eclipse.swt.events.SelectionEvent;
  import org.eclipse.swt.events.SelectionListener;
  import org.eclipse.swt.graphics.Point;
  import org.eclipse.swt.layout.GridData;
  import org.eclipse.swt.layout.GridLayout;
  import org.eclipse.swt.widgets.Button;
  import org.eclipse.swt.widgets.Composite;
  import org.eclipse.swt.widgets.Control;
  import org.eclipse.swt.widgets.Display;
  import org.eclipse.swt.widgets.Label;
  import org.eclipse.swt.widgets.Table;
  import org.eclipse.swt.widgets.TableColumn;
  import org.eclipse.swt.widgets.TableItem;
  import org.eclipse.swt.widgets.Text;
  import org.eclipse.ui.progress.UIJob;
  import org.python.pydev.core.log.Log;
  import org.python.pydev.editor.actions.PyAction;
  import org.python.pydev.plugin.PyDevUiPrefs;
  import org.python.pydev.plugin.PydevPlugin;
  import org.python.pydev.plugin.StatusInfo;
  import org.python.pydev.plugin.preferences.IPreferenceConfigurationBlock;
  import org.python.pydev.ui.EmulatedNativeCheckBoxLabelProvider;
  import org.python.pydev.ui.ScrolledPageContent;
  import org.python.pydev.ui.TableLayoutComposite;
  import org.python.pydev.utils.Messages;
  import org.python.pydev.utils.PyEditorMessages;
  import org.python.pydev.utils.SWTUtil;
  import org.python.pydev.utils.StatusUtil;
  
  /**
   * Configures PyDev Editor hover preferences.
   */
  public class PyEditorHoverConfigurationBlock implements IPreferenceConfigurationBlock {
  
      private static final String DELIMITER = PyEditorMessages.PyEditorHoverConfigurationBlock_delimiter;
  
      private static final int ENABLED_PROP = 0;
      private static final int PRIORITY_PROP = 1;
      private static final int MODIFIER_PROP = 2;
      private static final int PREEMPT_PROP = 3;
  
      private String priorityHelpStr = " (Lowest number is highest priority)";
  
      private class PyEditorTextHoverDescriptorLabelProvider extends EmulatedNativeCheckBoxLabelProvider {
  
          public PyEditorTextHoverDescriptorLabelProvider(ColumnViewer viewer) {
              super(viewer);
          }
  
          @Override
          public void addListener(ILabelProviderListener listener) {
          }
  
          @Override
          public void dispose() {
          }
  
          @Override
          public boolean isLabelProperty(Object element, String property) {
              return false;
          }
  
          @Override
          public void removeListener(ILabelProviderListener listener) {
          }
  
          @Override
          public void update(ViewerCell cell) {
              switch (cell.getColumnIndex()) {
                  case ENABLED_PROP:
                      cell.setText(((PyEditorTextHoverDescriptor) cell.getElement()).getLabel());
                      break;
                  case PRIORITY_PROP:
                      cell.setText(String.valueOf(((PyEditorTextHoverDescriptor) cell.getElement()).getPriority()));
                      break;
                  case MODIFIER_PROP:
                      TableItem item = (TableItem) fHoverTableViewer.testFindItem(cell.getElement());
                      int index = fHoverTable.indexOf(item);
                      cell.setText(fHoverDescs[index].fModifierString);
                      break;
                  case PREEMPT_PROP:
                      cell.setImage(getImage(cell.getElement()));
                      break;
                  default:
                      break;
              }
  
          }
  
          @Override
          protected boolean isChecked(Object element) {
              return ((PyEditorTextHoverDescriptor) element).isPreempt();
          }
      }
  
      private class PyEditorTextHoverDescriptorContentProvider implements IStructuredContentProvider {
  
          @Override
          public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
              // Do nothing since the viewer listens to resource deltas
          }
  
          @Override
          public void dispose() {
          }
  
          @Override
          public Object[] getElements(Object element) {
              return (Object[]) element;
          }
      }
  
      private class HoverTableEditingSupport extends EditingSupport {
  
          private int column;
          CellEditor editor;
          private TableViewer viewer;
  
          public HoverTableEditingSupport(final TableViewer viewer, int column) {
              super(viewer);
              this.viewer = viewer;
              this.column = column;
          }
  
          @Override
          protected CellEditor getCellEditor(Object element) {
              if (this.column == PRIORITY_PROP) {
                  this.editor = new TextCellEditor(this.viewer.getTable());
                  editor.setValidator(new ICellEditorValidator() {
  
                      @Override
                      public String isValid(Object value) {
                          boolean valid = true;
                          if (!"".equals(value)) {
                              try {
                                  int val = Integer.parseInt((String) value);
                                  if (val <= 0) {
                                      valid = false;
                                  }
                              } catch (NumberFormatException | ClassCastException e) {
                                  valid = false;
                              }
                          }
                          editor.getControl()
                                  .setBackground(valid ? null : Display.getDefault().getSystemColor(SWT.COLOR_RED));
                          return (valid ? null : "positive integer required");
                      }
                  });
                  ((Text) ((TextCellEditor) editor).getControl()).selectAll();
              } else if (this.column == PREEMPT_PROP) {
                  this.editor = new CheckboxCellEditor(viewer.getTable());
              }
              return this.editor;
          }
  
          /*
           * (non-Javadoc)
           * @see org.eclipse.jface.viewers.EditingSupport#canEdit(java.lang.Object)
           */
          @Override
          protected boolean canEdit(Object element) {
              return true;
          }
  
          /*
           * (non-Javadoc)
           * @see org.eclipse.jface.viewers.EditingSupport#getValue(java.lang.Object)
           */
          @Override
          protected Object getValue(Object element) {
              PyEditorTextHoverDescriptor descr = (PyEditorTextHoverDescriptor) element;
              if (descr != null) {
                  switch (this.column) {
                      case PRIORITY_PROP:
                          fTableLabel.setText(
                                  PyEditorMessages.PyEditorHoverConfigurationBlock_hoverPreferences + priorityHelpStr);
                          fTableLabel.setStyleRange(priorityHelpRange);
                          fTableLabel.getParent().layout();
                          return String.valueOf(descr.getPriority());
                      case PREEMPT_PROP:
                          return descr.isPreempt();
                      default:
                  }
  
              }
              return "";
          }
  
          /*
           * (non-Javadoc)
           * @see org.eclipse.jface.viewers.EditingSupport#setValue(java.lang.Object, java.lang.Object)
           */
          @Override
          protected void setValue(Object element, Object value) {
              switch (this.column) {
                  case PRIORITY_PROP:
                      ((PyEditorTextHoverDescriptor) element).setPriority(Integer.parseInt((String) value));
                      handleSetPriority(Integer.parseInt((String) value));
                      fTableLabel.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_hoverPreferences);
                      fTableLabel.setStyleRange(null);
                      fTableLabel.getParent().layout();
                      break;
                  case PREEMPT_PROP:
                      ((PyEditorTextHoverDescriptor) element)
                              .setIsPreempt(!((PyEditorTextHoverDescriptor) element).isPreempt());
                      handleSetPreempt((Boolean) value);
                      break;
                  default:
              }
              this.editor.dispose();
          }
  
      }
  
      private Text fModifierEditor;
      private Table fHoverTable;
      private CheckboxTableViewer fHoverTableViewer;
      private TableColumn fNameColumn;
      TableColumn fModifierColumn;
      private TableColumn fPriorityColumn;
      TableColumn fPreemptColumn;
      private int[] fColWidths = new int[] { 35, 15, 35, 15 };
      private Map<TableColumn, Integer> fColWidthsMap = new HashMap<TableColumn, Integer>();
      private Text fDescription;
  
      private StyleRange priorityHelpRange = null;
  
      private PreferencePage fMainPreferencePage;
  
      public static StatusInfo fStatus;
  
      private class KeyModifierListener implements KeyListener {
  
          private Text editor;
  
          private KeyModifierListener(Text editor) {
              this.editor = editor;
          }
  
          private boolean isModifierCandidate;
  
          @Override
          public void keyPressed(KeyEvent e) {
              isModifierCandidate = e.keyCode > 0 && e.character == 0 && e.stateMask == 0;
          }
  
          @Override
          public void keyReleased(KeyEvent e) {
              if (isModifierCandidate && e.stateMask > 0 && e.stateMask == e.stateMask && e.character == 0) {
                  String text = editor.getText();
                  Point selection = editor.getSelection();
                  int i = selection.x - 1;
                  while (i > -1 && Character.isWhitespace(text.charAt(i))) {
                      i--;
                  }
                  boolean needsPrefixDelimiter = i > -1 && !String.valueOf(text.charAt(i)).equals(DELIMITER);
  
                  i = selection.y;
                  while (i < text.length() && Character.isWhitespace(text.charAt(i))) {
                      i++;
                  }
                  boolean needsPostfixDelimiter = i < text.length()
                          && !String.valueOf(text.charAt(i)).equals(DELIMITER);
  
                  String insertString;
  
                  if (needsPrefixDelimiter && needsPostfixDelimiter) {
                      insertString = Messages.format(
                              PyEditorMessages.PyEditorHoverConfigurationBlock_insertDelimiterAndModifierAndDelimiter,
                              new String[] { Action.findModifierString(e.stateMask) });
                  } else if (needsPrefixDelimiter) {
                      insertString = Messages.format(
                              PyEditorMessages.PyEditorHoverConfigurationBlock_insertDelimiterAndModifier,
                              new String[] { Action.findModifierString(e.stateMask) });
                  } else if (needsPostfixDelimiter) {
                      insertString = Messages.format(
                              PyEditorMessages.PyEditorHoverConfigurationBlock_insertModifierAndDelimiter,
                              new String[] { Action.findModifierString(e.stateMask) });
                  } else {
                      insertString = Action.findModifierString(e.stateMask);
                  }
  
                  if (insertString != null) {
                      editor.insert(insertString);
                  }
              }
          }
      };
  
      private Button fCombineHovers;
  
      private Text fCombiningHoverModifierEditor;
  
      private Button fDebugShowVars;
  
      private Button fUseFirstHover;
  
      private Label fCombiningHoverLabel;
  
      private Button fUseHoverDivider;
  
      private Composite fComp;
  
      private GridData fDisabledLabelGridData;
  
      private Composite fButtonComp;
  
      private PyEditorTextHoverDescriptor[] fHoverDescs;
  
      private PyEditorTextHoverDescriptor fCombiningHover;
  
      private Label fModifierFieldLabel;
  
      private StyledText fTableLabel;
  
      public PyEditorHoverConfigurationBlock(PreferencePage mainPreferencePage) {
          Assert.isNotNull(mainPreferencePage);
          fMainPreferencePage = mainPreferencePage;
      }
  
      /**
       * Creates page for hover preferences.
       *
       * @param parent the parent composite
       * @return the control for the preference page
       */
      @Override
      public Control createControl(final Composite parent) {
  
          priorityHelpRange = new StyleRange(
                  PyEditorMessages.PyEditorHoverConfigurationBlock_hoverPreferences.length(),
                  priorityHelpStr.length(),
                  parent.getDisplay().getSystemColor(SWT.COLOR_BLUE), null);
  
          fComp = new Composite(parent, SWT.NONE);
          GridDataFactory.fillDefaults().grab(true, false).applyTo(fComp);
          GridLayoutFactory.fillDefaults().numColumns(3).applyTo(fComp);
  
          fButtonComp = new Composite(fComp, SWT.BORDER);
          GridLayoutFactory.fillDefaults().numColumns(2).applyTo(fButtonComp);
          fUseFirstHover = new Button(fButtonComp, SWT.RADIO | SWT.BORDER);
          fUseFirstHover.setText("Use highest priority Hover");
          GridDataFactory.fillDefaults().applyTo(fUseFirstHover);
          fCombineHovers = new Button(fButtonComp, SWT.RADIO | SWT.BORDER);
          fCombineHovers.setText("Combine Hovers");
          GridDataFactory.fillDefaults().applyTo(fCombineHovers);
          fCombineHovers.addSelectionListener(new SelectionAdapter() {
              @Override
              public void widgetSelected(SelectionEvent e) {
                  fCombiningHoverModifierEditor.setEnabled(fCombineHovers.getSelection());
                  fCombiningHoverLabel.setEnabled(fCombineHovers.getSelection());
                  showColumn(fPreemptColumn, fCombineHovers.getSelection());
                  showColumn(fModifierColumn, !fCombineHovers.getSelection());
                  fModifierFieldLabel.setEnabled(!fCombineHovers.getSelection());
              }
          });
  
          fCombiningHoverLabel = new Label(fComp, SWT.PUSH);
          fCombiningHoverLabel.setText("Combined Hovers Key Modifier:");
          fCombiningHoverModifierEditor = new Text(fComp, SWT.BORDER);
          fCombiningHoverModifierEditor.addKeyListener(new KeyModifierListener(fCombiningHoverModifierEditor));
          fCombiningHoverModifierEditor.addModifyListener(new ModifyListener() {
              @Override
              public void modifyText(ModifyEvent e) {
                  handleModifierModified(fCombiningHoverModifierEditor);
              }
          });
          GridDataFactory.fillDefaults().grab(true, false).applyTo(fCombiningHoverModifierEditor);
  
          ScrolledPageContent scrolled = new ScrolledPageContent(parent, SWT.H_SCROLL | SWT.V_SCROLL);
          scrolled.setExpandHorizontal(true);
          scrolled.setExpandVertical(true);
  
          Composite hoverComposite = new Composite(scrolled, SWT.NONE);
          GridLayout layout = new GridLayout();
          layout.numColumns = 2;
          layout.marginWidth = 0;
          layout.marginHeight = 0;
          hoverComposite.setLayout(layout);
  
          addFiller(hoverComposite);
  
          fTableLabel = new StyledText(hoverComposite, SWT.NONE);
          fTableLabel.setEditable(false);
          fTableLabel.setEnabled(false);
          fTableLabel.setBackground(fTableLabel.getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
          fTableLabel.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_hoverPreferences);
          GridData gd = new GridData(GridData.FILL_HORIZONTAL);
          gd.horizontalAlignment = GridData.BEGINNING;
          gd.horizontalSpan = 2;
          fTableLabel.setLayoutData(gd);
  
          TableLayoutComposite layouter = new TableLayoutComposite(hoverComposite, SWT.NONE);
          addColumnLayoutData(layouter, fColWidths);
  
          // Hover table
          fHoverTable = new Table(layouter,
                  SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION | SWT.CHECK);
          fHoverTable.setHeaderVisible(true);
          fHoverTable.setLinesVisible(true);
  
          gd = new GridData(GridData.FILL_HORIZONTAL);
          gd.heightHint = SWTUtil.getTableHeightHint(fHoverTable, 10);
          gd.horizontalSpan = 2;
          gd.widthHint = new PixelConverter(parent).convertWidthInCharsToPixels(100);
          layouter.setLayoutData(gd);
  
          fHoverTable.addSelectionListener(new SelectionListener() {
              @Override
              public void widgetSelected(SelectionEvent e) {
                  handleHoverListSelection();
              }
  
              @Override
              public void widgetDefaultSelected(SelectionEvent e) {
              }
          });
  
          TableLayout tableLayout = new TableLayout();
          fHoverTable.setLayout(tableLayout);
          fHoverTableViewer = new CheckboxTableViewer(fHoverTable);
  
          TableViewerColumn nameColumnViewer = new TableViewerColumn(fHoverTableViewer, SWT.NONE);
          nameColumnViewer.setLabelProvider(new PyEditorTextHoverDescriptorLabelProvider(fHoverTableViewer));
          fNameColumn = nameColumnViewer.getColumn();
          fNameColumn.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_nameColumnTitle);
          fNameColumn.setResizable(true);
  
          TableViewerColumn priorityViewerColumn = new TableViewerColumn(fHoverTableViewer, SWT.NONE);
          priorityViewerColumn.setEditingSupport(new HoverTableEditingSupport(fHoverTableViewer, PRIORITY_PROP));
          priorityViewerColumn.setLabelProvider(new PyEditorTextHoverDescriptorLabelProvider(fHoverTableViewer));
          fPriorityColumn = priorityViewerColumn.getColumn();
          fPriorityColumn.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_priorityColumnTitle);
          fPriorityColumn.setResizable(true);
  
          TableViewerColumn modifierColumnViewer = new TableViewerColumn(fHoverTableViewer, SWT.NONE);
          modifierColumnViewer.setLabelProvider(new PyEditorTextHoverDescriptorLabelProvider(fHoverTableViewer));
          fModifierColumn = modifierColumnViewer.getColumn();
          fModifierColumn.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_modifierColumnTitle);
          fModifierColumn.setResizable(true);
  
          TableViewerColumn preemptViewerColumn = new TableViewerColumn(fHoverTableViewer, SWT.NONE);
          preemptViewerColumn.setEditingSupport(new HoverTableEditingSupport(fHoverTableViewer, PREEMPT_PROP));
          preemptViewerColumn.setLabelProvider(new PyEditorTextHoverDescriptorLabelProvider(fHoverTableViewer));
  
          fPreemptColumn = preemptViewerColumn.getColumn();
          fPreemptColumn.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_preemptColumnTitle);
          fPreemptColumn.setResizable(true);
  
          fHoverTableViewer.setUseHashlookup(true);
          fHoverTableViewer.setContentProvider(new PyEditorTextHoverDescriptorContentProvider());
  
          fHoverTableViewer.addCheckStateListener(new ICheckStateListener() {
              /*
               * @see org.eclipse.jface.viewers.ICheckStateListener#checkStateChanged(org.eclipse.jface.viewers.Che((CheckboxTreeViewer) fHoverTableViewer)nt)
               */
              @Override
              public void checkStateChanged(CheckStateChangedEvent event) {
                  String id = ((PyEditorTextHoverDescriptor) event.getElement()).getId();
                  if (id == null) {
                      return;
                  }
                  PyEditorTextHoverDescriptor[] descriptors = fHoverDescs;
                  PyEditorTextHoverDescriptor hover = null;
                  int i = 0, length = fHoverDescs.length;
                  while (i < length) {
                      if (id.equals(descriptors[i].getId())) {
                          hover = fHoverDescs[i];
                          hover.setIsEnabled(event.getChecked());
                          fModifierEditor.setEnabled(event.getChecked());
                          fHoverTableViewer.setSelection(new StructuredSelection(descriptors[i]));
                      }
                      i++;
                  }
                  handleHoverListSelection();
                  updateStatus(hover);
              }
          });
  
          fHoverTableViewer.setSorter(new ViewerSorter() {
  
              @Override
              public int compare(Viewer viewer, Object e1, Object e2) {
                  return ((PyEditorTextHoverDescriptor) e1).getPriority()
                          .compareTo(((PyEditorTextHoverDescriptor) e2).getPriority());
              }
          });
  
          // Text field for modifier string
          fModifierFieldLabel = new Label(hoverComposite, SWT.LEFT);
          fModifierFieldLabel.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_keyModifier);
          fModifierEditor = new Text(hoverComposite, SWT.BORDER);
          gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
          fModifierEditor.setLayoutData(gd);
  
          fModifierEditor.addKeyListener(new KeyModifierListener(fModifierEditor));
  
          fModifierEditor.addModifyListener(new ModifyListener() {
              @Override
              public void modifyText(ModifyEvent e) {
                  handleModifierModified(fModifierEditor);
              }
          });
  
          // Description
          Label label = new Label(hoverComposite, SWT.LEFT);
          label.setText(PyEditorMessages.PyEditorHoverConfigurationBlock_description);
          gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING);
          gd.horizontalSpan = 2;
          label.setLayoutData(gd);
          fDescription = new Text(hoverComposite, SWT.LEFT | SWT.WRAP | SWT.MULTI | SWT.READ_ONLY | SWT.BORDER);
          SWTUtil.fixReadonlyTextBackground(fDescription);
          gd = new GridData(GridData.FILL_BOTH);
          gd.horizontalSpan = 2;
          fDescription.setLayoutData(gd);
  
          fDebugShowVars = new Button(parent, SWT.CHECK);
          fDebugShowVars.setText("Show variables values while debugging");
          fDebugShowVars.setSelection(PyHoverPreferencesPage.getShowValuesWhileDebuggingOnHover());
          fUseHoverDivider = new Button(parent, SWT.CHECK);
          fUseHoverDivider.setText("Add divider between contributions when combining hovers");
          fUseHoverDivider.setSelection(PyHoverPreferencesPage.getUseHoverDelimiters());
  
          scrolled.setContent(hoverComposite);
          final Point size = hoverComposite.computeSize(SWT.DEFAULT, SWT.DEFAULT);
          scrolled.setMinSize(size.x, size.y);
  
          Dialog.applyDialogFont(scrolled);
  
          return fComp;
  
      }
  
      private void addColumnLayoutData(TableLayoutComposite layouter, int[] widths) {
          layouter.addColumnData(new ColumnWeightData(widths[0], true));
          layouter.addColumnData(new ColumnWeightData(widths[1], true));
          layouter.addColumnData(new ColumnWeightData(widths[2], true));
          layouter.addColumnData(new ColumnWeightData(widths[3], true));
      }
  
      private PyEditorTextHoverDescriptor[] getContributedHovers() {
          return PydevPlugin.getDefault().getPyEditorTextHoverDescriptors();
      }
  
      @Override
      public void initialize() {
          //need to do this asynchronously, or it has no effect
          new UIJob("Show/Hide Column") {
  
              @Override
              public IStatus runInUIThread(IProgressMonitor monitor) {
                  showColumn(fPreemptColumn, PyHoverPreferencesPage.getCombineHoverInfo());
                  showColumn(fModifierColumn, !PyHoverPreferencesPage.getCombineHoverInfo());
                  fModifierFieldLabel.setEnabled(!fCombineHovers.getSelection());
                  return Status.OK_STATUS;
              }
  
          }.schedule();
          doInit(true);
      }
  
      private void doInit(boolean refreshHovers) {
          if (refreshHovers) {
              fHoverDescs = getContributedHovers();
              fCombiningHover = PydevPlugin.getCombiningHoverDescriptor();
          }
          fHoverTableViewer.setInput(fHoverDescs);
          fHoverTableViewer.refresh();
          initializeFields();
      }
  
      void initializeFields() {
          fModifierEditor.setEnabled(false);
  
          fCombineHovers.setSelection(PyHoverPreferencesPage.getCombineHoverInfo());
          fUseFirstHover.setSelection(!PyHoverPreferencesPage.getCombineHoverInfo());
          fCombiningHoverModifierEditor.setEnabled(PyHoverPreferencesPage.getCombineHoverInfo());
          fCombiningHoverLabel.setEnabled(PyHoverPreferencesPage.getCombineHoverInfo());
  
          for (int i = 0; i < fHoverDescs.length; i++) {
              fHoverTable.getItem(i).setChecked(fHoverDescs[i].isEnabled());
          }
          fHoverTableViewer.refresh();
          fCombiningHoverModifierEditor.setText(fCombiningHover.fModifierString);
      }
  
      @Override
      public void performOk() {
          Integer modifierMask = 0;
          Integer priority = 0;
          Boolean preempt = false;
          PyEditorTextHoverDescriptor[] hoverDescripters = (PyEditorTextHoverDescriptor[]) fHoverTableViewer.getInput();
          sortHoverDescriptors();
  
          if (hoverDescripters != fHoverDescs) {
              Log.log("Expecting hoverDescripters to be == fHoverDescs");
          }
          //save preferences for configured hovers
          for (int i = 0; i < hoverDescripters.length; i++) {
              PyEditorTextHoverDescriptor hoverDesc = fHoverDescs[i];
              String modifier = hoverDesc.fModifierString;
              if (modifier == null || modifier.length() == 0) {
                  modifier = PyEditorTextHoverDescriptor.NO_MODIFIER;
              }
              modifierMask = hoverDesc.fStateMask;
              priority = hoverDesc.getPriority();
              preempt = hoverDesc.isPreempt();
  
              Boolean enable = hoverDesc.isEnabled();
              String hoverDescId = hoverDesc.getId();
              PyDevUiPrefs.getPreferenceStore().setValue(
                      PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER + hoverDescId, modifier);
              PyDevUiPrefs.getPreferenceStore().setValue(
                      PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER_MASK + hoverDescId, modifierMask);
              PyDevUiPrefs.getPreferenceStore().setValue(
                      PyHoverPreferencesPage.KEY_TEXT_HOVER_PRIORITY + hoverDescId, priority);
              PyDevUiPrefs.getPreferenceStore().setValue(
                      PyHoverPreferencesPage.KEY_TEXT_HOVER_PREEMPT + hoverDescId, preempt);
              PyDevUiPrefs.getPreferenceStore().setValue(
                      PyHoverPreferencesPage.KEY_TEXT_HOVER_ENABLE + hoverDescId, enable);
          }
  
          //save preferences for the combining hover
          PyDevUiPrefs.getPreferenceStore().setValue(
                  PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER + fCombiningHover.getId(),
                  fCombiningHover.getModifierString());
          PyDevUiPrefs.getPreferenceStore().setValue(
                  PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER_MASK + fCombiningHover.getId(),
                  fCombiningHover.fStateMask);
          PyDevUiPrefs.getPreferenceStore().setValue(
                  PyHoverPreferencesPage.KEY_TEXT_HOVER_ENABLE + fCombiningHover.getId(),
                  PyHoverPreferencesPage.getCombineHoverInfo());
  
          //save general hover preferences
          PydevPlugin.getDefault().getPreferenceStore().setValue(PyHoverPreferencesPage.COMBINE_HOVER_INFO,
                  fCombineHovers.getSelection());
          PydevPlugin.getDefault().getPreferenceStore().setValue(
                  PyHoverPreferencesPage.SHOW_DEBUG_VARIABLES_VALUES_ON_HOVER,
                  fDebugShowVars.getSelection());
          PydevPlugin.getDefault().getPreferenceStore().setValue(PyHoverPreferencesPage.USE_HOVER_DIVIDER,
                  fUseHoverDivider.getSelection());
  
          PydevPlugin.getDefault().resetPyEditorTextHoverDescriptors();
          PydevCombiningHover.installTextHovers();
      }
  
      /**
       * Sort the hover descriptors to match the order they appear in the viewer,
       * which has a viewer sorter that sorts by priority
       */
      private void sortHoverDescriptors() {
          Arrays.sort(fHoverDescs, new Comparator<PyEditorTextHoverDescriptor>() {
  
              @Override
              public int compare(PyEditorTextHoverDescriptor o1, PyEditorTextHoverDescriptor o2) {
                  return o1.getPriority().compareTo(o2.getPriority());
              }
  
          });
      }
  
      @Override
      public void performCancel() {
          PydevPlugin.getDefault().resetPyEditorTextHoverDescriptors();
      }
  
      @Override
      public void performDefaults() {
          restoreFromPreferences();
          doInit(false);
          initializeFields();
          updateStatus(null);
          setcheckStates();
          if (!fHoverTableViewer.getSelection().isEmpty()) {
              handleHoverListSelection();
          }
      }
  
      private void setcheckStates() {
          for (PyEditorTextHoverDescriptor hover : fHoverDescs) {
              fHoverTableViewer.setChecked(hover, hover.isEnabled());
          }
      }
  
      /**
       * Computes the state mask for the given modifier string.
       *
       * @param modifiers the string with the modifiers, separated by '+', '-', ';', ',' or '.'
       * @return the state mask or -1 if the input is invalid
       */
      public int computeStateMask(String modifiers) {
          if (modifiers == null) {
              return -1;
          }
  
          if (modifiers.length() == 0) {
              return SWT.NONE;
          }
  
          int stateMask = 0;
          StringTokenizer modifierTokenizer = new StringTokenizer(modifiers, ",;.:+-* "); //$NON-NLS-1$
          while (modifierTokenizer.hasMoreTokens()) {
              int modifier = PyAction.findLocalizedModifier(modifierTokenizer.nextToken());
              if (modifier == 0 || (stateMask & modifier) == modifier) {
                  return -1;
              }
              stateMask = stateMask | modifier;
          }
          return stateMask;
      }
  
      private void restoreFromPreferences() {
  
          //restore settings for contributed hovers
          for (int i = 0; i < fHoverDescs.length; i++) {
              String modifierString = PyDevUiPrefs.getPreferenceStore()
                      .getDefaultString(PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER + fHoverDescs[i].getId());
  
              if (PyEditorTextHoverDescriptor.NO_MODIFIER.equals(modifierString)) {
                  modifierString = ""; //$NON-NLS-1$
              }
  
              fHoverDescs[i].fModifierString = modifierString;
  
              fHoverDescs[i].fStateMask = computeStateMask(modifierString);
              if (fHoverDescs[i].fStateMask == -1) {
                  try {
                      fHoverDescs[i].fStateMask = Integer
                              .parseInt(PyDevUiPrefs.getPreferenceStore().getString(
                                      PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER_MASK + fHoverDescs[i].getId()));
                  } catch (NumberFormatException ex) {
                      fHoverDescs[i].fStateMask = -1;
                  }
              }
  
              fHoverDescs[i].setIsEnabled(PydevPlugin.getDefault().getPreferenceStore()
                      .getDefaultBoolean(PyHoverPreferencesPage.KEY_TEXT_HOVER_ENABLE + fHoverDescs[i].getId()));
              fHoverDescs[i].setPriority(PydevPlugin.getDefault().getPreferenceStore()
                      .getDefaultInt(PyHoverPreferencesPage.KEY_TEXT_HOVER_PRIORITY + fHoverDescs[i].getId()));
              fHoverDescs[i].setIsPreempt(PydevPlugin.getDefault().getPreferenceStore()
                      .getDefaultBoolean(PyHoverPreferencesPage.KEY_TEXT_HOVER_PREEMPT + fHoverDescs[i].getId()));
              fHoverTableViewer.refresh();
              sortHoverDescriptors();
          }
  
          //restore settings for combining hover
          String modifierString = PyDevUiPrefs.getPreferenceStore().getDefaultString(
                  PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER + fCombiningHover.getId());
          if (PyEditorTextHoverDescriptor.NO_MODIFIER.equals(modifierString)) {
              modifierString = ""; //$NON-NLS-1$
          }
          fCombiningHover.fModifierString = modifierString;
          //state mask
          fCombiningHover.fStateMask = PyEditorTextHoverDescriptor.computeStateMask(modifierString);
          if (fCombiningHover.fStateMask == -1) {
              // Fallback: use stored modifier masks
              try {
                  fCombiningHover.fStateMask = Integer.parseInt(PyDevUiPrefs.getPreferenceStore().getString(
                          PyHoverPreferencesPage.KEY_TEXT_HOVER_MODIFIER_MASK + fCombiningHover.getId()));
              } catch (NumberFormatException ex) {
                  fCombiningHover.fStateMask = -1;
              }
              // Fix modifier string
              int stateMask = fCombiningHover.fStateMask;
              if (stateMask == -1) {
                  fCombiningHover.fModifierString = ""; //$NON-NLS-1$
              } else {
                  fCombiningHover.fModifierString = PyAction.getModifierString(stateMask);
              }
          }
      }
  
      private void handleModifierModified(Text source) {
          int i = fHoverTable.getSelectionIndex();
          PyEditorTextHoverDescriptor hover = null;
          Text editor = source;
          if (source == fCombiningHoverModifierEditor) {
              hover = PydevPlugin.getCombiningHoverDescriptor();
          } else {
              if (i < 0) {
                  return;
              }
              hover = fHoverDescs[i];
          }
  
          String modifiers = editor.getText();
          hover.fModifierString = modifiers;
          hover.fStateMask = PyEditorTextHoverDescriptor.computeStateMask(modifiers);
  
          // update table
          if (!fHoverTableViewer.isCellEditorActive() && i >= 0) {
              fHoverTableViewer.refresh(fHoverDescs[i]);
          }
  
          updateStatus(hover);
      }
  
      private void handleSetPriority(Integer priority) {
          int i = fHoverTable.getSelectionIndex();
          if (i == -1) {
              return;
          }
          PyEditorTextHoverDescriptor hover = fHoverDescs[i];
          hover.setPriority(priority);
  
          /**
           * Ensure hover configs and hover descriptors are in same order
           */
          fHoverTableViewer.update(fHoverDescs[i], null);
          sortHoverDescriptors();
          fHoverTableViewer.refresh();
  
          updateStatus(hover);
      }
  
      public void handleSetPreempt(Boolean preempt) {
          int i = fHoverTable.getSelectionIndex();
          if (i == -1) {
              return;
          }
  
          PyEditorTextHoverDescriptor hover = fHoverDescs[i];
          hover.setIsPreempt(preempt);
  
          // update table
          fHoverTableViewer.refresh(((IStructuredSelection) fHoverTableViewer.getSelection()).getFirstElement());
  
          updateStatus(hover);
      }
  
      private void handleHoverListSelection() {
          int i = fHoverTable.getSelectionIndex();
  
          if (i == -1) {
              if (fHoverTable.getSelectionCount() == 0) {
                  fModifierEditor.setEnabled(false);
              }
              return;
          }
          PyEditorTextHoverDescriptor hover = fHoverDescs[i];
          boolean enabled = hover.isEnabled();
          fModifierEditor.setEnabled(enabled && !fCombineHovers.getSelection());
          fModifierEditor.setText(hover.fModifierString);
          String description = fHoverDescs[i].getDescription();
          if (description == null) {
              description = ""; //$NON-NLS-1$
          }
          fDescription.setText(description);
      }
  
      IStatus getStatus() {
          if (fStatus == null) {
  