Package open.dolphin.ui

Source Code of open.dolphin.ui.MyJScrollPane$FadeAnimator

package open.dolphin.ui;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import open.dolphin.project.Project;

/**
* ボーダーのない JScrollPane
* Magic Mouse 用のスクロール速度調節もする
* Lion スタイルのスクロールバーも表示する
* @author pns
*/
public class MyJScrollPane extends JScrollPane implements MouseListener, MouseMotionListener, MouseWheelListener {
    private static final long serialVersionUID = 1L;
   
    // MouseWheelEvent 書き換え間隔の情報
    private long laptime = Integer.MAX_VALUE; // 時間間隔 msec
    private long prevTime = 0;
    // unitIncrement は 1,2,3 になるマシンと,1,2 にしかならないマシンがある
    private int maxUnitIncrement = 2;
   
    // マウスホイールを操作しているかどうかを判定するしきい値
    // laptime < THRESHOLD ならばスクロール操作中と考える
    private static final long THRESHOLD = 30;
   
    // LionScrollBar 関連
    // 従来の ScrollBar にしたいときは true にする
    public boolean isClassicScrollBar = false;
    // ScrollBar を常に表示したい場合は true にする
    public boolean isPermanentScrollBar = false;
    // ScrollBar を常に表示したときの fading につかうフラグ
    // false で paint に入ると,fading なしでスクロールバーを書く
    private boolean shouldFadeScrollBar = true;
    // スクロールバーの最低の長さ 短くなりすぎるとつかみにくくなるので
    private static final int MIN_SCROLLBAR_LENGTH = 50;
    // スクロールバーの幅
    private int scrollBarWidth = 7;
    // スクロールバーの大きさ
    private Rectangle verticalBarRect = new Rectangle(0,0,0,0);
    private Rectangle horizontalBarRect = new Rectangle(0,0,0,0);
    private Rectangle verticalBarFrameRect = new Rectangle(0,0,0,0);
    private Rectangle horizontalBarFrameRect = new Rectangle(0,0,0,0);
    // スクロールバーの枠表示の有無 マウスがスクロールバー領域に入ったら枠を表示する
    private boolean shouldShowScrollBarFrame = false;
    // スクロールバー表示のアルファ値
    private float alpha = 0.5f;
    // スクロールバーを消すアニメーションで使う変数
    private boolean isFadeScrollBarRequested = false;
    // スクロールバーを fade out させるアニメーションスレッド
    private Thread fader;
    private FadeAnimator fadeAnimator = new FadeAnimator();
    // スクロールされたかどうかを検出するための変数       
    private Rectangle prevViewRect = new Rectangle(0,0,0,0);
    // マウスがドラッグ中かどうか
    private boolean mouseDragging = false;
    // drag する場合のマウスポイントと scrollBar value のオフセット
    private int mouseDragOffset = 0;
    // スクロールバー操作でのマウスキャプチャのためのダミーパネル。
    // JLayeredPane に透明な Panel を配置し,そこでスクロールバーに対するマウス動作をキャプチャする
    protected JLayeredPane parentLayer;
    private JPanel verticalBarPanel;
    private JPanel horizontalBarPanel;
    private final static String VERTICAL_BAR_PANEL_NAME = "vbp";
    private final static String HORIZONTAL_BAR_PANEL_NAME = "hbp";   
    // overshoot animator 関連
    // overshoot animation を表示したくない場合は false にする
    private boolean isOvershootAnimationEnabled = true;
    // overshoot アニメーション実行中かどうか
    private boolean isOvershootAnimating;
    private OvershootAnimator overshootAnimator = new OvershootAnimator();
    private Timer overshootTimer;
    // overshoot アニメーション開始時間
    private long overshootStart;
    // overshoot の不応時間制御用フラグ
    private boolean overshootReady = true;
    private BufferedImage overshootImg;
    // overshoot アニメーション用の画像が用意できたかどうか
    private boolean isOvershootImgPrepared = false;
    private enum Overshoot { TOP, BOTTOM, LEFT, RIGHT }
    // drop の時に,フィードバックを出すかどうか
    private boolean showDropFeedback = false;
       
