Package de.timefinder.planner

Source Code of de.timefinder.planner.ScrollablePanel

/*
*  Copyright 2009 Peter Karich, peat_hal ‘at’ users ‘dot’ sourceforge ‘dot’ net.
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*  under the License.
*/
package de.timefinder.planner;

import de.timefinder.data.CalendarSettings;
import de.timefinder.data.Event;
import de.timefinder.data.ICalendarSettings;
import de.timefinder.data.IntervalInt;
import de.timefinder.data.IntervalLong;
import de.timefinder.data.IntervalLongImpl;
import de.timefinder.data.Task;
import de.timefinder.data.set.IntervalStepFunction;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.Shape;
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.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Collection;
import java.util.Date;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.sf.nachocalendar.CalendarFactory;
import net.sf.nachocalendar.components.DateField;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.DateTimeFormatterBuilder;

/**
* The calendar component to view events in a visual manner.
*
* TODO better project: http://sourceforge.net/projects/bizcal/ -> week view !!??
*
* Another implementation would be to let the conflicting events conflict,
* but make underlying events visible via scrollwheel and
* SwingUtilities.getDeepestComponentAt(parent, X, Y);
* or even better: http://java.sun.com/docs/books/tutorial/uiswing/components/layeredpane.html
*
* @author Peter Karich, peat_hal ‘at’ users ‘dot’ sourceforge ‘dot’ net
*/
public class TimeFinderPlanner extends JPanel {

    private static final long serialVersionUID = -3997754504205811813L;
    public static final String CHANGE_OBJECTS = "eventCollection";

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

