protected JLabel createActionLabel(
      final AnAction anAction,
      final String anActionName,
      final Color fg,
      final Color bg,
      final Icon icon) {
    final LayeredIcon layeredIcon = new LayeredIcon(2);
    layeredIcon.setIcon(EMPTY_ICON, 0);
    if (icon != null
        && icon.getIconWidth() <= EMPTY_ICON.getIconWidth()
        && icon.getIconHeight() <= EMPTY_ICON.getIconHeight()) {
      layeredIcon.setIcon(
          icon,
          1,
          (-icon.getIconWidth() + EMPTY_ICON.getIconWidth()) / 2,
          (EMPTY_ICON.getIconHeight() - icon.getIconHeight()) / 2);
    }

    final Shortcut[] shortcutSet =
        KeymapManager.getInstance().getActiveKeymap().getShortcuts(getActionId(anAction));
    final String actionName =
        anActionName
            + (shortcutSet != null && shortcutSet.length > 0
                ? " (" + KeymapUtil.getShortcutText(shortcutSet[0]) + ")"
                : "");
    final JLabel actionLabel = new JLabel(actionName, layeredIcon, SwingConstants.LEFT);
    actionLabel.setBackground(bg);
    actionLabel.setForeground(fg);
    return actionLabel;
  }
Beispiel #2
0
    public int getIconWidth(SynthContext context) {
      Icon icon = getIcon(context);

      if (icon != null) {
        return icon.getIconWidth();
      }
      return 0;
    }
  private void addErrorMessage(final FixableMessageInfo message, Icon icon) {
    if (message.myLinkText.length() > 0 || message.myAfterLinkText.length() > 0) {
      HyperlinkLabel warnLabel = new HyperlinkLabel();
      warnLabel.setOpaque(false);
      warnLabel.setHyperlinkText(
          message.myBeforeLinkText, message.myLinkText, message.myAfterLinkText);
      warnLabel.setIcon(icon);

      if (message.myQuickFix != null) {
        warnLabel.addHyperlinkListener(
            new HyperlinkListener() {
              public void hyperlinkUpdate(final HyperlinkEvent e) {
                if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                  message.myQuickFix.run();
                }
              }
            });
      }
      myErrorMessages.add(warnLabel);
    } else {
      JBLabel warnLabel = new JBLabel();
      warnLabel.setOpaque(false);
      warnLabel.setText(
          "<html><body>" + message.myBeforeLinkText.replace("\n", "<br>") + "</body></html>");
      warnLabel.setIcon(icon);
      myErrorMessages.add(warnLabel);
    }
    if (message.myAdditionalFixes != null && message.myAdditionalFixes.size() > 0) {
      JPanel fixesPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
      fixesPanel.setBorder(IdeBorderFactory.createEmptyBorder(3, 0, 10, 0));
      fixesPanel.setOpaque(false);
      fixesPanel.add(Box.createHorizontalStrut(icon.getIconWidth()));

      for (Pair<String, Runnable> pair : message.myAdditionalFixes) {
        HyperlinkLabel fixLabel = new HyperlinkLabel();
        fixLabel.setOpaque(false);
        fixLabel.setHyperlinkText(pair.getFirst());
        final Runnable fix = pair.getSecond();

        fixLabel.addHyperlinkListener(
            new HyperlinkListener() {
              @Override
              public void hyperlinkUpdate(HyperlinkEvent e) {
                if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                  fix.run();
                }
              }
            });
        fixesPanel.add(fixLabel);
      }
      myErrorMessages.add(fixesPanel);
    }
  }
  protected boolean doSetIcon(
      DefaultMutableTreeNode node, @Nullable String path, Component component) {
    if (StringUtil.isNotEmpty(path) && !new File(path).isFile()) {
      Messages.showErrorDialog(
          component,
          IdeBundle.message("error.file.not.found.message", path),
          IdeBundle.message("title.choose.action.icon"));
      return false;
    }

    String actionId = getActionId(node);
    if (actionId == null) return false;

    final AnAction action = ActionManager.getInstance().getAction(actionId);
    if (action != null && action.getTemplatePresentation() != null) {
      if (StringUtil.isNotEmpty(path)) {
        Image image = null;
        try {
          image =
              ImageLoader.loadFromStream(
                  VfsUtil.convertToURL(VfsUtil.pathToUrl(path.replace(File.separatorChar, '/')))
                      .openStream());
        } catch (IOException e) {
          LOG.debug(e);
        }
        Icon icon = new File(path).exists() ? IconLoader.getIcon(image) : null;
        if (icon != null) {
          if (icon.getIconWidth() > EmptyIcon.ICON_18.getIconWidth()
              || icon.getIconHeight() > EmptyIcon.ICON_18.getIconHeight()) {
            Messages.showErrorDialog(
                component,
                IdeBundle.message("custom.icon.validation.message"),
                IdeBundle.message("title.choose.action.icon"));
            return false;
          }
          node.setUserObject(Pair.create(actionId, icon));
          mySelectedSchema.addIconCustomization(actionId, path);
        }
      } else {
        node.setUserObject(Pair.create(actionId, null));
        mySelectedSchema.removeIconCustomization(actionId);
        final DefaultMutableTreeNode nodeOnToolbar = findNodeOnToolbar(actionId);
        if (nodeOnToolbar != null) {
          editToolbarIcon(actionId, nodeOnToolbar);
          node.setUserObject(nodeOnToolbar.getUserObject());
        }
      }
      return true;
    }
    return false;
  }
  /**
   * Initializes buttons panel.
   *
   * @param uiGroup the <tt>UIGroup</tt> for which we initialize the button panel
   */
  private void initButtonsPanel(UIGroup uiGroup) {
    if (!isSelected) return;

    int x = (statusIcon == null ? 0 : statusIcon.getIconWidth()) + LEFT_BORDER + H_GAP;
    int gridX = 0;

    // The list of the actions
    // we will create a button for every action
    Collection<SIPCommButton> contactActions = uiGroup.getCustomActionButtons();

    int lastGridX = gridX;
    if (contactActions != null && contactActions.size() > 0) {
      lastGridX = initGroupActionButtons(contactActions, gridX, x);
    } else {
      addLabels(gridX);
    }

    if (lastAddedButton != null) setButtonBg(lastAddedButton, lastGridX, true);

    this.setBounds(0, 0, treeContactList.getWidth(), getPreferredSize().height);
  }
  /**
   * Returns the drag icon used to represent a cell in all drag operations.
   *
   * @param tree the parent tree object
   * @param dragObject the dragged object
   * @param index the index of the dragged object in the tree
   * @return the drag icon
   */
  public Icon getDragIcon(JTree tree, Object dragObject, int index) {
    ContactListTreeCellRenderer dragC =
        (ContactListTreeCellRenderer)
            getTreeCellRendererComponent(
                tree,
                dragObject,
                false, // is selected
                false, // is expanded
                true, // is leaf
                index,
                true // has focus
                );

    // We should explicitly set the bounds of all components in order that
    // they're correctly painted by paintIcon afterwards. This fixes empty
    // drag component in contact list!
    dragC.setBounds(0, 0, dragC.getIconWidth(), dragC.getIconHeight());

    Icon rightLabelIcon = rightLabel.getIcon();
    int imageHeight = 0;
    int imageWidth = 0;
    if (rightLabelIcon != null) {
      imageWidth = rightLabelIcon.getIconWidth();
      imageHeight = rightLabelIcon.getIconHeight();
      dragC.rightLabel.setBounds(tree.getWidth() - imageWidth, 0, imageWidth, imageHeight);
    }

    dragC.statusLabel.setBounds(0, 0, statusLabel.getWidth(), statusLabel.getHeight());

    dragC.nameLabel.setBounds(
        statusLabel.getWidth(), 0, tree.getWidth() - imageWidth - 5, nameLabel.getHeight());

    dragC.displayDetailsLabel.setBounds(
        displayDetailsLabel.getX(),
        nameLabel.getHeight(),
        displayDetailsLabel.getWidth(),
        displayDetailsLabel.getHeight());

    return dragC;
  }
