OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 137 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

package org.jopencalendar.ui;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;

import org.jopencalendar.model.JCalendarItem;
import org.jopencalendar.model.JCalendarItemPart;

public abstract class MultipleDayView extends JPanel implements Scrollable {
    public static final String CALENDARD_ITEMS_PROPERTY = "calendard_items_changed";
    public final static Color WHITE_TRANSPARENCY_COLOR = new Color(255, 255, 255, 180);
    public static final Color LIGHT_BLUE = new Color(202, 212, 220);
    public final static Color LIGHT = new Color(222, 222, 222);
    public static final int TITLE_HEIGHT = 20;
    public static final int HOURS_LABEL_WIDTH = 50;

    protected static final int YEAR_HEIGHT = 20;
    private int deltaY = 10;
    private int rowHeight = 44;

    private List<List<AllDayItem>> allDayItems = new ArrayList<List<AllDayItem>>();
    private List<List<ItemPartView>> itemParts = new ArrayList<List<ItemPartView>>();

    private int allDayItemsRowCount;
    public int allDayItemsRowHeight = 20;

    private Set<ItemPartView> selectedItems = new HashSet<ItemPartView>();
    // Right click menu
    private JPopupMenuProvider popupProvider;

    protected List<ItemPartHoverListener> hListeners = new ArrayList<ItemPartHoverListener>();
    private int startHour = 0;
    private int endHour = 24;