            @Override
            public void run() {
                JFrame frame = new JFrame("TimeFinder Planner");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                CalendarSettings settings = new CalendarSettings();

                // set 1/4h as one timeslot, now even half of an hour will be displayed
                settings.setMillisPerTimeslot(15 * 60 * 1000L);
                settings.setTimeslotsPerDay(4 * 8);

                settings.setStartDate(new DateTime(2009, 4, 6, 8, 0, 0, 0));
                TimeFinderPlanner planner = new TimeFinderPlanner(settings);

                // monday
                planner.addInterval(new IntervalLongImpl("Interval 1", 2009, 4, 6, 8, 30, 60));
                planner.addInterval(new IntervalLongImpl("Interval 2", 2009, 4, 6, 9, 0, 60));

                planner.addInterval(new IntervalLongImpl("Interval A", 2009, 4, 6, 10, 0, 120));
                planner.addInterval(new IntervalLongImpl("Interval B", 2009, 4, 6, 10, 0, 180));
                planner.addInterval(new IntervalLongImpl("Interval C", 2009, 4, 6, 11, 0, 120));
                planner.addInterval(new IntervalLongImpl("Interval D", 2009, 4, 6, 13, 0, 180));
                planner.addInterval(new IntervalLongImpl("Interval E", 2009, 4, 6, 14, 0, 120));

                // tuesday
                planner.addInterval(new IntervalLongImpl("Interval 3", 2009, 4, 7, 8, 30, 30));
                planner.addInterval(new IntervalLongImpl("Interval 3", 2009, 4, 7, 9, 0, 60));
                planner.addInterval(new IntervalLongImpl("Interval 4a", 2009, 4, 7, 10, 0, 120));

                planner.addInterval(new IntervalLongImpl("Interval 4b", 2009, 4, 7, 13, 0, 180));
                planner.addInterval(new IntervalLongImpl("Interval 4c", 2009, 4, 7, 14, 0, 60));

                // wednesday
                planner.addInterval(new IntervalLongImpl("Interval 5", 2009, 4, 8, 9, 0, 120));
                planner.addInterval(new IntervalLongImpl("Interval 6 more description "
                        + "to show line breaking", 2009, 4, 8, 10, 0, 120));

                // explicit line breaking
                planner.addInterval(new IntervalLongImpl("brok\nken", 2009, 4, 9, 15, 0, 30));
                // implicit
                planner.addInterval(new IntervalLongImpl("Interval 7 show line breaking and "
                        + "clipping not shown ? 123 ********", 2009, 4, 9, 9, 0, 120));
                planner.addInterval(new IntervalLongImpl("Interval 8", 2009, 4, 9, 11, 0, 120));
                planner.addInterval(new IntervalLongImpl("Interval 9", 2009, 4, 9, 10, 0, 120));
                planner.addInterval(new IntervalLongImpl("Interval 10", 2009, 4, 9, 11, 0, 120));
                planner.addInterval(new IntervalLongImpl("Interval 11", 2009, 4, 9, 13, 0, 120));

                planner.addInterval(new IntervalLongImpl("Interval 12", 2009, 4, 10, 8, 30, 60));
                planner.addInterval(new IntervalLongImpl("Interval 13 smaller than one hour", 2009, 4, 10, 9, 30, 30));

                frame.setContentPane(planner);
                frame.setSize(1000, 640);
                frame.setVisible(true);
            }
        });
    }
    private Logger logger = Logger.getLogger(getClass().getName());
    // TODO: make this font independent, init from g2.getFontMetrics().getHeight(); int fontDesent = g2.getFontMetrics().getDescent();
    private int xTextOffset = 4;
    private int yTextOffset = 4;
    private int yHeaderTextOffset = 15;
    private DefaultListModel taskListModel = new DefaultListModel();
    private JList taskJList = new JList(taskListModel);
    private DefaultListModel eventListModel = new DefaultListModel();
    private JList eventJList = new JList(eventListModel);
    private JPanel rowHeader;
    private JPanel columnHeader;
    private JPanel timetableGrid;
    private JScrollPane scroll;
    private ICalendarSettings settings;
    // TODO calculate from window size and initialize minSize when scrollbars should be visible
    private int dayWidth = 130;
    private int hourWidth = 50;
    private int columnHeaderHeight = 30;
    private int rowHeaderWidth = 80;
    private Color gridColor = Color.LIGHT_GRAY;
    private IntervalStepFunction stepFunction;
    private PropertyChangeListener listener;
    private int visibleDays;
    private JButton nextDaysButton;
    private JButton prevDaysButton;
    private DateField calendarDateField;

    public TimeFinderPlanner(ICalendarSettings settings_) {
        this.settings = settings_;
        if (settings == null) {
            throw new NullPointerException("Settings cannot be null!");
        }
        stepFunction = new IntervalStepFunction();
        timetableGrid = new ScrollablePanel() {

            @Override
            public boolean isOptimizedDrawingEnabled() {
                // our code has no childs so they cannot overlapp
                return true;
            }

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                final Graphics2D g2d = (Graphics2D) g;

                // get viewport clip
                //Shape oldClip = g2d.getClip();
                // get the whole area
                //Rectangle oldBounds = getBounds();

                g2d.setColor(gridColor);
                // vertical lines
                for (int x = 1; x < visibleDays + 1; x++) {
                    g2d.drawLine(x * dayWidth, 0, x * dayWidth, getHoursPerDay() * hourWidth);
                }

                // horizontal lines
                int xEnd = visibleDays * dayWidth;
                for (int y = 1; y < getHoursPerDay() + 1; y++) {
                    g2d.drawLine(0, y * hourWidth, xEnd, y * hourWidth);
                }

                RoundBox selectedBox = null;
                DateTime startDate = settings.getStartDate();
                // paint events
                for (IntervalLong interval : getCurrentIntervals()) {
                    long startMillisOffset = interval.getStart() - startDate.getMillis()
                            + startDate.getMillisOfDay();
                    int day = (int) (startMillisOffset / ICalendarSettings.DAY);

                    int duration_hourWidth = (int) (interval.getDuration() / settings.getMillisPerTimeslot()
                            * getHourFactor() * hourWidth);

                    int start_hourWidth = (int) ((startMillisOffset % CalendarSettings.DAY
                            - startDate.getMillisOfDay()) / ICalendarSettings.HOUR * hourWidth)
                            + (hourWidth * interval.getStartDateTime().getMinuteOfHour() / 60);

                    int numberOfConflicts = stepFunction.getMaxAssignments(interval);
                    int position = stepFunction.getOffset(interval);
                    int thickness = dayWidth / numberOfConflicts;
                    int offset = position * thickness;

                    Rectangle2D paintingRect = new Rectangle2D.Double(
                            day * dayWidth + offset, start_hourWidth,
                            thickness, duration_hourWidth);

                    String text = interval.getDescription();
                    if (text == null || text.length() == 0)
                        text = interval.getName();
                    RoundBox box = new RoundBox(text, numberOfConflicts);
                    box.setRect(paintingRect);

                    if (paintingRect.intersects(mousePositionX, mousePositionY, 1, 1)) {
                        selectedBox = box;
                        continue;
                    }

                    box.paintComponent(g2d);
                }

                // now paint a selected interval a bit larger *and* on the top of the other
                if (selectedBox != null) {
                    selectedBox.setTransparent(false);
                    selectedBox.zoom(g2d.getClip());
                    selectedBox.paintComponent(g2d);
                }
            }
        };

        final DateTimeFormatter fmt = new DateTimeFormatterBuilder().appendDayOfWeekText().
                appendLiteral(" - ").
                appendDayOfMonth(2).
                appendLiteral(". ").
                appendMonthOfYearText().toFormatter();

        columnHeader = new JPanel() {

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);

                Shape oldClip = g.getClip();
                DateTime tempDateTime = settings.getStartDate();
                for (int x = 0; x < visibleDays; x++) {
                    g.setClip(new Rectangle2D.Double(x * dayWidth, 0,
                            dayWidth, columnHeaderHeight));
                    g.drawString(fmt.print(tempDateTime),
                            xTextOffset + x * dayWidth, yHeaderTextOffset);
                    tempDateTime = tempDateTime.plusDays(1);
                }
                g.setClip(oldClip);
            }
        };

        rowHeader = new JPanel() {

            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
                for (int y = 0; y < getHoursPerDay(); y++) {
                    g.drawString(String.format("%1$02d", y + getHourOffset()) + ":00",
                            xTextOffset, yHeaderTextOffset + y * hourWidth);
                }
            }
        };

        scroll = new JScrollPane();
        scroll.setRowHeaderView(rowHeader);