Beispiel #7
0
  @Nullable
  public static Range<Integer> getExpandControlRange(final JTree aTree, final TreePath path) {
    TreeModel treeModel = aTree.getModel();

    final BasicTreeUI basicTreeUI = (BasicTreeUI) aTree.getUI();
    Icon expandedIcon = basicTreeUI.getExpandedIcon();

    Range<Integer> box = null;
    if (path != null && !treeModel.isLeaf(path.getLastPathComponent())) {
      int boxWidth;
      Insets i = aTree.getInsets();

      if (expandedIcon != null) {
        boxWidth = expandedIcon.getIconWidth();
      } else {
        boxWidth = 8;
      }

      int boxLeftX = i != null ? i.left : 0;

      boolean leftToRight = aTree.getComponentOrientation().isLeftToRight();
      int depthOffset = getDepthOffset(aTree);
      int totalChildIndent = basicTreeUI.getLeftChildIndent() + basicTreeUI.getRightChildIndent();

      if (leftToRight) {
        boxLeftX +=
            ((path.getPathCount() + depthOffset - 2) * totalChildIndent
                    + basicTreeUI.getLeftChildIndent())
                - boxWidth / 2;
      }
      int boxRightX = boxLeftX + boxWidth;

      box = new Range<Integer>(boxLeftX, boxRightX);
    }
    return box;
  }
  public RegistryUi() {
    myContent.setLayout(new BorderLayout(UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP));

    myModel = new MyTableModel();
    myTable = new JBTable(myModel);
    myTable.setCellSelectionEnabled(true);
    myTable.setEnableAntialiasing(true);
    final MyRenderer r = new MyRenderer();

    final TableColumn c0 = myTable.getColumnModel().getColumn(0);
    c0.setCellRenderer(r);
    c0.setMaxWidth(RESTART_ICON.getIconWidth() + 12);
    c0.setMinWidth(RESTART_ICON.getIconWidth() + 12);
    c0.setHeaderValue(null);

    final TableColumn c1 = myTable.getColumnModel().getColumn(1);
    c1.setCellRenderer(r);
    c1.setHeaderValue("Key");

    final TableColumn c2 = myTable.getColumnModel().getColumn(2);
    c2.setCellRenderer(r);
    c2.setHeaderValue("Value");
    c2.setCellEditor(new MyEditor());
    myTable.setStriped(true);

    myDescriptionLabel = new JTextArea(3, 50);
    myDescriptionLabel.setEditable(false);
    final JScrollPane label = ScrollPaneFactory.createScrollPane(myDescriptionLabel);
    final JPanel descriptionPanel = new JPanel(new BorderLayout());
    descriptionPanel.add(label, BorderLayout.CENTER);
    descriptionPanel.setBorder(
        IdeBorderFactory.createTitledBorder("Description", false, false, true));

    myContent.add(ScrollPaneFactory.createScrollPane(myTable), BorderLayout.CENTER);
    myContent.add(descriptionPanel, BorderLayout.SOUTH);

    myTable
        .getSelectionModel()
        .addListSelectionListener(
            new ListSelectionListener() {
              public void valueChanged(ListSelectionEvent e) {
                if (e.getValueIsAdjusting()) return;

                final int selected = myTable.getSelectedRow();
                if (selected != -1) {
                  final RegistryValue value = myModel.getRegistryValue(selected);
                  String desc = value.getDescription();
                  if (value.isRestartRequired()) {
                    String required = "Requires IDE restart.";
                    if (desc.endsWith(".")) {
                      desc += required;
                    } else {
                      desc += (". " + required);
                    }
                  }
                  myDescriptionLabel.setText(desc);
                } else {
                  myDescriptionLabel.setText(null);
                }
              }
            });

    myRestoreDefaultsAction = new RestoreDefaultsAction();

    final DefaultActionGroup tbGroup = new DefaultActionGroup();
    tbGroup.add(new EditAction());
    tbGroup.add(new RevertAction());

    final ActionToolbar tb =
        ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, tbGroup, true);
    tb.setTargetComponent(myTable);

    myContent.add(tb.getComponent(), BorderLayout.NORTH);
    new TableSpeedSearch(myTable).setComparator(new SpeedSearchComparator(false));
  }
  /**
   * Initializes buttons panel.
   *
   * @param uiContact the <tt>UIContact</tt> for which we initialize the button panel
   */
  private void initButtonsPanel(UIContact uiContact) {
    this.remove(chatButton);
    this.remove(callButton);
    this.remove(callVideoButton);
    this.remove(desktopSharingButton);
    this.remove(addContactButton);

    clearCustomActionButtons();

    if (!isSelected) return;

    UIContactDetail imContact = null;
    // For now we support instance messaging only for contacts in our
    // contact list until it's implemented for external source contacts.
    if (uiContact.getDescriptor() instanceof MetaContact)
      imContact = uiContact.getDefaultContactDetail(OperationSetBasicInstantMessaging.class);

    int x = (statusIcon == null ? 0 : statusIcon.getIconWidth()) + LEFT_BORDER + H_GAP;

    // Re-initialize the x grid.
    constraints.gridx = 0;
    int gridX = 0;

    if (imContact != null) {
      x += addButton(chatButton, ++gridX, x, false);
    }

    UIContactDetail telephonyContact =
        uiContact.getDefaultContactDetail(OperationSetBasicTelephony.class);

    // Check if contact has additional phone numbers, if yes show the
    // call button
    ContactPhoneUtil contactPhoneUtil = null;

    // check for phone stored in contact info only
    // if telephony contact is missing
    if (uiContact.getDescriptor() != null
        && uiContact.getDescriptor() instanceof MetaContact
        && telephonyContact == null) {
      contactPhoneUtil = ContactPhoneUtil.getPhoneUtil((MetaContact) uiContact.getDescriptor());

      MetaContact metaContact = (MetaContact) uiContact.getDescriptor();
      Iterator<Contact> contacts = metaContact.getContacts();

      while (contacts.hasNext()) // && !hasPhone)
      {
        Contact contact = contacts.next();

        if (!contact.getProtocolProvider().isRegistered()) continue;

        contactPhoneUtil.addDetailsResponseListener(
            contact, new DetailsListener(treeNode, callButton, uiContact));
      }
    }

    // for SourceContact in history that do not support telephony, we
    // show the button but disabled
    List<ProtocolProviderService> providers =
        AccountUtils.getOpSetRegisteredProviders(OperationSetBasicTelephony.class, null, null);

    if ((telephonyContact != null && telephonyContact.getAddress() != null)
        || (contactPhoneUtil != null && contactPhoneUtil.isCallEnabled() && providers.size() > 0)) {
      x += addButton(callButton, ++gridX, x, false);
    }

    UIContactDetail videoContact =
        uiContact.getDefaultContactDetail(OperationSetVideoTelephony.class);

    if (videoContact != null
        || (contactPhoneUtil != null && contactPhoneUtil.isVideoCallEnabled())) {
      x += addButton(callVideoButton, ++gridX, x, false);
    }

    UIContactDetail desktopContact =
        uiContact.getDefaultContactDetail(OperationSetDesktopSharingServer.class);

    if (desktopContact != null
        || (contactPhoneUtil != null && contactPhoneUtil.isDesktopSharingEnabled())) {
      x += addButton(desktopSharingButton, ++gridX, x, false);
    }

    // enable add contact button if contact source has indicated
    // that this is possible
    if (uiContact.getDescriptor() instanceof SourceContact
        && uiContact.getDefaultContactDetail(OperationSetPersistentPresence.class) != null
        && AccountUtils.getOpSetRegisteredProviders(
                    OperationSetPersistentPresence.class, null, null)
                .size()
            > 0
        && !ConfigurationUtils.isAddContactDisabled()) {
      x += addButton(addContactButton, ++gridX, x, false);
    }

    // The list of the contact actions
    // we will create a button for every action
    Collection<SIPCommButton> contactActions = uiContact.getContactCustomActionButtons();

    int lastGridX = gridX;
    if (contactActions != null && contactActions.size() > 0) {
      lastGridX = initContactActionButtons(contactActions, gridX, x);
    } else {
      addLabels(gridX);
    }

    if (lastAddedButton != null) setButtonBg(lastAddedButton, lastGridX, true);

    this.setBounds(0, 0, treeContactList.getWidth(), getPreferredSize().height);
  }