    public MyJScrollPane() {
        this(null);
    }
   
    public MyJScrollPane(Component view) {
        this(view, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);
    }

    public MyJScrollPane(Component view, int v, int h) {
        super(view, v, h);
        setBorder(BorderFactory.createEmptyBorder());
        addMouseWheelListener(this);
        // JScrollPane に MouseListener をつけても viewport の component に取られるので MouseEvent は拾えない
        // ↓ 無理矢理イベントを拾う方法も試したが,結局 component がイベントを受け取ってしまうのでダメだった
        // Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
        // そこで,JLayeredPane にダミーパネルを用意してスクロールバーを覆うように配置,マウスイベントを横取りする方法にした
    }

    /*
     * Magic Mouse 用スクロール速度調節のために,デフォルトの ScrollBar をのっとる
     */
    @Override
    public JScrollBar createVerticalScrollBar() {
        return new MyScrollBar(JScrollBar.VERTICAL, this);
    }
   
    @Override
    public JScrollBar createHorizontalScrollBar() {
        return new MyScrollBar(JScrollBar.HORIZONTAL, this);
    }

    /**
     * paint を横取りして,Lion 風スクロールバーを表示する
     * paint は paintBorder→paintComponent→paintChildren を呼び出す
     * KarteScrollPane は paintChildren で描画するようにしたので
     * その後に,ここでスクロールバーを出す
     * @param g
     */
    @Override
    public void paint(Graphics g) {
        //showFrameRate();
        super.paint(g);
       
        if (isOvershootImgPrepared) g.drawImage(overshootImg, 0, 0, null);
       
        if (!isClassicScrollBar) showScrollBar((Graphics2D)g);
       
        // Drop の際に,feedback を出す
        if (showDropFeedback) {
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6f));
            // cut and try
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            MyBorder.drawSelectedBlueRoundRect(this, g2d, 0, 0, getWidth(), getHeight(),6,6);
            g2d.dispose();
        }
    }
   
    public void setShowDropFeedback(boolean b) {
        this.showDropFeedback = b;
        repaint();
    }