//        scroll.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, new JLabel() {
//
//            @Override
//            public String getText() {
//                return "no:" + getCurrentIntervals().size();
//            }
//        });

        scroll.setColumnHeaderView(columnHeader);

        JPanel timeNorthPanel = new JPanel();
        calendarDateField = CalendarFactory.createDateField();
        calendarDateField.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent e) {
                // get the date only - not the time
                DateTime val = new DateTime(((Date) calendarDateField.getValue()).getTime());
                DateTime start = settings.getStartDate();
                setStartDate(start.withYear(val.getYear()).
                        withMonthOfYear(val.getMonthOfYear()).withDayOfMonth(val.getDayOfMonth()));
            }
        });
        timeNorthPanel.add(calendarDateField);
        timeNorthPanel.add(prevDaysButton = new SmallButton("<"));
        timeNorthPanel.add(nextDaysButton = new SmallButton(">"));
        prevDaysButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                setStartDate(settings.getStartDate().minusDays(settings.getNumberOfDays()));
            }
        });
        nextDaysButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                setStartDate(settings.getStartDate().plusDays(settings.getNumberOfDays()));
            }
        });

        scroll.setViewportView(timetableGrid);

        JPanel southPanel = new JPanel(new GridLayout(1, 0));
        eventJList.setVisibleRowCount(3);
        taskJList.setVisibleRowCount(3);
        southPanel.add(new JScrollPane(eventJList));
        southPanel.add(new JScrollPane(taskJList));

        listener = new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (ICalendarSettings.CHANGE_ALL.equals(evt.getPropertyName())) {
                    initFromSettings();
                }
            }
        };
        settings.addListener(listener);
        initFromSettings();

        setLayout(new BorderLayout());
        add(timeNorthPanel, BorderLayout.NORTH);
        add(scroll, BorderLayout.CENTER);
        add(southPanel, BorderLayout.SOUTH);
    }

    public void updateToolTip() {
        eventJList.setToolTipText(tr("Events") + " " + eventJList.getModel().getSize());
        taskJList.setToolTipText(tr("Tasks") + " " + taskJList.getModel().getSize());
    }

    private int getHourOffset() {
        return settings.getStartDate().getHourOfDay();
    }

    private double getHourFactor() {
        return settings.getMillisPerTimeslot() / (60 * 60 * 1000.0);
    }

    private int getHoursPerDay() {
        return (int) (settings.getTimeslotsPerDay() * getHourFactor()) + 1;
    }

    public void setStartDate(DateTime dateTime) {
        settings.setStartDate(dateTime);
        initFromSettings();
        // preferred size could have changed => repaint is not sufficient
        revalidate();
        repaint();
    }

    public boolean addObject(Object obj) {
        if (obj instanceof Event) {
            Event ev = (Event) obj;
            if (ev.getStart() < 0)
                addTask(settings.toTask(ev));
            else
                addInterval(settings.toIntervalLong(ev));
        } else if (obj instanceof IntervalInt) {
            IntervalInt i = (IntervalInt) obj;
            return addInterval(settings.toIntervalLong(i));
        } else if (obj instanceof Task) {
            addTask((Task) obj);
            return true;
        }

        return false;
    }

    public boolean addInterval(IntervalLong interval) {
        if (containsInterval(interval)) {
            logger.severe("Couldn't add interval: " + interval);
            return false;
        }

//        logger.fine("Adding interval:" + interval);
        eventListModel.addElement(interval);
        boolean ret = stepFunction.addInterval(interval);
        if (!ret)
            logger.severe("Couldn't add interval:" + interval + " to stepFunction - " + stepFunction);

        updateToolTip();
        repaint();
        return true;
    }

    public void addAllInterval(Collection<? extends IntervalLong> all) {
        for (IntervalLong ev : all) {
            addInterval(ev);
        }
    }

    public void addTask(Task task) {
//        logger.fine("Adding task:" + task);
        taskListModel.addElement(task);
        updateToolTip();
    }

    public void addAllTasks(Collection<Task> all) {
        for (Task t : all) {
            addTask(t);
        }
    }

    public boolean removeInterval(IntervalLong ev) {
        boolean ret = containsInterval(ev);
        if (ret) {
            eventListModel.addElement(ev);
            stepFunction.removeInterval(ev);
            repaint();
        }
        return ret;
    }

    public void removeAllObjects() {
//        logger.fine("Remove all objects");
        stepFunction.clear();
        eventListModel.clear();
        repaint();
    }

    public void removeAllTasks() {
        taskListModel.clear();
    }

    public boolean containsInterval(IntervalLong interval) {
        return stepFunction.containsInterval(interval);
    }

    public Collection<IntervalLong> getCurrentIntervals() {
        long start = settings.getStartDate().getMillis();
        long duration = settings.getNumberOfDays() * ICalendarSettings.DAY;
        return stepFunction.getIntervals(start, start + duration);
    }

    @Override
    public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
        super.firePropertyChange(propertyName, oldValue, newValue);
        if (propertyName.equals(CHANGE_OBJECTS)) {
            removeAllObjects();
            removeAllTasks();
            for (Object o : (Collection) newValue) {
                addObject(o);
            }
        }
    }

    public void initFromSettings() {
        if (getHoursPerDay() < 2 || getHourFactor() == 0 || getHourOffset() < 0) {
            logger.severe("startup configuration was wrong!");
            return;
        }

        calendarDateField.setValue(settings.getStartDate().toDate());
        visibleDays = settings.getNumberOfDays();
        Dimension prefSize = new Dimension(visibleDays * dayWidth,
                getHoursPerDay() * hourWidth);

        rowHeader.setPreferredSize(new Dimension(rowHeaderWidth, prefSize.height));
        columnHeader.setPreferredSize(new Dimension(prefSize.width, columnHeaderHeight));
        timetableGrid.setMaximumSize(prefSize);
        timetableGrid.setPreferredSize(prefSize);
    }

    /**
     * This Method releases all resources associated with this object.
     */
    public void close() {
        boolean ret = settings.removeListener(listener);
        assert ret == true;
    }

    @Override
    public void repaint() {
        super.repaint();
        if (timetableGrid != null) {
            timetableGrid.repaint();
        }
    }

    @Override
    public void revalidate() {
        super.revalidate();
        // after init timetableGrid != null and should be revalidated as well,
        // because of the possible changed pref-size.
        if (timetableGrid != null) {
            timetableGrid.revalidate();
        }
    }

    /**
     * This method translates the specified field into a localized string.
     * Go through the code to locate the usage.
     */
    public String tr(String str) {
        return str;
    }
}

/**
* Gimmick class, so that scrolling is a bit faster.
*/
class ScrollablePanel extends JPanel implements Scrollable, MouseMotionListener, MouseListener {

    private static final long serialVersionUID = 3475577056164274369L;
    private int block = 50;
    private int unit = 25;
    protected int mousePositionX = -10;
    protected int mousePositionY = -10;

    public ScrollablePanel() {
        addMouseMotionListener(this);
        addMouseListener(this);
    }

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

    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return unit;
    }

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

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

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

    @Override
    public void mouseDragged(MouseEvent e) {
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        mousePositionX = e.getX();
        mousePositionY = e.getY();
        repaint();
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
    }

    @Override
    public void mouseExited(MouseEvent e) {
        // avoid that zoomed intervals stays zoomed
        mousePositionX = -10;
        mousePositionY = -10;
        repaint();
    }
}
TOP

Related Classes of de.timefinder.planner.ScrollablePanel

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.