    public MultipleDayView() {

        this.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                ItemPartView newSelection = getItemPartAt(e.getX(), e.getY());
                if (newSelection != null) {
                    boolean isCurrentlySelected = newSelection.isSelected();
                    // Click on a non selected item
                    boolean ctrlPressed = ((e.getModifiers() & InputEvent.CTRL_MASK) == InputEvent.CTRL_MASK);
                    if (!ctrlPressed) {
                        deselectAll();
                    }
                    if (e.getButton() == MouseEvent.BUTTON1) {
                        if (isCurrentlySelected) {
                            // Click on a selected item
                            newSelection.setSelected(false);
                            selectedItems.remove(newSelection);
                        } else {
                            newSelection.setSelected(true);
                            selectedItems.add(newSelection);
                        }
                    } else {
                        newSelection.setSelected(true);
                        selectedItems.add(newSelection);
                    }
                    repaint();
                }
                // Deselect all if nothing is selected
                if (newSelection == null && !selectedItems.isEmpty()) {
                    deselectAll();
                    repaint();
                }
                // For mac
                showPopup(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                showPopup(e);
            }

            private void showPopup(MouseEvent e) {
                if (e.isPopupTrigger()) {
                    ItemPartView newSelection = getItemPartAt(e.getX(), e.getY());
                    if (newSelection != null && !newSelection.isSelected()) {
                        newSelection.setSelected(true);
                        selectedItems.add(newSelection);
                        repaint();
                    }

                    if (popupProvider != null) {
                        // Selected parts
                        final List<JCalendarItemPart> sParts = new ArrayList<JCalendarItemPart>();
                        for (ItemPartView p : selectedItems) {
                            sParts.add(p.getPart());
                        }
                        // Current column parts
                        final List<JCalendarItemPart> currentColumnParts = new ArrayList<JCalendarItemPart>();
                        int columnIndex = getColumnIndex(e.getX());
                        List<ItemPartView> l = MultipleDayView.this.itemParts.get(columnIndex);
                        for (ItemPartView p : l) {
                            currentColumnParts.add(p.getPart());
                        }

                        JPopupMenu popup = popupProvider.getPopup(sParts, currentColumnParts);
                        if (popup != null) {
                            popup.show(MultipleDayView.this, e.getX(), e.getY());
                        }
                    }
                }
            }
        });
        this.addMouseMotionListener(new MouseAdapter() {
            ItemPartView previous;

            @Override
            public void mouseMoved(MouseEvent e) {
                if (hListeners.isEmpty()) {
                    return;
                }
                ItemPartView newSelection = getItemPartAt(e.getX(), e.getY());

                if (newSelection != previous) {

                    for (ItemPartHoverListener l : hListeners) {
                        if (newSelection == null) {
                            l.mouseOn(null);
                        } else {
                            l.mouseOn(newSelection.getPart());
                        }
                    }
                    previous = newSelection;
                }

            }

        });

    }

    protected int getColumnIndex(int currentX) {
        int x = HOURS_LABEL_WIDTH;
        int columnCount = getColumnCount();
        for (int i = 0; i < columnCount - 1; i++) {
            x += getColumnWidth(i);
            if (currentX < x) {
                return i;
            }
        }
        return 0;
    }

    public void setHourRange(int start, int end) {
        if (start < 0 || start > 24) {
            throw new IllegalArgumentException("Bad start hour : " + start);
        }
        if (end < 0 || end > 24) {
            throw new IllegalArgumentException("Bad end hour : " + end);
        }
        if (end - start < 1) {
            throw new IllegalArgumentException("Bad end hour must be > (start + 1)");
        }
        this.startHour = start;
        this.endHour = end;
        repaint();
    }

    protected void setItems(List<List<JCalendarItem>> list) {
        assert SwingUtilities.isEventDispatchThread();
        this.itemParts.clear();
        this.allDayItems.clear();
        for (List<JCalendarItem> l : list) {
            final List<AllDayItem> aDayItems = new ArrayList<AllDayItem>();
            final List<ItemPartView> aItems = new ArrayList<ItemPartView>();
            for (JCalendarItem jCalendarItem : l) {
                if (jCalendarItem.getDtStart().before(jCalendarItem.getDtEnd())) {
                    if (jCalendarItem.isDayOnly()) {
                        aDayItems.add(new AllDayItem(jCalendarItem));
                    } else {
                        aItems.addAll(ItemPartView.split(jCalendarItem));
                    }
                } else {
                    System.err.println(
                            "MultipleDayView.setItems() " + jCalendarItem.getSummary() + " start :" + jCalendarItem.getDtStart().getTime() + " after " + jCalendarItem.getDtEnd().getTime() + " !");
                }
            }
            this.allDayItems.add(aDayItems);
            this.itemParts.add(aItems);
        }

        layoutAllDayItems();
        layoutItemParts();
        // Tweak to force header to be relayouted and repainted
        final Container parent = getParent().getParent();
        final int w = parent.getSize().width;
        final int h = parent.getSize().height;
        parent.setSize(w + 1, h);
        parent.validate();
        parent.setSize(w, h);
        parent.validate();
        firePropertyChange(CALENDARD_ITEMS_PROPERTY, null, list);
    }

    private void layoutItemParts() {
        ItemPartViewLayouter layouter = new ItemPartViewLayouter();
        for (List<ItemPartView> items : itemParts) {
            layouter.layout(items);
        }

    }

    private void layoutAllDayItems() {
        // final Calendar c = getFirstDayCalendar();
        // Collections.sort(this.allDayItems, new Comparator<AllDayItem>() {
        //
        // @Override
        // public int compare(AllDayItem o1, AllDayItem o2) {
        // return o1.getLengthFrom(c) - o2.getLengthFrom(c);
        // }
        // });
        // final int size = this.allDayItems.size();
        // for (int i = 0; i < size; i++) {
        // AllDayItem jCalendarItem = this.allDayItems.get(i);
        // // set X and W
        // Calendar start = jCalendarItem.getItem().getDtStart();
        // Calendar end = jCalendarItem.getItem().getDtEnd();
        // if (start.before(getFirstDayCalendar())) {
        // start = getFirstDayCalendar();
        // } else {
        // jCalendarItem.setLeftBorder(true);
        // }
        //
        // if (end.after(getAfterLastDayCalendar())) {
        // end = getAfterLastDayCalendar();
        // end.add(Calendar.MILLISECOND, -1);
        // } else {
        // jCalendarItem.setRightBorder(true);
        // }
        // int x = (int) (start.getTimeInMillis() - getFirstDayCalendar().getTimeInMillis());
        // x = x / MILLIS_PER_DAY;
        // jCalendarItem.setX(x);
        // int l = ((int) (end.getTimeInMillis() - start.getTimeInMillis())) / MILLIS_PER_DAY + 1;
        // jCalendarItem.setW(l);
        //
        // // Y and H
        // jCalendarItem.setY(0);
        // jCalendarItem.setH(1);
        // for (int j = 0; j < i; j++) {
        // AllDayItem layoutedItem = this.allDayItems.get(j);
        // if (layoutedItem.conflictWith(jCalendarItem)) {
        // jCalendarItem.setY(layoutedItem.getY() + 1);
        // }
        // }
        // }
        // allDayItemsRowCount = 0;
        // for (int i = 0; i < size; i++) {
        // AllDayItem jCalendarItem = this.allDayItems.get(i);
        // if (jCalendarItem.getY() + jCalendarItem.getH() > allDayItemsRowCount) {
        // allDayItemsRowCount = jCalendarItem.getY() + jCalendarItem.getH();
        // }
        // }
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(800, TITLE_HEIGHT + (this.endHour - this.startHour) * rowHeight);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        float[] dash4 = { 8f, 4f };

        BasicStroke bs4 = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 1.0f, dash4, 1f);

        final BasicStroke s = new BasicStroke(1f);
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, this.getWidth(), this.getHeight());
        int columnCount = getColumnCount();
        if (columnCount < 1)
            return;

        g2.setStroke(s);
        // H lines : Hours
        int y = deltaY;
        int hourPaintedCount = this.endHour - this.startHour;
        for (int i = 0; i < hourPaintedCount; i++) {
            g.setColor(Color.LIGHT_GRAY);
            g.drawLine(HOURS_LABEL_WIDTH, y, this.getWidth(), y);
            y += rowHeight;
        }
        // H lines : 1/2 Hours
        y = deltaY + rowHeight / 2;
        if (rowHeight > 44) {
            g2.setStroke(s);
        } else {
            g2.setStroke(bs4);
        }
        g.setColor(LIGHT);
        for (int i = 0; i < hourPaintedCount; i++) {
            g.drawLine(HOURS_LABEL_WIDTH, y, this.getWidth(), y);
            y += rowHeight;
        }

        if (rowHeight > 44) {
            g2.setStroke(bs4);
            y = deltaY + rowHeight / 4;
            for (int i = 0; i < hourPaintedCount * 2; i++) {
                g.drawLine(HOURS_LABEL_WIDTH, y, this.getWidth(), y);
                y += rowHeight / 2;
            }
        }

        // Horizontal lines : 5 minutes
        g.setColor(Color.LIGHT_GRAY);
        if (rowHeight > 120) {
            g2.setStroke(s);
            final float h5 = rowHeight / 12f;

            // int w = this.getWidth();
            int x = HOURS_LABEL_WIDTH;
            for (int j = 0; j < columnCount - 1; j++) {
                x += getColumnWidth(j);
                y = deltaY + (int) h5;
                int x2 = x + 10;
                for (int i = 0; i < 24; i++) {
                    float y1 = y;
                    g.drawLine(x, (int) y1, x2, (int) y1);
                    y1 += h5;
                    g.drawLine(x, (int) y1, x2, (int) y1);
                    y1 += 2 * h5;
                    g.drawLine(x, (int) y1, x2, (int) y1);
                    y1 += h5;
                    g.drawLine(x, (int) y1, x2, (int) y1);
                    y += rowHeight / 2;
                }
            }
        }

        // Hours : text on left
        g2.setStroke(s);
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setFont(getFont().deriveFont(14f));
        y = deltaY + 6;
        g.setColor(Color.GRAY);
        for (int i = this.startHour; i < this.endHour; i++) {
            g.drawString(getHourLabel(i), 5, y);
            y += rowHeight;
        }
        // Vertical lines
        int x = HOURS_LABEL_WIDTH;

        for (int i = 0; i < columnCount - 1; i++) {
            x += getColumnWidth(i);
            g.drawLine(x, 0, x, this.getHeight());
        }
        int dY = deltaY - this.startHour * rowHeight;
        for (int i = 0; i < this.itemParts.size(); i++) {
            List<ItemPartView> items = this.itemParts.get(i);
            final int columnWidth = getColumnWidth(i);
            // Draw standard items
            for (ItemPartView jCalendarItem : items) {
                final int itemX = getColumnX(i);
                jCalendarItem.draw(g2, itemX, dY, rowHeight, columnWidth);
            }
        }

    }

    String getHourLabel(int i) {
        if (i < 10) {
            return "0" + i + ":00";
        }
        return String.valueOf(i) + ":00";
    }

    public int getColumnWidth(int column) {
        final int c = (this.getWidth() - HOURS_LABEL_WIDTH) / getColumnCount();

        return c;
    }

    public int getColumnX(int column) {
        int x = HOURS_LABEL_WIDTH;
        for (int i = 0; i < column; i++) {
            x += getColumnWidth(i);

        }
        return x;
    }

    public void paintHeader(Graphics g) {

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, this.getWidth() + 100, getHeaderHeight());
        g.setColor(Color.GRAY);
        // Vertical lines
        int columnCount = getColumnCount();
        int x = HOURS_LABEL_WIDTH;

        for (int i = 0; i < columnCount - 1; i++) {
            x += getColumnWidth(i);
            g.drawLine(x, YEAR_HEIGHT, x, this.getHeaderHeight());
        }
        g.setColor(Color.DARK_GRAY);
        // Text : month & year
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setFont(getFont().deriveFont(13f));

        String strTitle = getTitle();
        Rectangle r = g.getFontMetrics().getStringBounds(strTitle, g).getBounds();
        int w = 0;
        for (int i = 0; i < getColumnCount(); i++) {
            w += getColumnWidth(i);
        }
        g.drawString(strTitle, (int) (HOURS_LABEL_WIDTH + (w - r.getWidth()) / 2), 15);

        // Text : day
        final Font font1 = getFont().deriveFont(12f);
        g.setFont(font1);
        x = HOURS_LABEL_WIDTH;
        Rectangle clipRect = g.getClipBounds();
        for (int i = 0; i < columnCount; i++) {
            String str = getColumnTitle(i);
            r = g.getFontMetrics().getStringBounds(str, g).getBounds();
            int columnWidth = getColumnWidth(i);
            g.setClip(x, YEAR_HEIGHT, columnWidth - 1, YEAR_HEIGHT + 20);
            // Centering
            int x2 = (int) (x + (columnWidth - r.getWidth()) / 2);
            // If no room, left align
            if (x2 < x + 2) {
                x2 = x + 2;
            }
            g.drawString(str, x2, YEAR_HEIGHT + 15);
            x += columnWidth;

        }
        g.setClip(clipRect);
        paintHeaderAlldays(g);
    }

    public String getTitle() {
        return "Title";
    }

    public void paintHeaderAlldays(Graphics g) {
        // Draw alldays
        // for (AllDayItem jCalendarItem : this.allDayItems) {
        // int w = 0;
        // for (int i = jCalendarItem.getX(); i < jCalendarItem.getX() + jCalendarItem.getW(); i++)
        // {
        // w += getColumnWidth(i);
        // }
        // int x2 = getColumnX(jCalendarItem.getX());
        // jCalendarItem.draw(g2, x2, YEAR_HEIGHT + TITLE_HEIGHT, allDayItemsRowHeight, w);
        // }
    }

    public int getHeaderHeight() {
        return YEAR_HEIGHT + TITLE_HEIGHT + this.allDayItemsRowCount * this.allDayItemsRowHeight + 4;
    }

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return new Dimension(1024, 768);
    }

    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return orientation * this.rowHeight * 2;
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        return this.rowHeight;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return true;
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    public void mouseWheelMoved(MouseWheelEvent e, MouseWheelListener[] l) {

        if (e.getModifiers() == InputEvent.CTRL_MASK) {

            if (e.getWheelRotation() < 0) {
                zoom(22);
            } else {
                zoom(-22);
            }

            e.consume();
        } else {
            for (int i = 0; i < l.length; i++) {
                MouseWheelListener mouseWheelListener = l[i];
                mouseWheelListener.mouseWheelMoved(e);
            }
        }

    }

    public final void setZoom(int zIndex) {
        if (zIndex >= 0) {
            int rh = (1 + zIndex) * 22;
            setRowHeight(rh);
        }
    }

    public final int getZoom() {
        return (this.rowHeight / 22) - 1;
    }

    private void zoom(int v) {
        setRowHeight(this.rowHeight + v);
    }

    public final void setRowHeight(final int v) {
        if (this.rowHeight != v && v < 200 && v > 20) {
            this.rowHeight = v;
            // update size
            final int w = this.getSize().width;
            final int h = getPreferredSize().height;
            setSize(new Dimension(w, h));
            validate();
            repaint();
            this.firePropertyChange("rowHeight", null, v);
            this.firePropertyChange("zoom", null, getZoom());
        }
    }

    // how many pixels for one hour
    public final int getRowHeight() {
        return this.rowHeight;
    }

    public void setPopupMenuProvider(JPopupMenuProvider popupProvider) {
        this.popupProvider = popupProvider;
    }

    public Set<ItemPartView> getSelectedItems() {
        return selectedItems;
    }

    public ItemPartView getItemPartAt(int x, int y) {
        ItemPartView newSelection = null;
        for (List<ItemPartView> l : itemParts) {
            for (ItemPartView p : l) {
                if (p.contains(x, y)) {
                    newSelection = p;
                    break;
                }
            }
        }
        return newSelection;
    }

    public void addItemPartHoverListener(ItemPartHoverListener l) {
        this.hListeners.add(l);
    }

    public void removeItemPartHoverListener(ItemPartHoverListener l) {
        this.hListeners.remove(l);
    }

    abstract public String getColumnTitle(int index);

    abstract public int getColumnCount();

    abstract public void reload();

    public void deselectAll() {
        for (ItemPartView p : selectedItems) {
            p.setSelected(false);
        }
        selectedItems.clear();

    }
}