OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 149 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.ui.touch;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.util.ArrayList;
import java.util.List;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class ScrollableList extends JComponent implements MouseListener, MouseMotionListener, MouseWheelListener, ListDataListener {
    /**
     * 
     */
    private static final long serialVersionUID = 8176007217030710406L;

    private ListModel model;

    int offsetY = 0;// >0 si list remontée

    private int cellHeight;

    private int mousePressedY;
    private int selectedIndex = -1;

    private ScrollAnimator a;

    private int lastMouseY;

    private boolean hasScrolled;

    private List<ListSelectionListener> selectionListeners = new ArrayList<>();
    private List<ActionListener> actionListeners = new ArrayList<>();

    public ScrollableList(ListModel model) {
        a = new ScrollAnimator(this);
        this.model = model;
        cellHeight = 60;
        this.addMouseListener(this);
        this.addMouseMotionListener(this);
        this.addMouseWheelListener(this);
        model.addListDataListener(this);

    }

    @Override
    public void paint(Graphics g) {
        if (isOpaque()) {
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, this.getWidth(), this.getHeight());
        }
        int minIndex = getMinVisibleIndex();

        int maxIndex = getMaxVisibleIndex();
        int drawY = getYFromIndex(minIndex);
        for (int i = minIndex; i <= maxIndex; i++) {
            paintCell(g, model.getElementAt(i), i, (i == selectedIndex), drawY);
            drawY += cellHeight;
        }

    }

    public int getMinVisibleIndex() {
        int minIndex = getIndexFromY(0);
        if (minIndex < 0) {
            minIndex = 0;
        }
        return minIndex;
    }

    public int getMinFullyVisibleIndex() {
        int minIndex = getIndexFromY(0);
        if (minIndex < 0) {
            minIndex = 0;
        }

        if ((minIndex) * cellHeight - offsetY < 0) {
            minIndex++;
        }
        return minIndex;
    }

    public int getMaxVisibleIndex() {
        int maxIndex = getIndexFromY(this.getHeight());
        if (maxIndex >= model.getSize()) {
            maxIndex = model.getSize() - 1;
        }
        return maxIndex;
    }

    public int getMaxFullyVisibleIndex() {
        int maxIndex = getIndexFromY(this.getHeight());
        if (maxIndex >= model.getSize()) {
            maxIndex = model.getSize() - 1;
        }
        if (this.getHeight() % cellHeight > 0) {
            maxIndex--;
        }
        return maxIndex;
    }

    public void paintCell(Graphics g, Object object, int index, boolean selected, int posY) {

        g.setColor(Color.WHITE);

        g.fillRect(0, posY, this.getWidth(), this.cellHeight);
        g.setColor(Color.GRAY);

        g.drawLine(0, posY + this.cellHeight - 1, this.getWidth(), posY + this.cellHeight - 1);

        if (selected) {
            g.setColor(Color.BLACK);
        } else {
            g.setColor(Color.GRAY);
        }
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);

        g.setFont(new Font("Arial", Font.BOLD, 38));
        g.drawString(object.toString(), 14, posY + 43);
    }

    public int getIndexFromY(int y) {
        return (offsetY + y) / cellHeight;
    }

    public int getYFromIndex(int index) {
        return (index * cellHeight) - offsetY;
    }

    public void setFixedCellHeight(int h) {
        this.cellHeight = h;
    }

    public int getCellHeight() {
        return cellHeight;
    }

    public void setOffsetY(int y) {
        if (offsetY != y) {
            this.offsetY = y;
            repaint();
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
        this.mousePressedY = e.getY();
        this.lastMouseY = mousePressedY;
        hasScrolled = false;
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        long targetOffset = cellHeight * Math.round((((double) this.offsetY)) / this.cellHeight);

        if (!hasScrolled) {
            setSelectedIndex(getIndexFromY(e.getY()));
        } else {
            scrollToOffset(targetOffset);
        }
        fireActionPerformed();
    }

    public void setSelectedIndex(int index) {
        setSelectedIndex(index, true);
    }

    public void setSelectedIndex(int index, boolean scroll) {
        if (index != selectedIndex) {
            this.selectedIndex = index;
            if (scroll) {
                scrollToSelectedIndex();
            }
            fireSelectionChanged();
        }
        repaint();
    }

    public Object getSelectedValue() {
        if (selectedIndex < 0 || selectedIndex > this.model.getSize() - 1) {
            return null;
        }
        return model.getElementAt(selectedIndex);
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        // Pas besoin de scroller, il y a plus de place que de lignes
        if (model.getSize() <= (int) this.getHeight() / cellHeight) {
            return;
        }
        int dy = e.getY() - this.lastMouseY;
        this.setOffsetY(this.offsetY - dy);
        this.lastMouseY = e.getY();
        if (Math.abs(lastMouseY - this.mousePressedY) > 10) {
            hasScrolled = true;
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
        long targetOffset = cellHeight * Math.round((((double) this.offsetY)) / this.cellHeight);
        targetOffset += e.getWheelRotation() * cellHeight * 2;

        scrollToOffset(targetOffset);

    }

    public void scrollToOffset(long targetOffset) {

        int max = (model.getSize()) * cellHeight - this.getHeight();
        if (targetOffset > max) {
            targetOffset = max;
        }
        if (targetOffset < 0) {
            targetOffset = 0;
        }

        a.stop();
        a.setStart(this.offsetY);
        a.setStop(targetOffset);
        AnimatorManager.getInstance().start(a);
    }

    public void scrollToSelectedIndex() {
        if (selectedIndex < 0 || selectedIndex > this.model.getSize() - 1) {
            return;
        }
        if (this.selectedIndex < getMinFullyVisibleIndex()) {
            scrollToOffset(this.selectedIndex * cellHeight);
        } else if (this.selectedIndex > getMaxFullyVisibleIndex()) {
            int targetOffset = (this.selectedIndex + 1) * cellHeight - this.getHeight();

            scrollToOffset(targetOffset);
        } else {
            if (offsetY < 0) {
                scrollToOffset(0);
            }

        }
    }

    public void setCellHeight(int cellHeight) {
        this.cellHeight = cellHeight;
        repaint();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                DefaultListModel model1 = new DefaultListModel();
                for (int i = 0; i < 20; i++) {
                    model1.addElement("Item" + i);
                }

                DefaultListModel model2 = new DefaultListModel();
                for (int i = 0; i < 3; i++) {
                    model2.addElement("Item" + i);
                }

                final ScrollableList l1 = new ScrollableList(model1);
                final ScrollableList l2 = new ScrollableList(model2);

                JFrame f = new JFrame();
                JPanel p = new JPanel();
                p.setLayout(new GridLayout(1, 3));
                p.add(l1);

                JButton comp = new JButton("ScrollToSelected");
                p.add(comp);
                p.add(l2);
                f.setContentPane(p);
                f.setSize(600, 400);
                f.setLocation(10, 80);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.setVisible(true);
                comp.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        l1.scrollToSelectedIndex();
                        l2.scrollToSelectedIndex();
                    }
                });
            }
        });

    }

    public void clearSelection() {
        if (this.selectedIndex != -1) {
            this.selectedIndex = -1;
            fireSelectionChanged();
            repaint();
        }
    }

    /**
     * Select a value
     * 
     * @return true if the object is not null and was found in the model
     */
    public boolean setSelectedValue(Object obj, boolean scroll) {
        if (obj == null) {
            clearSelection();
            return true;
        }

        int size = model.getSize();
        for (int i = 0; i < size; i++) {
            Object o = model.getElementAt(i);
            if (o.equals(obj)) {
                this.setSelectedIndex(i);
                return true;
            }
        }
        return false;
    }

    public void addListSelectionListener(ListSelectionListener selectionListener) {
        this.selectionListeners.add(selectionListener);
    }

    void fireSelectionChanged() {
        int size = this.selectionListeners.size();
        for (int i = 0; i < size; i++) {
            this.selectionListeners.get(i).valueChanged(new ListSelectionEvent(this, this.selectedIndex, this.selectedIndex, false));
        }
    }

    public void addActionListener(ActionListener listener) {
        this.actionListeners.add(listener);
    }

    private void fireActionPerformed() {
        int size = this.actionListeners.size();
        for (int i = 0; i < size; i++) {
            this.actionListeners.get(i).actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "click"));
        }

    }

    @Override
    public void contentsChanged(ListDataEvent e) {
        this.offsetY = 0;
        repaint();
    }

    @Override
    public void intervalAdded(ListDataEvent e) {
        contentsChanged(e);
    }

    @Override
    public void intervalRemoved(ListDataEvent e) {
        contentsChanged(e);
    }

}