Package net.vektah.codeglance

Source Code of net.vektah.codeglance.GlancePanel$MouseWheelListener

/*
* Copyright © 2013, Adam Scarr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package net.vektah.codeglance;

import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.colors.ColorKey;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.ui.JBColor;
import net.vektah.codeglance.config.Config;
import net.vektah.codeglance.config.ConfigChangeListener;
import net.vektah.codeglance.config.ConfigService;
import net.vektah.codeglance.render.CoordinateHelper;
import net.vektah.codeglance.render.Minimap;
import net.vektah.codeglance.render.RenderTask;
import net.vektah.codeglance.render.TaskRunner;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.Array;

/**
* This JPanel gets injected into editor windows and renders a image generated by GlanceFileRenderer
*/
public class GlancePanel extends JPanel implements VisibleAreaListener {
  private final TaskRunner runner;
  private Editor editor;
  private Minimap[] minimaps = new Minimap[2];
  private Integer activeBuffer = -1;
  private Integer nextBuffer = 0;
  private JPanel container;
  private Logger logger = Logger.getInstance(getClass().getName());
  private Project project;
  private Boolean updatePending = false;
  private boolean dirty = false;
  private CoordinateHelper coords = new CoordinateHelper();
  private ConfigService configService = ServiceManager.getService(ConfigService.class);
  private Config config;
  private int lastFoldCount = -1;
  private Color viewportColor;

  // Anonymous Listeners that should be cleaned up.
  private ComponentListener componentListener;
  private DocumentListener documentListener;
  private ConfigChangeListener configChangeListener;
  private MouseWheelListener mouseWheelListener = new MouseWheelListener();
  private MouseListener mouseListener = new MouseListener();
  private SelectionListener selectionListener;

  public GlancePanel(Project project, FileEditor fileEditor, JPanel container, TaskRunner runner) {
    this.runner = runner;
    this.editor = ((TextEditor) fileEditor).getEditor();
    this.container = container;
    this.project = project;

    container.addComponentListener(componentListener = new ComponentAdapter() {
      @Override public void componentResized(ComponentEvent componentEvent) {
        updateSize();
        GlancePanel.this.revalidate();
        GlancePanel.this.repaint();
      }
    });

    editor.getDocument().addDocumentListener(documentListener = new DocumentAdapter() {
      @Override public void documentChanged(DocumentEvent documentEvent) {
        updateImage();
      }
    });

    configService.add(configChangeListener = new ConfigChangeListener() {
      @Override public void configChanged() {
        readConfig();
        updateImage();
        updateSize();
        GlancePanel.this.revalidate();
        GlancePanel.this.repaint();
      }
    });

    addMouseWheelListener(mouseWheelListener);

    readConfig();

    editor.getScrollingModel().addVisibleAreaListener(this);

    editor.getSelectionModel().addSelectionListener(selectionListener = new SelectionListener() {
      @Override public void selectionChanged(SelectionEvent selectionEvent) {
        repaint();
      }
    });

    addMouseListener(mouseListener);
    addMouseMotionListener(mouseListener);

    updateSize();
    for(int i = 0; i < Array.getLength(minimaps); i++) {
      minimaps[i] = new Minimap(configService.getState());
    }
    updateImage();
  }

  private void readConfig() {
    config = configService.getState();

    coords.setPixelsPerLine(config.pixelsPerLine);
    viewportColor = Color.decode("#" + config.viewportColor);
  }

  /**
   * Adjusts the panels size to be a percentage of the total window
   */
  private void updateSize() {
    if (config.disabled) {
      setPreferredSize(new Dimension(0, 0));
    } else {
      Dimension size = new Dimension(config.width, 0);
      setPreferredSize(size);
    }
  }

  /**
   * Fires off a new task to the worker thread. This should only be called from the ui thread.
   */
  private void updateImage() {
    if (config.disabled) return;
    if (project.isDisposed()) return;

    PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
    if (file == null) {
      return;
    }

    synchronized (this) {
      // If we have already sent a rendering job off to get processed then first we need to wait for it to finish.
      // see updateComplete for dirty handling. The is that there will be fast updates plus one final update to
      // ensure accuracy, dropping any requests in the middle.
      if (updatePending) {
        dirty = true;
        return;
      }
      updatePending = true;
    }

    SyntaxHighlighter hl = SyntaxHighlighterFactory.getSyntaxHighlighter(file.getLanguage(), project, file.getVirtualFile());

    nextBuffer = activeBuffer == 0 ? 1 : 0;

    runner.add(new RenderTask(minimaps[nextBuffer], editor.getDocument().getText(), editor.getColorsScheme(), hl, editor.getFoldingModel().getAllFoldRegions(), new Runnable() {
      @Override public void run() {
        updateComplete();
      }
    }));
  }