public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMarkupModel {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.editor.impl.EditorMarkupModelImpl");

  private static final TooltipGroup ERROR_STRIPE_TOOLTIP_GROUP =
      new TooltipGroup("ERROR_STRIPE_TOOLTIP_GROUP", 0);
  private static final Icon ERRORS_FOUND_ICON = IconLoader.getIcon("/general/errorsFound.png");
  private static final int ERROR_ICON_WIDTH = ERRORS_FOUND_ICON.getIconWidth();
  private static final int ERROR_ICON_HEIGHT = ERRORS_FOUND_ICON.getIconHeight();
  private static final int PREFERRED_WIDTH = ERRORS_FOUND_ICON.getIconWidth() + 3;
  private final EditorImpl myEditor;
  private ErrorStripeRenderer myErrorStripeRenderer = null;
  private final List<ErrorStripeListener> myErrorMarkerListeners =
      new ArrayList<ErrorStripeListener>();
  private ErrorStripeListener[] myCachedErrorMarkerListeners = null;

  private int myEditorScrollbarTop = -1;
  private int myEditorTargetHeight = -1;
  private int myEditorSourceHeight = -1;

  @NotNull
  private ErrorStripTooltipRendererProvider myTooltipRendererProvider =
      new BasicTooltipRendererProvider();

  private static int myMinMarkHeight = 3;

  EditorMarkupModelImpl(@NotNull EditorImpl editor) {
    super((DocumentImpl) editor.getDocument());
    myEditor = editor;
  }

  private int offsetToLine(int offset, Document document) {
    if (offset > document.getTextLength()) {
      return document.getLineCount();
    }
    return myEditor.offsetToVisualLine(offset);
  }

  private static int getMinHeight() {
    return myMinMarkHeight;
  }

  private void recalcEditorDimensions() {
    EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar();
    int scrollBarHeight = scrollBar.getSize().height;

    myEditorScrollbarTop = scrollBar.getDecScrollButtonHeight() /* + 1*/;
    int editorScrollbarBottom = scrollBar.getIncScrollButtonHeight();
    myEditorTargetHeight = scrollBarHeight - myEditorScrollbarTop - editorScrollbarBottom;
    myEditorSourceHeight = myEditor.getPreferredHeight();
  }

  public void repaintTrafficLightIcon() {
    MyErrorPanel errorPanel = getErrorPanel();
    if (errorPanel != null) {
      errorPanel.myErrorStripeButton.repaint();
      errorPanel.repaintTrafficTooltip();
    }
  }

  private static class PositionedStripe {
    private final Color color;
    private final int yStart;
    private int yEnd;
    private final boolean thin;
    private final int layer;

    private PositionedStripe(Color color, int yStart, int yEnd, boolean thin, int layer) {
      this.color = color;
      this.yStart = yStart;
      this.yEnd = yEnd;
      this.thin = thin;
      this.layer = layer;
    }
  }

  private boolean showToolTipByMouseMove(final MouseEvent e, final double width) {
    MouseEvent me = e;

    Set<RangeHighlighter> highlighters = new THashSet<RangeHighlighter>();

    getNearestHighlighters(this, me, width, highlighters);
    getNearestHighlighters(
        (MarkupModelEx) myEditor.getDocument().getMarkupModel(getEditor().getProject()),
        me,
        width,
        highlighters);

    if (highlighters.isEmpty()) return false;

    int minDelta = Integer.MAX_VALUE;
    int y = e.getY();

    for (RangeHighlighter each : highlighters) {
      ProperTextRange range = offsetToYPosition(each.getStartOffset(), each.getEndOffset());
      int eachStartY = range.getStartOffset();
      int eachEndY = range.getEndOffset();
      int eachY = eachStartY + (eachEndY - eachStartY) / 2;
      if (Math.abs(e.getY() - eachY) < minDelta) {
        y = eachY;
      }
    }

    me =
        new MouseEvent(
            (Component) e.getSource(),
            e.getID(),
            e.getWhen(),
            e.getModifiers(),
            e.getX(),
            y + 1,
            e.getClickCount(),
            e.isPopupTrigger());

    TooltipRenderer bigRenderer = myTooltipRendererProvider.calcTooltipRenderer(highlighters);
    if (bigRenderer != null) {
      showTooltip(
          me,
          bigRenderer,
          new HintHint(me).setAwtTooltip(true).setPreferredPosition(Balloon.Position.atLeft));
      return true;
    }
    return false;
  }

  private RangeHighlighter getNearestRangeHighlighter(final MouseEvent e, final int width) {
    List<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>();
    getNearestHighlighters(this, e, width, highlighters);
    getNearestHighlighters(
        (MarkupModelEx) myEditor.getDocument().getMarkupModel(myEditor.getProject()),
        e,
        width,
        highlighters);
    RangeHighlighter nearestMarker = null;
    int yPos = 0;
    for (RangeHighlighter highlighter : highlighters) {
      final int newYPos =
          offsetToYPosition(highlighter.getStartOffset(), highlighter.getEndOffset())
              .getStartOffset();

      if (nearestMarker == null || Math.abs(yPos - e.getY()) > Math.abs(newYPos - e.getY())) {
        nearestMarker = highlighter;
        yPos = newYPos;
      }
    }
    return nearestMarker;
  }

  private void getNearestHighlighters(
      MarkupModelEx markupModel,
      MouseEvent e,
      final double width,
      final Collection<RangeHighlighter> nearest) {
    if (0 > e.getX() || e.getX() >= width) return;
    int startOffset = yPositionToOffset(e.getY() - getMinHeight(), true);
    int endOffset = yPositionToOffset(e.getY() + getMinHeight(), false);
    markupModel.processHighlightsOverlappingWith(
        startOffset,
        endOffset,
        new Processor<RangeHighlighterEx>() {
          public boolean process(RangeHighlighterEx highlighter) {
            if (highlighter.getErrorStripeMarkColor() != null) nearest.add(highlighter);
            return true;
          }
        });
  }

  public void doClick(final MouseEvent e, final int width) {
    RangeHighlighter marker = getNearestRangeHighlighter(e, width);
    if (marker == null) return;
    int offset = marker.getStartOffset();

    final Document doc = myEditor.getDocument();
    if (doc.getLineCount() > 0) {
      // Necessary to expand folded block even if navigating just before one
      // Very useful when navigating to first unused import statement.
      int lineEnd = doc.getLineEndOffset(doc.getLineNumber(offset));
      myEditor.getCaretModel().moveToOffset(lineEnd);
    }

    myEditor.getCaretModel().moveToOffset(offset);
    myEditor.getSelectionModel().removeSelection();
    ScrollingModel scrollingModel = myEditor.getScrollingModel();
    scrollingModel.disableAnimation();
    scrollingModel.scrollToCaret(ScrollType.CENTER);
    scrollingModel.enableAnimation();
    fireErrorMarkerClicked(marker, e);
  }

  public void setErrorStripeVisible(boolean val) {
    if (val) {
      myEditor.getVerticalScrollBar().setPersistentUI(new MyErrorPanel());
    } else {
      myEditor.getVerticalScrollBar().setPersistentUI(ButtonlessScrollBarUI.createNormal());
    }
  }

  private MyErrorPanel getErrorPanel() {
    ScrollBarUI ui = myEditor.getVerticalScrollBar().getUI();
    return ui instanceof MyErrorPanel ? (MyErrorPanel) ui : null;
  }

  public void setErrorPanelPopupHandler(@NotNull PopupHandler handler) {
    MyErrorPanel errorPanel = getErrorPanel();
    if (errorPanel != null) {
      errorPanel.setPopupHandler(handler);
    }
  }

  public void setErrorStripTooltipRendererProvider(
      @NotNull final ErrorStripTooltipRendererProvider provider) {
    myTooltipRendererProvider = provider;
  }

  @NotNull
  public ErrorStripTooltipRendererProvider getErrorStripTooltipRendererProvider() {
    return myTooltipRendererProvider;
  }

  @NotNull
  public Editor getEditor() {
    return myEditor;
  }

  public void setErrorStripeRenderer(ErrorStripeRenderer renderer) {
    assertIsDispatchThread();
    myErrorStripeRenderer = renderer;
    // try to not cancel tooltips here, since it is being called after every writeAction, even to
    // the console
    // HintManager.getInstance().getTooltipController().cancelTooltips();

    myEditor.getVerticalScrollBar().repaint();
  }

  private void assertIsDispatchThread() {
    ApplicationManagerEx.getApplicationEx().assertIsDispatchThread(myEditor.getComponent());
  }

  public ErrorStripeRenderer getErrorStripeRenderer() {
    return myErrorStripeRenderer;
  }

  public void dispose() {
    myErrorStripeRenderer = null;
    super.dispose();
  }

  void repaint(int startOffset, int endOffset) {
    markDirtied();

    ProperTextRange range = offsetToYPosition(startOffset, endOffset);

    myEditor
        .getVerticalScrollBar()
        .repaint(0, range.getStartOffset(), PREFERRED_WIDTH, range.getLength() + getMinHeight());
  }

  private boolean isMirrored() {
    return myEditor.getVerticalScrollbarOrientation() == EditorEx.VERTICAL_SCROLLBAR_LEFT;
  }

  private static final Dimension STRIPE_BUTTON_PREFERRED_SIZE =
      new Dimension(PREFERRED_WIDTH, ERROR_ICON_HEIGHT + 4);

  private class ErrorStripeButton extends JButton {
    private ErrorStripeButton() {
      setFocusable(false);
    }

    @Override
    public void paint(Graphics g) {
      ((ApplicationImpl) ApplicationManager.getApplication()).editorPaintStart();

      final Rectangle bounds = getBounds();

      g.setColor(ButtonlessScrollBarUI.TRACK_BACKGROUND);
      g.fillRect(0, 0, bounds.width, bounds.height);

      g.setColor(ButtonlessScrollBarUI.TRACK_BORDER);
      g.drawLine(0, 0, 0, bounds.height);

      try {
        if (myErrorStripeRenderer != null) {
          myErrorStripeRenderer.paint(
              this, g, new Rectangle(5, 2, ERROR_ICON_WIDTH, ERROR_ICON_HEIGHT));
        }
      } finally {
        ((ApplicationImpl) ApplicationManager.getApplication()).editorPaintFinish();
      }
    }

    @Override
    public Dimension getPreferredSize() {
      return STRIPE_BUTTON_PREFERRED_SIZE;
    }
  }

  private class MyErrorPanel extends ButtonlessScrollBarUI
      implements MouseMotionListener, MouseListener {
    private PopupHandler myHandler;
    private ErrorStripeButton myErrorStripeButton;

    @Override
    protected JButton createDecreaseButton(int orientation) {
      myErrorStripeButton = new ErrorStripeButton();
      return myErrorStripeButton;
    }

    @Override
    protected void installListeners() {
      super.installListeners();
      scrollbar.addMouseMotionListener(this);
      scrollbar.addMouseListener(this);
      myErrorStripeButton.addMouseMotionListener(this);
      myErrorStripeButton.addMouseListener(this);
    }

    @Override
    protected void uninstallListeners() {
      scrollbar.removeMouseMotionListener(this);
      scrollbar.removeMouseListener(this);
      myErrorStripeButton.removeMouseMotionListener(this);
      myErrorStripeButton.removeMouseListener(this);
      super.uninstallListeners();
    }

    @Override
    protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
      int shift = isMirrored() ? -9 : 9;
      g.translate(shift, 0);
      super.paintThumb(g, c, thumbBounds);
      g.translate(-shift, 0);
    }

    @Override
    protected int adjustThumbWidth(int width) {
      return width - 2;
    }

    @Override
    protected int getThickness() {
      return super.getThickness() + 7;
    }

    @Override
    protected void paintTrack(Graphics g, JComponent c, Rectangle bounds) {
      g.setColor(TRACK_BACKGROUND);
      g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);

      g.setColor(TRACK_BORDER);
      int border = isMirrored() ? bounds.x + bounds.width - 1 : bounds.x;
      g.drawLine(border, bounds.y, border, bounds.y + bounds.height);

      ((ApplicationImpl) ApplicationManager.getApplication()).editorPaintStart();

      try {
        Rectangle clipBounds = g.getClipBounds();
        repaint(g, ERROR_ICON_WIDTH - 1, clipBounds);
      } finally {
        ((ApplicationImpl) ApplicationManager.getApplication()).editorPaintFinish();
      }
    }

    @Override
    protected Color adjustColor(Color c) {
      return ColorUtil.withAlpha(ColorUtil.shift(super.adjustColor(c), 0.9), 0.85);
    }

    private void repaint(final Graphics g, final int width, Rectangle clipBounds) {
      Document document = myEditor.getDocument();
      int startOffset = yPositionToOffset(clipBounds.y, true);
      int endOffset = yPositionToOffset(clipBounds.y + clipBounds.height, false);

      drawMarkup(g, width, startOffset, endOffset, EditorMarkupModelImpl.this);
      drawMarkup(
          g,
          width,
          startOffset,
          endOffset,
          (MarkupModelEx) document.getMarkupModel(myEditor.getProject()));
    }

    private void drawMarkup(
        final Graphics g, final int width, int startOffset, int endOffset, MarkupModelEx markup) {
      final Queue<PositionedStripe> thinEnds =
          new PriorityQueue<PositionedStripe>(
              5,
              new Comparator<PositionedStripe>() {
                public int compare(PositionedStripe o1, PositionedStripe o2) {
                  return o1.yEnd - o2.yEnd;
                }
              });
      final Queue<PositionedStripe> wideEnds =
          new PriorityQueue<PositionedStripe>(
              5,
              new Comparator<PositionedStripe>() {
                public int compare(PositionedStripe o1, PositionedStripe o2) {
                  return o1.yEnd - o2.yEnd;
                }
              });
      // sorted by layer
      final List<PositionedStripe> thinStripes = new ArrayList<PositionedStripe>();
      final List<PositionedStripe> wideStripes = new ArrayList<PositionedStripe>();
      final int[] thinYStart = new int[1]; // in range 0..yStart all spots are drawn
      final int[] wideYStart = new int[1]; // in range 0..yStart all spots are drawn

      markup.processHighlightsOverlappingWith(
          startOffset,
          endOffset,
          new Processor<RangeHighlighterEx>() {
            public boolean process(RangeHighlighterEx highlighter) {
              Color color = highlighter.getErrorStripeMarkColor();
              if (color == null) return true;
              boolean isThin = highlighter.isThinErrorStripeMark();
              int[] yStart = isThin ? thinYStart : wideYStart;
              List<PositionedStripe> stripes = isThin ? thinStripes : wideStripes;
              Queue<PositionedStripe> ends = isThin ? thinEnds : wideEnds;

              ProperTextRange range =
                  offsetToYPosition(highlighter.getStartOffset(), highlighter.getEndOffset());
              final int ys = range.getStartOffset();
              int ye = range.getEndOffset();
              if (ye - ys < getMinHeight()) ye = ys + getMinHeight();

              yStart[0] = drawStripesEndingBefore(ys, ends, stripes, g, width, yStart[0]);

              final int layer = highlighter.getLayer();

              PositionedStripe stripe = null;
              int i;
              for (i = 0; i < stripes.size(); i++) {
                PositionedStripe s = stripes.get(i);
                if (s.layer == layer) {
                  stripe = s;
                  break;
                }
                if (s.layer < layer) {
                  break;
                }
              }
              if (stripe == null) {
                // started new stripe, draw previous above
                if (yStart[0] != ys) {
                  if (!stripes.isEmpty()) {
                    PositionedStripe top = stripes.get(0);
                    drawSpot(g, width, top.thin, yStart[0], ys, top.color, true, true);
                  }
                  yStart[0] = ys;
                }
                stripe = new PositionedStripe(color, ys, ye, isThin, layer);
                stripes.add(i, stripe);
                ends.offer(stripe);
              } else {
                // key changed, reinsert into queue
                if (stripe.yEnd != ye) {
                  ends.remove(stripe);
                  stripe.yEnd = ye;
                  ends.offer(stripe);
                }
              }

              return true;
            }
          });

      drawStripesEndingBefore(Integer.MAX_VALUE, thinEnds, thinStripes, g, width, thinYStart[0]);
      drawStripesEndingBefore(Integer.MAX_VALUE, wideEnds, wideStripes, g, width, wideYStart[0]);
    }

    private int drawStripesEndingBefore(
        int ys,
        Queue<PositionedStripe> ends,
        List<PositionedStripe> stripes,
        Graphics g,
        int width,
        int yStart) {
      while (!ends.isEmpty()) {
        PositionedStripe endingStripe = ends.peek();
        if (endingStripe.yEnd > ys) break;
        ends.remove();

        // check whether endingStripe got obscured in the range yStart..endingStripe.yEnd
        int i = stripes.indexOf(endingStripe);
        stripes.remove(i);
        if (i == 0) {
          // visible
          drawSpot(
              g,
              width,
              endingStripe.thin,
              yStart,
              endingStripe.yEnd,
              endingStripe.color,
              true,
              true);
          yStart = endingStripe.yEnd;
        }
      }
      return yStart;
    }

    private void drawSpot(
        Graphics g,
        int width,
        boolean thinErrorStripeMark,
        int yStart,
        int yEnd,
        Color color,
        boolean drawTopDecoration,
        boolean drawBottomDecoration) {
      int x = isMirrored() ? 3 : 5;
      int paintWidth = width;
      if (thinErrorStripeMark) {
        paintWidth /= 2;
        paintWidth += 1;
        x = isMirrored() ? width + 2 : 0;
      }
      if (color == null) return;
      g.setColor(color);
      g.fillRect(x + 1, yStart, paintWidth - 2, yEnd - yStart + 1);

      Color brighter = color.brighter();
      g.setColor(brighter);
      // left decoration
      UIUtil.drawLine(g, x, yStart, x, yEnd /* - 1*/);
      if (drawTopDecoration) {
        // top decoration
        UIUtil.drawLine(g, x + 1, yStart, x + paintWidth - 2, yStart);
      }
      Color darker = ColorUtil.shift(color, 0.75);

      g.setColor(darker);
      if (drawBottomDecoration) {
        // bottom decoration
        UIUtil.drawLine(
            g,
            x + 1,
            yEnd /* - 1*/,
            x + paintWidth - 2,
            yEnd /* - 1*/); // large bottom to let overwrite by hl below
      }
      // right decoration
      UIUtil.drawLine(g, x + paintWidth - 2, yStart, x + paintWidth - 2, yEnd /* - 1*/);
    }

    // mouse events
    public void mouseClicked(final MouseEvent e) {
      CommandProcessor.getInstance()
          .executeCommand(
              myEditor.getProject(),
              new Runnable() {
                public void run() {
                  doMouseClicked(e);
                }
              },
              EditorBundle.message("move.caret.command.name"),
              DocCommandGroupId.noneGroupId(getDocument()),
              UndoConfirmationPolicy.DEFAULT,
              getDocument());
    }

    public void mousePressed(MouseEvent e) {}

    public void mouseReleased(MouseEvent e) {}

    private int getWidth() {
      return scrollbar.getWidth();
    }

    private void doMouseClicked(MouseEvent e) {
      myEditor.getContentComponent().requestFocus();
      int lineCount =
          getDocument().getLineCount() + myEditor.getSettings().getAdditionalLinesCount();
      if (lineCount == 0) {
        return;
      }
      doClick(e, getWidth());
    }

    public void mouseMoved(MouseEvent e) {
      EditorImpl.MyScrollBar scrollBar = myEditor.getVerticalScrollBar();
      int buttonHeight = scrollBar.getDecScrollButtonHeight();
      int lineCount =
          getDocument().getLineCount() + myEditor.getSettings().getAdditionalLinesCount();
      if (lineCount == 0) {
        return;
      }

      if (e.getY() < buttonHeight && myErrorStripeRenderer != null) {
        showTrafficLightTooltip(e);
        return;
      }

      if (showToolTipByMouseMove(e, getWidth())) {
        scrollbar.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        return;
      }

      cancelMyToolTips(e, false);

      if (scrollbar.getCursor().equals(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR))) {
        scrollbar.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
      }
    }

    private TrafficTooltipRenderer myTrafficTooltipRenderer;

    private void showTrafficLightTooltip(MouseEvent e) {
      if (myTrafficTooltipRenderer == null) {
        myTrafficTooltipRenderer =
            myTooltipRendererProvider.createTrafficTooltipRenderer(
                new Runnable() {
                  @Override
                  public void run() {
                    myTrafficTooltipRenderer = null;
                  }
                },
                myEditor);
      }
      showTooltip(
          e,
          myTrafficTooltipRenderer,
          new HintHint(e)
              .setAwtTooltip(true)
              .setMayCenterPosition(true)
              .setContentActive(false)
              .setPreferredPosition(Balloon.Position.atLeft));
    }

    private void repaintTrafficTooltip() {
      if (myTrafficTooltipRenderer != null) {
        myTrafficTooltipRenderer.repaintTooltipWindow();
      }
    }

    private void cancelMyToolTips(final MouseEvent e, boolean checkIfShouldSurvive) {
      final TooltipController tooltipController = TooltipController.getInstance();
      if (!checkIfShouldSurvive || !tooltipController.shouldSurvive(e)) {
        tooltipController.cancelTooltip(ERROR_STRIPE_TOOLTIP_GROUP, e, true);
      }
    }

    public void mouseEntered(MouseEvent e) {}

    public void mouseExited(MouseEvent e) {
      cancelMyToolTips(e, true);
    }

    public void mouseDragged(MouseEvent e) {
      cancelMyToolTips(e, true);
    }

    public void setPopupHandler(final PopupHandler handler) {
      if (myHandler != null) {
        scrollbar.removeMouseListener(myHandler);
        myErrorStripeButton.removeMouseListener(myHandler);
      }

      myHandler = handler;
      scrollbar.addMouseListener(handler);
      myErrorStripeButton.addMouseListener(myHandler);
    }
  }

  private void showTooltip(MouseEvent e, final TooltipRenderer tooltipObject, HintHint hintHint) {
    TooltipController tooltipController = TooltipController.getInstance();
    tooltipController.showTooltipByMouseMove(
        myEditor,
        new RelativePoint(e),
        tooltipObject,
        myEditor.getVerticalScrollbarOrientation() == EditorEx.VERTICAL_SCROLLBAR_RIGHT,
        ERROR_STRIPE_TOOLTIP_GROUP,
        hintHint);
  }

  private ErrorStripeListener[] getCachedErrorMarkerListeners() {
    if (myCachedErrorMarkerListeners == null) {
      myCachedErrorMarkerListeners =
          myErrorMarkerListeners.toArray(new ErrorStripeListener[myErrorMarkerListeners.size()]);
    }

    return myCachedErrorMarkerListeners;
  }

  private void fireErrorMarkerClicked(RangeHighlighter marker, MouseEvent e) {
    ErrorStripeEvent event = new ErrorStripeEvent(getEditor(), e, marker);
    ErrorStripeListener[] listeners = getCachedErrorMarkerListeners();
    for (ErrorStripeListener listener : listeners) {
      listener.errorMarkerClicked(event);
    }
  }

  public void addErrorMarkerListener(@NotNull ErrorStripeListener listener) {
    myCachedErrorMarkerListeners = null;
    myErrorMarkerListeners.add(listener);
  }

  public void removeErrorMarkerListener(@NotNull ErrorStripeListener listener) {
    myCachedErrorMarkerListeners = null;
    boolean success = myErrorMarkerListeners.remove(listener);
    LOG.assertTrue(success);
  }

  public void markDirtied() {
    myEditorScrollbarTop = -1;
    myEditorSourceHeight = -1;
    myEditorTargetHeight = -1;
  }

  public void setMinMarkHeight(final int minMarkHeight) {
    myMinMarkHeight = minMarkHeight;
  }

  private static class BasicTooltipRendererProvider implements ErrorStripTooltipRendererProvider {
    public TooltipRenderer calcTooltipRenderer(
        @NotNull final Collection<RangeHighlighter> highlighters) {
      LineTooltipRenderer bigRenderer = null;
      // do not show same tooltip twice
      Set<String> tooltips = null;

      for (RangeHighlighter highlighter : highlighters) {
        final Object tooltipObject = highlighter.getErrorStripeTooltip();
        if (tooltipObject == null) continue;

        final String text = tooltipObject.toString();
        if (tooltips == null) {
          tooltips = new THashSet<String>();
        }
        if (tooltips.add(text)) {
          if (bigRenderer == null) {
            bigRenderer = new LineTooltipRenderer(text, new Object[] {highlighters});
          } else {
            bigRenderer.addBelow(text);
          }
        }
      }

      return bigRenderer;
    }

    public TooltipRenderer calcTooltipRenderer(@NotNull final String text) {
      return new LineTooltipRenderer(text, new Object[] {text});
    }

    public TooltipRenderer calcTooltipRenderer(@NotNull final String text, final int width) {
      return new LineTooltipRenderer(text, width, new Object[] {text});
    }

    @Override
    public TrafficTooltipRenderer createTrafficTooltipRenderer(
        final Runnable onHide, Editor editor) {
      return new TrafficTooltipRenderer() {
        @Override
        public void repaintTooltipWindow() {}

        @Override
        public LightweightHint show(
            Editor editor, Point p, boolean alignToRight, TooltipGroup group, HintHint hintHint) {
          JLabel label = new JLabel("WTF");
          return new LightweightHint(label) {
            @Override
            public void hide() {
              super.hide();
              onHide.run();
            }
          };
        }
      };
    }
  }

  private ProperTextRange offsetToYPosition(int start, int end) {
    if (myEditorScrollbarTop == -1 || myEditorTargetHeight == -1) {
      recalcEditorDimensions();
    }
    Document document = myEditor.getDocument();
    int startLineNumber = offsetToLine(start, document);
    int startY;
    int lineCount;
    if (myEditorSourceHeight < myEditorTargetHeight) {
      lineCount = 0;
      startY = myEditorScrollbarTop + startLineNumber * myEditor.getLineHeight();
    } else {
      lineCount = myEditorSourceHeight / myEditor.getLineHeight();
      startY =
          myEditorScrollbarTop + (int) ((float) startLineNumber / lineCount * myEditorTargetHeight);
    }

    int endY;
    if (document.getLineNumber(start) == document.getLineNumber(end)) {
      endY = startY; // both offsets are on the same line, no need to recalc Y position
    } else {
      int endLineNumber = offsetToLine(end, document);
      if (myEditorSourceHeight < myEditorTargetHeight) {
        endY = myEditorScrollbarTop + endLineNumber * myEditor.getLineHeight();
      } else {
        endY =
            myEditorScrollbarTop + (int) ((float) endLineNumber / lineCount * myEditorTargetHeight);
      }
      if (endY < startY) endY = startY;
    }
    return new ProperTextRange(startY, endY);
  }

  private int yPositionToOffset(int y, boolean beginLine) {
    if (myEditorScrollbarTop == -1 || myEditorTargetHeight == -1) {
      recalcEditorDimensions();
    }
    final int safeY = Math.max(0, y - myEditorScrollbarTop);
    VisualPosition visual;
    if (myEditorSourceHeight < myEditorTargetHeight) {
      visual = myEditor.xyToVisualPosition(new Point(0, safeY));
    } else {
      float fraction = Math.max(0, Math.min(1, safeY / (float) myEditorTargetHeight));
      final int lineCount = myEditorSourceHeight / myEditor.getLineHeight();
      visual = new VisualPosition((int) (fraction * lineCount), 0);
    }
    int line = myEditor.visualToLogicalPosition(visual).line;
    Document document = myEditor.getDocument();
    if (line < 0) return 0;
    if (line >= document.getLineCount()) return document.getTextLength();

    return beginLine ? document.getLineStartOffset(line) : document.getLineEndOffset(line);
  }
}