/*   
    // paint のアニメーションが何コマ/秒になっているかを計測する
    private long prev;
    private int count;
    private long sum;
    private void showFrameRate() {
        long now = System.currentTimeMillis();
        long past = now - prev;
        prev = now;
        if (past < 200) {
            System.out.printf("%5d", past);
            sum += past;
            if (count++ % 10 == 0) {
                System.out.printf(" %5d/sec\n", 10000/sum);
                sum = 0;
            }
        }
    }
*/   
    /**
     * スクロールバーの大きさを決定して,スクロールバーを表示する
     * スクロールバーのつかむところの大きさ verticalBarRect,horizontalBarRect
     * スクロールバー表示領域全体の大きさ verticalBarFrameRec,horizontalBarFrameRect
     * @param g
     */
    protected void showScrollBar(Graphics2D g) {
       
        // viewport から見えている component の rectangle
        Rectangle viewRect = viewport.getViewRect();
        // viewport の component 全体の大きさ
        Dimension viewSize = viewport.getViewSize();
       
        // fade アニメーションすべきかどうか
        shouldFadeScrollBar = viewRect.x != prevViewRect.x || viewRect.y != prevViewRect.y
                || isFadeScrollBarRequested || isOvershootAnimating;
       
        // 縦スクロールバーを表示する必要がある条件      
        if (viewSize.height > viewRect.height && (viewRect.y != prevViewRect.y || isFadeScrollBarRequested || isOvershootAnimating
                || (isPermanentScrollBar && verticalScrollBar.isVisible()))) {
           
            // scrollBarRect の決定
            int max = viewSize.height;
            int y = (viewRect.y <= 0)? 0 : viewRect.y * viewRect.height / max ; // スクロールバーの開始位置
            int len = viewRect.height * viewRect.height / max; // スクロールバーの長さ
           
            // scrollBar が短すぎないようにする
            if (len < MIN_SCROLLBAR_LENGTH) {
                int newLen = (viewRect.height/2 < MIN_SCROLLBAR_LENGTH)?
                        newLen = viewRect.height/2 : MIN_SCROLLBAR_LENGTH;
                y = y * (viewRect.height - newLen) / (viewRect.height - len);
                len = newLen;
            }
            // 上下のマージン1ドットずつ入れる
            int margin = (columnHeader == null)? 1 : columnHeader.getHeight() + 2; //header があるとき調節
            verticalBarRect.setBounds(viewRect.width - scrollBarWidth - 1, y+margin, scrollBarWidth, len-2);
            verticalBarFrameRect.setBounds(verticalBarRect.x, margin, scrollBarWidth, viewRect.height-2);
           
            // マウスキャプチャのためのダミーパネル挿入
            if (verticalBarPanel == null) {
                verticalBarPanel = createScrollBarPanel();               
                verticalBarPanel.setName(VERTICAL_BAR_PANEL_NAME);
                //verticalBarPanel.setBorder(BorderFactory.createLineBorder(Color.red));
                getParentLayer().add(verticalBarPanel, JLayeredPane.DRAG_LAYER);
            }
           
        } else {
            verticalBarRect.setBounds(0,0,0,0);
            verticalBarFrameRect.setBounds(0,0,0,0);
        }
       
        // 横スクロールバーを表示する必要がある条件
        if (viewSize.width > viewRect.width && (viewRect.x != prevViewRect.x || isFadeScrollBarRequested || isOvershootAnimating
                || (isPermanentScrollBar && horizontalScrollBar.isVisible()))) {
           
            // scrollBarRect の決定
            int max = viewSize.width;
            int x = (viewRect.x <= 0)? 0 : viewRect.x * viewRect.width / max ; // スクロールバーの開始位置
            int margin = (rowHeader == null)? 1 : rowHeader.getWidth() + 2;
            int len = viewRect.width * viewRect.width / max; // スクロールバーの長さ
           
            // scrollBar が短すぎないようにする
            if (len < MIN_SCROLLBAR_LENGTH) {
                int newLen = (viewRect.width/2 < MIN_SCROLLBAR_LENGTH)?
                        newLen = viewRect.width/2 : MIN_SCROLLBAR_LENGTH;
                x = x * (viewRect.width - newLen) / (viewRect.width - len);
                len = newLen;
            }
            // 左右のマージン1ドットずつ入れる
            horizontalBarRect.setBounds(x+margin, viewRect.height - scrollBarWidth - 1, len-2, scrollBarWidth);
            horizontalBarFrameRect.setBounds(margin, horizontalBarRect.y, viewRect.width-2, scrollBarWidth);
           
            // マウスキャプチャのためのダミーパネル挿入
            if (horizontalBarPanel == null) {
                horizontalBarPanel = createScrollBarPanel();               
                horizontalBarPanel.setName(HORIZONTAL_BAR_PANEL_NAME);
                //horizontalBarPanel.setBorder(BorderFactory.createLineBorder(Color.blue));
                getParentLayer().add(horizontalBarPanel, JLayeredPane.DRAG_LAYER);
            }
           
        } else {
            horizontalBarRect.setBounds(0,0,0,0);
            horizontalBarFrameRect.setBounds(0,0,0,0);
        }
       
        // 縦横両方のスクロールバーが表示される場合は,交点の分だけ調節が必要
        if (verticalBarRect.height !=0 && horizontalBarRect.width !=0) {
            verticalBarRect.height -= scrollBarWidth;
            verticalBarFrameRect.height -= scrollBarWidth;
            horizontalBarRect.width -= scrollBarWidth;
            horizontalBarFrameRect.width -= scrollBarWidth;
        }
       
        // 描画メソッド呼び出し
        if (verticalBarRect.height != 0) {
            drawScrollBar(g, verticalBarRect, verticalBarFrameRect);
            // ダミーパネル表示
            Rectangle r = SwingUtilities.convertRectangle(this, verticalBarFrameRect, parentLayer);
            r.x -=7; r.width += 10; //操作しやすいように少し広げる
            verticalBarPanel.setBounds(r);
            verticalBarPanel.setVisible(true);
        }
        if (horizontalBarRect.width != 0) {
            drawScrollBar(g, horizontalBarRect, horizontalBarFrameRect);
            // ダミーパネル表示
            Rectangle r = SwingUtilities.convertRectangle(this, horizontalBarFrameRect, parentLayer);
            r.y -=7; r.height += 10; //操作しやすいように少し広げる
            horizontalBarPanel.setBounds(r);
            horizontalBarPanel.setVisible(true);
        }
       
        // スクロールしたかどうか判定するために今の位置を取っておく
        prevViewRect = viewRect;
    }
   
    /**
     * 親フレームの JLayeredPane を返す
     * @return
     */
    public JLayeredPane getParentLayer() {
        if (parentLayer == null) {
            parentLayer = ((JFrame)SwingUtilities.getWindowAncestor(this)).getLayeredPane();               
        }
        return parentLayer;
    }
   
    /**
     * スクロールバーを実際に描画する
     */
    private void drawScrollBar(Graphics2D g, Rectangle r, Rectangle fr) {       
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
       
        if (isPermanentScrollBar && !shouldFadeScrollBar) {           
            // ScrollBar を常に表示する場合,fade アニメーションが必要ない場合は単純にスクロールバーを描画する
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f));
            g.setColor(Color.GRAY);
            g.fillRoundRect(fr.x, fr.y, fr.width, fr.height, scrollBarWidth, scrollBarWidth);       
            g.setColor(Color.BLACK);       
            g.fillRoundRect(r.x, r.y, r.width, r.height, scrollBarWidth, scrollBarWidth);
                       
        } else {
            // fade アニメーションする場合こちらにくる
            // ScrollBar を常に表示する場合でも,スクロールの際は濃く表示して,fade アニメーションする
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));           
            // 枠は,マウスがスクロールバー領域に乗ったときに表示。ScrollBar を常に表示する場合は常に表示する
            if (shouldShowScrollBarFrame || isPermanentScrollBar) {
                g.setColor(Color.GRAY);
                g.fillRoundRect(fr.x, fr.y, fr.width, fr.height, scrollBarWidth, scrollBarWidth);       
            }
            g.setColor(Color.BLACK);       
            g.fillRoundRect(r.x, r.y, r.width, r.height, scrollBarWidth, scrollBarWidth);       
           
            fadeScrollBar();
        }
    }
   
    /**
     * LionScrollBar を fade させるアニメーションスレッドをスタートする
     */
    private void fadeScrollBar() {
        if (fader == null || !fader.isAlive()) {
            fader = new Thread(fadeAnimator);
            fader.start();
            isFadeScrollBarRequested = true;
        }
    }
    
    /**
     * fade アニメーション実行スレッド
     */
    private class FadeAnimator implements Runnable {
        @Override
        public void run() {
            try {
                while(true) {
                    long now = System.currentTimeMillis();
                    //System.out.print(".");
                    // delay msec 経過後に fading animation 実行
                    // prevTime には最後のマウスホイール操作,最後のドラッグ操作,最後のクリック操作が記録される
                    // スクロール操作中は常に prevTime が最新時間に更新されるので fade アニメーションは開始待ちになる
                    int delay = 1000;
                    // ScrollBar フレームを表示している間(マウスがスクロールバー領域に乗っているとき)は fade しない
                    // スクロールバーをマウスでつかんで操作している間は fade しない
                    if (!shouldShowScrollBarFrame && !mouseDragging && now - prevTime > delay) {                   
                        //System.out.println("fading animation thread starts");
                        for(int i=9; i>=0; i--) {
                            if (isPermanentScrollBar ) alpha = (float)i * 0.25f / 9f + 0.2f;
                            else alpha = (float)i * 0.05f;
                            repaint();
                            Thread.sleep(30); // 30コマ/秒のアニメーション
                        }
                        prevTime = now;
                        isFadeScrollBarRequested = false;
                        alpha = 0.5f;

                        // ダミーパネルを消去
                        if (verticalBarPanel != null) {
                            verticalBarPanel.setVisible(false);
                            verticalBarPanel.repaint();
                        }
                        if (horizontalBarPanel != null) {
                            horizontalBarPanel.setVisible(false);
                            horizontalBarPanel.repaint();
                        }

                        break;

                    } else {
                        // 少し待ってから,もう1回トライする
                        Thread.sleep(250);                                   
                    }
                }           
                //System.out.println("fading animation thread ends");
            } catch (InterruptedException ex) {
                System.out.println("MyJScrollPane: " + ex);
            }
        }
    }
   
    /**
     * overshoot のアニメーションをスタート
     * @param direction
     * @param amplitude
     */
    private void showOvershoot(Overshoot direction, int amplitude) {
        if (overshootTimer == null) {
            overshootTimer = new Timer(10, overshootAnimator);
        }
        if (!isOvershootAnimating) {
            isOvershootAnimating = true;
            overshootStart = System.currentTimeMillis();
            overshootAnimator.setDirection(direction);
            overshootAnimator.setAmplitude(amplitude);
            overshootTimer.start();
        }
    }
   
    /**
     * overshoot アニメーション実行スレッド
     */
    private class OvershootAnimator implements ActionListener {
        private Overshoot direction;
        private int amplitude;
        private float duration = 200f;

        public void setDirection(Overshoot direction) {
            this.direction = direction;
        }
        public void setAmplitude(int amplitude) {
            this.amplitude = amplitude;
        }
       
        @Override
        public void actionPerformed(ActionEvent e) {
            double percent = Math.min((System.currentTimeMillis() - overshootStart) / duration, 1.0f);
            double theta = Math.PI*percent;
            double d = amplitude * Math.sin(theta);
           
            Dimension viewSize = viewport.getExtentSize();
            int marginX = (rowHeader == null)? 0 : rowHeader.getWidth();
            int marginY = (columnHeader == null)? 0 : columnHeader.getHeight(); //header があるとき調節
            viewSize.width += marginX;
            viewSize.height += marginY;
           
            overshootImg = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_3BYTE_BGR);
            BufferedImage tmpImg = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_3BYTE_BGR);
            Graphics2D g = (Graphics2D) overshootImg.getGraphics();
            paintChildren(tmpImg.getGraphics());

            switch (direction) {
                case TOP:
                    g.drawImage(tmpImg.getSubimage(0, 0, tmpImg.getWidth(), tmpImg.getHeight() - (int)d),
                            0, (int)d, null);

                    g.setColor(Color.LIGHT_GRAY);
                    g.fillRect(0, marginY, tmpImg.getWidth(), (int)d);

                    if (columnHeader != null) {
                        g.drawImage(tmpImg.getSubimage(0, 0, tmpImg.getWidth(), marginY),
                                0, 0, null);
                    }
                    break;
                   
                case BOTTOM:
                    g.drawImage(tmpImg.getSubimage(0, (int)d, tmpImg.getWidth(), tmpImg.getHeight() - (int)d),
                            0, 0, null);

                    g.setColor(Color.LIGHT_GRAY);
                    g.fillRect(0, viewSize.height - (int)d, tmpImg.getWidth(), (int)d);

                    if (columnHeader != null) {
                        g.drawImage(tmpImg.getSubimage(0, 0, tmpImg.getWidth(), marginY),
                                0, 0, null);
                    }
                    break;
                   
                case LEFT:
                    g.drawImage(tmpImg.getSubimage(0, 0, tmpImg.getWidth() - (int)d, tmpImg.getHeight()),
                            (int)d, 0, null);

                    g.setColor(Color.LIGHT_GRAY);
                    g.fillRect(marginX, 0, (int)d, tmpImg.getHeight());

                    if (rowHeader != null) {
                        g.drawImage(tmpImg.getSubimage(0, 0, marginX, tmpImg.getHeight()),
                                0, 0, null);
                    }
                    break;
                   
                case RIGHT:
                    g.drawImage(tmpImg.getSubimage((int)d, 0, tmpImg.getWidth() - (int)d, tmpImg.getHeight()),
                            0, 0, null);

                    g.setColor(Color.LIGHT_GRAY);
                    g.fillRect(viewSize.width - (int)d, 0, (int)d, tmpImg.getHeight());

                    if (rowHeader != null) {
                        g.drawImage(tmpImg.getSubimage(0, 0, marginX, tmpImg.getHeight()),
                                0, 0, null);
                    }
                    break;                   
            }
           
            isOvershootImgPrepared = true;
            repaint();
           
            if (percent >= 1.0f) {
                overshootTimer.stop();
                isOvershootImgPrepared = false;
                isOvershootAnimating = false;
                overshootImg.flush();
                tmpImg.flush();
                g.dispose();
            }
        }
    }

    /**
     * スクロールバー操作のマウスイベントを取得するためのダミーパネルを作成する
     * @return
     */
    private JPanel createScrollBarPanel() {
        JPanel p = new JPanel();
        p.setOpaque(false);
        p.addMouseListener(this);
        p.addMouseMotionListener(this);
        p.addMouseWheelListener(this);
        return p;
    }
   
    // MouseListener の処理:スクロールバーダミーパネルから呼ばれる
    // クリックした場合はその位置までジャンプ
    @Override
    public void mouseClicked(MouseEvent e) {
        //System.out.println("mouse clicked");
        if (VERTICAL_BAR_PANEL_NAME.equals(getSourcePanelName(e))) {
            verticalScrollBar.setValue(e.getY() * (viewport.getView().getHeight() / verticalBarFrameRect.height));
        } else {
            horizontalScrollBar.setValue(e.getX() * (viewport.getView().getWidth() / horizontalBarFrameRect.width));           
        }
        prevTime = System.currentTimeMillis();
    }
   
    @Override
    public void mousePressed(MouseEvent e) {
        //System.out.println("mouse pressed");
    }
   
    @Override
    public void mouseReleased(MouseEvent e) {
        //System.out.println("mouse released");
        if (mouseDragging) {
            mouseDragging = false;
            shouldShowScrollBarFrame = false;
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
        //System.out.println("mouse exited");
        if (!mouseDragging) shouldShowScrollBarFrame = false;
    }

    // ドラッグした場合はマウスの動きに応じてスクロールする
    @Override
    public void mouseDragged(MouseEvent e) {
        String panelName = getSourcePanelName(e);
        boolean isVertical = VERTICAL_BAR_PANEL_NAME.equals(panelName);
        Point p = e.getPoint();
       
        if (!mouseDragging) {
            if (isVertical) mouseDragOffset = p.y - verticalBarRect.y;
            else mouseDragOffset = p.x - horizontalBarRect.x;           
        }
               
        if (isVertical) {
            mouseDragging = true;           
            int max = verticalBarFrameRect.height - verticalBarRect.height;
            int value = p.y - mouseDragOffset;
            if (value < 0) {
                value = 0;
                mouseDragOffset = p.y;

            } else if (value > max) {
                value = max;
                mouseDragOffset = p.y - max;
            }
            verticalScrollBar.setValue(value * (viewport.getView().getHeight()-verticalBarFrameRect.height) / max);
           
        } else {
            mouseDragging = true;           
            int max = horizontalBarFrameRect.width - horizontalBarRect.width;
            int value = p.x - mouseDragOffset;
            if (value < 0) {
                value = 0;
                mouseDragOffset = p.x;

            } else if (value > max) {
                value = max;
                mouseDragOffset = p.x - max;
            }
            horizontalScrollBar.setValue(value * (viewport.getView().getWidth()-horizontalBarFrameRect.width) / max);           
        }
       
        // mouseWheel と drug を同時に操作はしないとの仮定
        prevTime = System.currentTimeMillis();
    }

    // スクロールバー領域でマウスを動かしたらフレームを表示する
    @Override
    public void mouseMoved(MouseEvent e) {
        //System.out.printf("mouse moved (%d,%d)" , e.getX(), e.getY());
        setShouldShowScrollBarFrame();
    }   

    /**
     * この listener は MyJScrollPane とスクロールダミーパネルの両方からイベントを受け取る
     * MyJScrollBar では,WheelEvent の頻度を計測
     * スクロールダミーパネルでは,スクロールフレームの表示をしてから MyJScroppPane にイベントを投げる
     * @param e
     */
    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
       
        // mouseWheelEvent の頻度を計測
        long now = System.currentTimeMillis();
        laptime = now - prevTime;
        prevTime = now;
       
        // ダミーパネルからの mouse event かどうかを判定,MyJScrollPane に伝達し,フレーム表示をセット
        String panelName = getSourcePanelName(e);
        if (VERTICAL_BAR_PANEL_NAME.equals(panelName) || HORIZONTAL_BAR_PANEL_NAME.equals(panelName)) {
            setShouldShowScrollBarFrame();
            dispatchEvent(SwingUtilities.convertMouseEvent((JPanel)e.getSource(), e, this));
        }
       
        // overshoot animation - Touchpad の情報を使ってみるテスト
        if (isOvershootAnimationEnabled) {           
            // スクロール方向の検出
            int orientation = (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == 0? ScrollBar.VERTICAL : ScrollBar.HORIZONTAL;

            // overshoot 検出
            Rectangle r = viewport.getViewRect();
            Point p = viewport.getViewPosition();

            Dimension compSize = viewport.getView().getSize();
            int horizontalLimit = compSize.width - r.width;
            int verticalLimit = compSize.height - r.height;
            int unitIncrement = verticalScrollBar.getUnitIncrement(orientation);

            // Touchpad がはなされていたら不応期にする。触ってたら不応期を解除する
            // overshoot 不応期の解除
            if (overshootReady == false && TouchpadTest.isPressed()) {
                overshootReady = true;
                if (orientation == ScrollBar.VERTICAL)
                    maxUnitIncrement = Math.abs((int) (TouchpadTest.getYVelocity() * 5));
                else if (orientation == ScrollBar.HORIZONTAL)
                    maxUnitIncrement = Math.abs((int) (TouchpadTest.getXVelocity() * 5));
            }

            p.x += e.getUnitsToScroll() * unitIncrement;
            p.y += e.getUnitsToScroll() * unitIncrement;
           
            int amplitude = maxUnitIncrement;

            if (orientation == ScrollBar.VERTICAL && viewport.getView().getHeight() > viewport.getExtentSize().getHeight()) {
                if (p.y < 0 && !TouchpadTest.isPressed() && overshootReady) {
                    showOvershoot(Overshoot.TOP, amplitude);
                    overshootReady = false;
                    //System.out.println("overshoot on top");
                } else if (p.y > verticalLimit && !TouchpadTest.isPressed() && overshootReady) {
                    showOvershoot(Overshoot.BOTTOM, amplitude);
                    overshootReady = false;
                    //System.out.println("overshoot on bottom");
                }           

            } else if (orientation == ScrollBar.HORIZONTAL && viewport.getView().getWidth() > viewport.getExtentSize().getWidth()) {
                if (p.x < 0 && !TouchpadTest.isPressed() && overshootReady) {
                    showOvershoot(Overshoot.LEFT, amplitude);
                    overshootReady = false;
                    //System.out.println("overshoot to left");
                } else if (p.x > horizontalLimit && !TouchpadTest.isPressed() && overshootReady) {
                    showOvershoot(Overshoot.RIGHT, amplitude);
                    overshootReady = false;
                    //System.out.println("overshoot to right");
                }
            }
        }
    }
   
    /**
     * スクロールバーのフレームを書く
     * @param x
     * @param y
     */
    private void setShouldShowScrollBarFrame() {
        if (shouldShowScrollBarFrame == false) {
            shouldShowScrollBarFrame = true;
            repaint();
        }
    }
   
    /**
     * MouseEvent がスクロールダミーパネルのどちらから来たかを判定
     * @param e
     * @return
     */
    private String getSourcePanelName(MouseEvent e) {
        Object src = e.getSource();
        if (src instanceof JPanel) return ((JPanel)src).getName();           
        else return null;
    }
   
    /**
     * 慣性スクロールの止まり際に,スピードを落として滑らかに見せるスクロールバー
     * isClassicScrollBar = true  : 従来のスクロールバー表示
     * isClassicScrollBar = false : スクロールバー表示しない
     */
    private class MyScrollBar extends ScrollBar {
        private static final long serialVersionUID = 1L;
       
        MyJScrollPane context;
       
        private int scrollUnit;
        private int defaultScrollUnit;
        private Dimension preferredSize = new Dimension(0,0);
   
        // この scroll pane を使っている component
        private Component child = null;
       
        public MyScrollBar(int direction, MyJScrollPane context) {
            super(direction);
            this.context = context;
            this.putClientProperty("JScrollBar.fastWheelScrolling", null);
        }
       
        // Dimension(0,0) を返すと,isVisible=true のまま見えなくなる
        @Override
        public Dimension getPreferredSize() {
            if (isClassicScrollBar) {
                Object sizeVariant = ((JComponent)getParent()).getClientProperty("JComponent.sizeVariant");
                if (sizeVariant != null && sizeVariant.equals("small")) preferredSize.setSize(7,7);
                else preferredSize.setSize(10,10);
            } else {
                setSize(0,0);
            }
            return preferredSize;
        }
       
        @Override
        public int getUnitIncrement(int direction) {
           
            if (child == null) {
                child = viewport.getView();
                // カルテ
                if (child instanceof JPanel || child instanceof JTextPane) {
                    defaultScrollUnit = Project.getPreferences().getInt(Project.SCROLL_UNIT_KARTE, 15);
                   
                // スタンプ
                } else if (child instanceof JTree) {
                    defaultScrollUnit = Project.getPreferences().getInt(Project.SCROLL_UNIT_STAMP, 15);

                // テーブル
                } else if (child instanceof JTable || child instanceof JList) {
                    defaultScrollUnit = Project.getPreferences().getInt(Project.SCROLL_UNIT_TABLE, 15);

                // unknown source
                } else {
                    defaultScrollUnit = super.getUnitIncrement(direction);
                }
            }

            // 慣性スクロールが止まるときの動きを滑らかにするため scrollUnit を動的に調整する
            // laptime が THRESHOLD 以上かかっていたら(=スクロールが遅くなっていたら)ブレーキをかける
            if (laptime > THRESHOLD) {
                // scrollUnit が大きいと,減速が間に合わないので,まずは 8 まで一気に減速
                if (scrollUnit > 9) scrollUnit = 8;
                // その後,1ドットずつ減速していくと滑らかに止まったように見える
                scrollUnit = (scrollUnit <= 1)? 1 : --scrollUnit;
            } else {
                scrollUnit = defaultScrollUnit;
            }
           
            return scrollUnit;
        }
       
        @Override
        public void paint(Graphics g) {
            if (isClassicScrollBar) super.paint(g);
        }
       
        /**
         * scrollable なものは,ここに value をセットするだけで viewport が書き換わるので,JScrollPane の paint は呼ばれない。
         * それだと LionScrollBar が書けないので,無理矢理 repaint して書かせる
         * @param value
         */
        @Override
        public void setValue(int value) {
            super.setValue(value);
            //if (!isClassicScrollBar && context.viewport.getView() instanceof Scrollable) context.repaint();
            if (!isClassicScrollBar) context.repaint();
        }
    }
}
TOP

Related Classes of open.dolphin.ui.MyJScrollPane$FadeAnimator

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.