  private void updateComplete() {
    synchronized (this) {
      updatePending = false;
    }

    if (dirty) {
      SwingUtilities.invokeLater(new Runnable() {
        @Override public void run() {
          updateImage();
          dirty = false;
        }
      });
    }

    activeBuffer = nextBuffer;

    repaint();
  }

  private float getHidpiScale() {
    // Work around for apple going full retard with half pixel pixels.
    // Removed temporarily as a fix for bugged scaling on OS X
    //Float scale = (Float)Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor");
    //if (scale == null) {
    //  scale = 1.0f;
    //}
    float scale = 1.0f;

    return scale;
  }

  private int getMapYFromEditorY(int y) {
    int offset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(new Point(0, y)));

    return coords.offsetToScreenSpace(offset);
  }

  @Override
  public void paint(Graphics gfx) {
    Graphics2D g = (Graphics2D) gfx;
    g.setColor(editor.getColorsScheme().getDefaultBackground());
    g.fillRect(0, 0, getWidth(), getHeight());

    logger.debug(String.format("Rendering to buffer: %d", activeBuffer));
    if (activeBuffer >= 0) {
      paintSelection(g);

      Minimap minimap = minimaps[activeBuffer];

      Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();

      double documentEndY = editor.logicalPositionToXY(editor.offsetToLogicalPosition(editor.getDocument().getTextLength() - 1)).getY();

      coords.setMinimap(minimap)
        .setPanelHeight(getHeight())
        .setPanelWidth(getWidth())
        .setPercentageComplete(visibleArea.getMinY() / (documentEndY - (visibleArea.getMaxY() - visibleArea.getMinY())))
        .setHidpiScale(getHidpiScale());

      Rectangle src = coords.getImageSource();
      Rectangle dest = coords.getImageDestination();

      // Draw the image and scale it to stretch vertically.
      g.drawImage(minimap.img,                                                    // source image
          dest.x, dest.y, dest.width, dest.height,
          src.x, src.y, src.width, src.height,
          null);

      paintVisibleWindow(g);
    }
  }

  private void paintVisibleWindow(Graphics2D g) {
    Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
    int firstVisibleLine =  getMapYFromEditorY((int) visibleArea.getMinY());
    int height = coords.linesToPixels((int) ((visibleArea.getMaxY() - visibleArea.getMinY()) / editor.getLineHeight()));

    // Draw the current viewport
    g.setColor(viewportColor);
    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.50f));
    g.drawRect(0, firstVisibleLine, getWidth(), height);
    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.20f));
    g.fillRect(0, firstVisibleLine, getWidth(), height);
  }

  private void paintSelection(Graphics2D g) {
    int selectionStartOffset = editor.getSelectionModel().getSelectionStart();
    int selectionEndOffset = editor.getSelectionModel().getSelectionEnd();
    int firstSelectedLine = coords.offsetToScreenSpace(selectionStartOffset);
    int firstSelectedCharacter = coords.offsetToCharacterInLine(selectionStartOffset);
    int lastSelectedLine = coords.offsetToScreenSpace(selectionEndOffset);
    int lastSelectedCharacter = coords.offsetToCharacterInLine(selectionEndOffset);

    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.90f));
    g.setColor(editor.getColorsScheme().getColor(ColorKey.createColorKey("SELECTION_BACKGROUND", JBColor.BLUE)));

    if (firstSelectedLine == lastSelectedLine) {
      // Single line is easy
      g.fillRect(firstSelectedCharacter, firstSelectedLine, lastSelectedCharacter - firstSelectedCharacter, config.pixelsPerLine);
    } else {
      // Draw the line leading in
      g.fillRect(firstSelectedCharacter, firstSelectedLine, getWidth() - firstSelectedCharacter, config.pixelsPerLine);

      // Then the line at the end
      g.fillRect(0, lastSelectedLine, lastSelectedCharacter, config.pixelsPerLine);

      if (firstSelectedLine + 1 != lastSelectedLine) {
        // And if there is anything in between, fill it in
        g.fillRect(0, firstSelectedLine + config.pixelsPerLine, getWidth(), lastSelectedLine - firstSelectedLine - config.pixelsPerLine);
      }
    }
  }

  @Override public void visibleAreaChanged(VisibleAreaEvent visibleAreaEvent) {
    // TODO pending http://youtrack.jetbrains.com/issue/IDEABKL-1141 - once fixed this should be a listener
    int currentFoldCount = 0;
    for (FoldRegion fold: editor.getFoldingModel().getAllFoldRegions()) {
      if (!fold.isExpanded()) {
        currentFoldCount++;
      }
    }

    if (currentFoldCount != lastFoldCount) {
      updateImage();
    }

    lastFoldCount = currentFoldCount;

    updateSize();
    repaint();
  }

  public void onClose() {
    container.removeComponentListener(componentListener);
    editor.getDocument().removeDocumentListener(documentListener);
    configService.remove(configChangeListener);
    editor.getScrollingModel().removeVisibleAreaListener(this);
    editor.getSelectionModel().removeSelectionListener(selectionListener);
    removeMouseWheelListener(mouseWheelListener);
    removeMouseListener(mouseListener);
    removeMouseMotionListener(mouseListener);

    componentListener = null;
    documentListener = null;
    configChangeListener = null;
    mouseListener = null;
  }

  private class MouseWheelListener implements java.awt.event.MouseWheelListener {
    @Override public void mouseWheelMoved(MouseWheelEvent mouseWheelEvent) {
      editor.getScrollingModel().scrollVertically(editor.getScrollingModel().getVerticalScrollOffset() + (mouseWheelEvent.getWheelRotation() * editor.getLineHeight() * 3));
    }
  }

  private class MouseListener extends MouseAdapter {
    private int scrollStart;
    private int mouseStart;
    private Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
    private Cursor resizeCursor = new Cursor(Cursor.W_RESIZE_CURSOR);
    private boolean resizing = false;
    private boolean dragging = false;
    private int resizeStart;
    private int widthStart;

    private boolean inResizeGutter(int x) {
      return x >= 0 && x < 8;
    }

    @Override public void mouseDragged(MouseEvent e) {


      if (resizing) {
        config.width = widthStart + (resizeStart - e.getXOnScreen());
        if (config.width < 1) {
          config.width = 1;
        }
        configService.dispatch().configChanged();
      }

      if (dragging) {
        // Disable animation when dragging for better experience.
        editor.getScrollingModel().disableAnimation();

        editor.getScrollingModel().scrollVertically(scrollStart + coords.pixelsToLines(e.getY() - mouseStart) * editor.getLineHeight());
        editor.getScrollingModel().enableAnimation();
      }
    }

    @Override public void mousePressed(MouseEvent e) {
      if (!dragging && inResizeGutter(e.getX())) {
        resizing = true;
      } else if (!resizing) {
        dragging = true;
      }

      if (resizing) {
        resizeStart = e.getXOnScreen();
        widthStart = config.width;
      }

      if (dragging) {
        Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
        int firstVisibleLine =  getMapYFromEditorY((int) visibleArea.getMinY());
        int height = coords.linesToPixels((int) ((visibleArea.getMaxY() - visibleArea.getMinY()) / editor.getLineHeight()));

        int panelY = e.getY() - getY();

        if (config.jumpOnMouseDown && (panelY <= firstVisibleLine || panelY >= (firstVisibleLine + height))) {
          editor.getScrollingModel().disableAnimation();
          editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(coords.screenSpaceToOffset(e.getY(), config.percentageBasedClick)), ScrollType.CENTER);
          editor.getScrollingModel().enableAnimation();
        }

        scrollStart = editor.getScrollingModel().getVerticalScrollOffset();
        mouseStart = e.getY();
      }
    }

    @Override public void mouseReleased(MouseEvent e) {
      dragging = false;
      resizing = false;
    }

    @Override public void mouseClicked(MouseEvent e) {
      if (!config.jumpOnMouseDown) {
        editor.getScrollingModel().scrollTo(editor.offsetToLogicalPosition(coords.screenSpaceToOffset(e.getY(), config.percentageBasedClick)), ScrollType.CENTER);
      }
    }

    @Override public void mouseMoved(MouseEvent e) {
      if (inResizeGutter(e.getX())) {
        setCursor(resizeCursor);
      } else {
        setCursor(defaultCursor);
      }
    }
  }
}
TOP

Related Classes of net.vektah.codeglance.GlancePanel$MouseWheelListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.
swt.graphics.Point">org.eclipse.swt.graphics.Point
TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.