changeset 1306:ac26a7ee871d

Make all timeline-related classes experimental Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-November/008640.html
author Omair Majid <omajid@redhat.com>
date Thu, 07 Nov 2013 14:55:48 -0500
parents b69449c66e57
children 75fe7ce6dea2
files client/swing/pom.xml client/swing/src/main/java/com/redhat/thermostat/client/swing/components/BasicEventTimelineUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimeline.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineDataChangeListener.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineModel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineRangeChangeListener.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalMouseHandler.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelector.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorModel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorUIBasic.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/BasicEventTimelineUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimeline.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineDataChangeListener.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineModel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineRangeChangeListener.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/Timeline.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalMouseHandler.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelector.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelectorModel.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelectorUI.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelectorUIBasic.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineRulerHeader.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineUtils.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/timeline/Timeline.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/timeline/TimelineRulerHeader.java client/swing/src/main/java/com/redhat/thermostat/client/swing/components/timeline/TimelineUtils.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/EventTimelineModelTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorModelTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorTest.java client/swing/src/test/java/com/redhat/thermostat/client/swing/components/timeline/TimelineTest.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/ThreadTimelineHeader.java thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineComponent.java vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/OverlayComponent.java vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java
diffstat 38 files changed, 2091 insertions(+), 2086 deletions(-) [+]
line wrap: on
line diff
--- a/client/swing/pom.xml	Thu Nov 07 19:10:06 2013 +0100
+++ b/client/swing/pom.xml	Thu Nov 07 14:55:48 2013 -0500
@@ -168,7 +168,7 @@
               com.redhat.thermostat.client.swing,
               com.redhat.thermostat.client.swing.components,
               com.redhat.thermostat.client.swing.components.models,
-              com.redhat.thermostat.client.swing.components.timeline,
+              com.redhat.thermostat.client.swing.components.experimental,
               com.redhat.thermostat.client.swing.vmlist,
             </Export-Package>
             <Private-Package>
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/BasicEventTimelineUI.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,419 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Cursor;
-import java.awt.FontMetrics;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.GridLayout;
-import java.awt.Insets;
-import java.awt.Paint;
-import java.awt.RenderingHints;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.AdjustmentEvent;
-import java.awt.event.AdjustmentListener;
-import java.awt.event.HierarchyBoundsListener;
-import java.awt.event.HierarchyEvent;
-import java.awt.event.HierarchyListener;
-import java.util.concurrent.TimeUnit;
-
-import javax.swing.JButton;
-import javax.swing.JComponent;
-import javax.swing.JPanel;
-
-import com.redhat.thermostat.client.swing.components.EventTimelineModel.Event;
-import com.redhat.thermostat.client.swing.components.TimelineIntervalMouseHandler.TimeIntervalSelectorTarget;
-import com.redhat.thermostat.client.swing.components.timeline.Timeline;
-import com.redhat.thermostat.client.swing.internal.LocaleResources;
-import com.redhat.thermostat.common.model.LongRangeNormalizer;
-import com.redhat.thermostat.common.model.Range;
-import com.redhat.thermostat.shared.locale.Translate;
-
-public class BasicEventTimelineUI extends EventTimelineUI {
-
-    private static final Translate<LocaleResources> translate = LocaleResources.createLocalizer();
-
-    private static final Color DEFAULT_FILL_COLOR = new Color(0,0,0,255);
-    private static final Color DEFAULT_EDGE_COLOR = Color.BLACK;
-    private static final Color DEFAULT_MARKER_COLOR = Color.BLACK;
-
-    private EventTimeline eventTimeline;
-
-    private OverviewPanel overviewPanel = new OverviewPanel();
-    private Timeline overviewRuler;
-
-    private Refresher refresher = new Refresher(overviewPanel);
-    private JButton moveLeftButton;
-    private JButton moveRightButton;
-    private JButton zoomInButton;
-    private JButton zoomOutButton;
-    private JButton resetZoomButton;
-
-    @Override
-    protected void installComponents(EventTimeline component) {
-        eventTimeline = component;
-
-        overviewRuler = new Timeline(new Range<Long>(1l, 2l));
-
-        moveLeftButton = new JButton("<");
-        moveLeftButton.setMargin(new Insets(0, 0, 0, 0));
-        moveRightButton = new JButton(">");
-        moveRightButton.setMargin(new Insets(0, 0, 0, 0));
-
-        JPanel buttonPanel = new JPanel();
-        buttonPanel.setLayout(new GridLayout());
-
-        zoomInButton = new JButton("+");
-        zoomInButton.setToolTipText(translate.localize(LocaleResources.ZOOM_IN).getContents());
-        zoomInButton.setMargin(new Insets(2, 2, 2, 2));
-
-        buttonPanel.add(zoomInButton);
-
-        zoomOutButton = new JButton("-");
-        zoomOutButton.setToolTipText(translate.localize(LocaleResources.ZOOM_OUT).getContents());
-        zoomOutButton.setMargin(new Insets(2, 2, 2, 2));
-        buttonPanel.add(zoomOutButton);
-
-        resetZoomButton = new JButton("R");
-        resetZoomButton.setToolTipText(translate.localize(LocaleResources.RESET_ZOOM).getContents());
-        resetZoomButton.setMargin(new Insets(2, 2, 2, 2));
-        buttonPanel.add(resetZoomButton);
-
-        GridBagLayout layout = new GridBagLayout();
-        component.setLayout(layout);
-        overviewPanel.setLayout(layout);
-
-        GridBagConstraints c = new GridBagConstraints();
-
-        c.gridx = 0;
-        c.gridy = 0;
-        c.fill = GridBagConstraints.VERTICAL;
-        c.weightx = 0;
-        c.weighty = 1;
-
-        component.add(moveLeftButton, c);
-
-        c.gridx = 1;
-        c.gridy = 0;
-        c.fill = GridBagConstraints.BOTH;
-        c.weighty = 1.0;
-        c.weightx = 1.0;
-
-        component.add(overviewPanel, c);
-
-        c.gridy++;
-        c.fill = GridBagConstraints.HORIZONTAL;
-        c.weighty = 0;
-        component.add(overviewRuler, c);
-
-        c.gridx = 2;
-        c.gridy = 0;
-        c.fill = GridBagConstraints.VERTICAL;
-        c.weighty = 1;
-        c.weightx = 0;
-        component.add(moveRightButton, c);
-
-        c.gridx = 0;
-        c.gridy = 3;
-        c.fill = GridBagConstraints.VERTICAL;
-        c.anchor = GridBagConstraints.LINE_START;
-        c.weightx = 1;
-        c.weighty = 0;
-        c.gridwidth = 4;
-        component.add(buttonPanel, c);
-    }
-
-    @Override
-    protected void installDefaults(EventTimeline c) {
-        c.setSelectionEdgePaint(DEFAULT_EDGE_COLOR);
-        c.setSelectionFillPaint(DEFAULT_FILL_COLOR);
-        c.setEventPaint(DEFAULT_MARKER_COLOR);
-    }
-
-    @Override
-    protected void installListeners(EventTimeline c) {
-        c.addHierarchyBoundsListener(refresher);
-        c.addHierarchyListener(refresher);
-        c.getModel().addDataChangeListener(refresher);
-        c.getModel().addRangeChangeListener(new EventTimelineRangeChangeListener() {
-            @Override
-            public void rangeChanged(Range<Long> overview, Range<Long> detail) {
-                overviewRuler.setRange(overview);
-                overviewRuler.repaint();
-
-            }
-        });
-        moveRightButton.addActionListener(new DetailChangeListener() {
-            @Override
-            protected Range<Long> computeNewDetailRange(long min, long max) {
-                long diff = (long) ((max - min) * 0.1);
-                return new Range<>(min + diff, max + diff);
-            }
-        });
-        moveLeftButton.addActionListener(new DetailChangeListener() {
-            protected Range<Long> computeNewDetailRange(long min, long max) {
-                long diff = (long) ((max - min) * 0.1);
-                return new Range<>(min - diff, max - diff);
-            };
-        });
-        zoomOutButton.addActionListener(new DetailChangeListener() {
-           protected Range<Long> computeNewDetailRange(long min, long max) {
-               long diff = max - min;
-               return new Range<>(min - diff / 2, max + diff / 2);
-           };
-        });
-
-        zoomInButton.addActionListener(new DetailChangeListener() {
-            protected Range<Long> computeNewDetailRange(long min, long max) {
-                long diff = max - min;
-                return new Range<>(min + diff / 4, max - diff / 4);
-            };
-        });
-
-        resetZoomButton.addActionListener(new DetailChangeListener() {
-            @Override
-            protected Range<Long> computeNewDetailRange(long min, long max) {
-                long timeDelta = max - min;
-
-                long tenMinutesInMillis = TimeUnit.MINUTES.toMillis(10);
-
-                if (timeDelta <= tenMinutesInMillis) {
-                    return new Range<>(min, max);
-                } else {
-                    return new Range<>(max - tenMinutesInMillis, max);
-                }
-            }
-        });
-    }
-
-    private abstract class DetailChangeListener implements ActionListener {
-        @Override
-        public void actionPerformed(ActionEvent arg0) {
-            Range<Long> range = eventTimeline.getModel().getDetailRange();
-
-            long min = range.getMin();
-            long max = range.getMax();
-
-            eventTimeline.getModel().setDetailRange(computeNewDetailRange(min, max));
-
-            overviewPanel.refresh();
-        }
-
-        protected abstract Range<Long> computeNewDetailRange(long min, long max);
-    }
-
-    protected void uninstallListeners(EventTimeline c) {
-        c.removeHierarchyBoundsListener(refresher);
-        c.removeHierarchyListener(refresher);
-    }
-
-    @Override
-    protected void uninstallComponents(EventTimeline c) {
-        c.remove(overviewPanel);
-
-        eventTimeline = null;
-    }
-
-    private long positionToTimeStamp(int position) {
-        Range<Long> range = eventTimeline.getModel().getTotalRange();
-        LongRangeNormalizer normalizer = new LongRangeNormalizer(new Range<>(0l, (long)overviewPanel.getWidth()), range);
-        long result = normalizer.getValueNormalized(position);
-        return result;
-    }
-
-    private int timeStampToPosition(long timeStamp) {
-        Range<Long> range = eventTimeline.getModel().getTotalRange();
-        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, new Range<>(0l, (long)overviewPanel.getWidth()));
-        int result = (int) normalizer.getValueNormalized(timeStamp);
-        return result;
-    }
-
-    private static class Refresher implements HierarchyBoundsListener, HierarchyListener, AdjustmentListener, EventTimelineDataChangeListener {
-
-        private OverviewPanel toRefresh;
-
-        public Refresher(OverviewPanel toRefresh) {
-            this.toRefresh = toRefresh;
-        }
-
-        @Override
-        public void dataChanged() {
-            refresh();
-        }
-
-        @Override
-        public void adjustmentValueChanged(AdjustmentEvent e) {
-            refresh();
-        }
-
-        @Override
-        public void ancestorMoved(HierarchyEvent e) {
-            refresh();
-        }
-
-        @Override
-        public void ancestorResized(HierarchyEvent e) {
-            refresh();
-        }
-
-        @Override
-        public void hierarchyChanged(HierarchyEvent e) {
-            refresh();
-        }
-
-        private void refresh() {
-            toRefresh.refresh();
-        }
-    }
-
-    private class OverviewPanel extends JPanel implements TimeIntervalSelectorTarget {
-
-        private int MOUSE_MARGIN = 10;
-
-        private int left;
-        private int right;
-
-        public OverviewPanel() {
-            TimelineIntervalMouseHandler chartMotionListener = new TimelineIntervalMouseHandler(this);
-            addMouseMotionListener(chartMotionListener);
-            addMouseListener(chartMotionListener);
-        }
-
-        public void refresh() {
-            recomputeBars();
-            repaint();
-        }
-
-        private void recomputeBars() {
-            Range<Long> range = eventTimeline.getModel().getDetailRange();
-            if (range != null) {
-                left = timeStampToPosition(range.getMin());
-                right = timeStampToPosition(range.getMax());
-            }
-        }
-
-        @Override
-        protected void paintComponent(Graphics g) {
-            super.paintComponent(g);
-
-            int width = (right - left);
-            g.clearRect(0, 0, getWidth(), getHeight());
-
-            Graphics2D g2 = (Graphics2D) g;
-            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-
-            EventTimelineModel model = eventTimeline.getModel();
-
-            int i = 0;
-            for (Event event : model.getEvents()) {
-                paintEvent(g2, event, i);
-                i++;
-            }
-
-            Paint fillColor = eventTimeline.getSelectionFillPaint();
-            g2.setPaint(fillColor);
-            g2.fillRect(left, 1, width, getHeight() - 2);
-
-            g2.setStroke(new BasicStroke(2));
-            Paint edgeColor = eventTimeline.getSelectionEdgePaint();
-            g2.setPaint(edgeColor);
-            g2.drawRect(left, 1, width, getHeight() - 2);
-
-            g2.dispose();
-        }
-
-        private void paintEvent(Graphics2D g, Event event, int count) {
-            int y = getYBandPosition(count);
-            int x = timeStampToPosition(event.getTimeStamp());
-            paintEvent(g, event.getDescription(), x, y);
-        }
-
-        private int getYBandPosition(int step) {
-            // TODO can we do better in determining the number of 'bands' ?
-            int TOTAL_STEPS = 10;
-            step = step % TOTAL_STEPS + 1;
-
-            return Math.round(1.0f * step * getHeight() / TOTAL_STEPS);
-        }
-
-        private void paintEvent(Graphics2D g, String text, int x, int y) {
-            g = (Graphics2D) g.create();
-
-            FontMetrics metrics = g.getFontMetrics();
-            int descent = metrics.getDescent();
-            int stringWidth = (int) Math.round(metrics.getStringBounds(text, g).getWidth());
-
-            g.setPaint(eventTimeline.getEventPaint());
-            g.drawLine(x, getHeight(), x, y + descent);
-            g.drawLine(x, y + descent, x + stringWidth, y + descent);
-            g.drawString(text, x, y);
-
-            g.dispose();
-        }
-
-        @Override
-        public int getSelectionMargin() {
-            return MOUSE_MARGIN;
-        }
-
-        @Override
-        public int getLeftSelectionPosition() {
-            return left;
-        }
-
-        @Override
-        public int getRightSelectionPosition() {
-            return right;
-        }
-
-        @Override
-        public void updateSelectionPosition(int newLeft, int newRight) {
-            left = newLeft;
-            right = newRight;
-            Range<Long> range = new Range<Long>(positionToTimeStamp(left), positionToTimeStamp(right));
-            eventTimeline.getModel().setDetailRange(range);
-            refresh();
-        }
-    }
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimeline.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,105 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import java.awt.Paint;
-
-import javax.swing.JComponent;
-import javax.swing.UIManager;
-
-public class EventTimeline extends JComponent {
-
-    private static final String uiClassID = "EventTimelineUI";
-
-    private EventTimelineModel model = new EventTimelineModel();
-
-    private Paint selectionEdgePaint = null;
-    private Paint selectionFillPaint = null;
-    private Paint eventPaint;
-
-    public EventTimeline() {
-        updateUI();
-    }
-
-    public void setUI(EventTimelineUI newUI) {
-        super.setUI(newUI);
-    }
-
-    @Override
-    public void updateUI() {
-        if (UIManager.get(getUIClassID()) != null) {
-            setUI((EventTimelineUI) UIManager.getUI(this));
-        } else {
-            setUI(new BasicEventTimelineUI());
-        }
-    }
-
-    @Override
-    public String getUIClassID() {
-        return uiClassID;
-    }
-
-    public EventTimelineModel getModel() {
-        return model;
-    }
-
-    public Paint getSelectionEdgePaint() {
-        return selectionEdgePaint;
-    }
-
-    public void setSelectionEdgePaint(Paint edgePaint) {
-        this.selectionEdgePaint = edgePaint;
-    }
-
-    public Paint getSelectionFillPaint() {
-        return selectionFillPaint;
-    }
-
-    public void setSelectionFillPaint(Paint fillPaint) {
-        this.selectionFillPaint = fillPaint;
-    }
-
-    public void setEventPaint(Paint eventPaint) {
-        this.eventPaint = eventPaint;
-    }
-
-    public Paint getEventPaint() {
-        return eventPaint;
-    }
-
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineDataChangeListener.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-public interface EventTimelineDataChangeListener {
-
-    void dataChanged();
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineModel.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,167 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.TimeUnit;
-
-import com.redhat.thermostat.common.model.Range;
-
-/**
- * A time line with an total and a detail range.
- */
-public class EventTimelineModel {
-
-    private final List<EventTimelineRangeChangeListener> rangeListeners = new CopyOnWriteArrayList<>();
-    private final List<EventTimelineDataChangeListener> dataListeners = new CopyOnWriteArrayList<>();
-
-    private Range<Long> totalRange;
-    private Range<Long> detailRange;;
-    private List<Event> events = new ArrayList<>();
-
-    private boolean isAdjusting = false;
-
-    public Range<Long> getTotalRange() {
-        return totalRange;
-    }
-
-    private void setTotalRange(Range<Long> newRange) {
-        if (totalRange != null && totalRange.equals(newRange)) {
-            return;
-        }
-
-        this.totalRange = newRange;
-        fireRangeChanged();
-    }
-
-    public void addEvent(long eventTimeStamp, String description) {
-        addEvent(new Event(eventTimeStamp, description));
-    }
-
-    public void addEvent(Event event) {
-        long eventTimeStamp = event.getTimeStamp();
-
-        if (totalRange == null) {
-            // some heuristics to get sane initial ranges automagically
-            setTotalRange(new Range<>(eventTimeStamp - TimeUnit.MINUTES.toMillis(1), eventTimeStamp + TimeUnit.MINUTES.toMillis(1)));
-            setDetailRange(new Range<>(eventTimeStamp - TimeUnit.MINUTES.toMillis(1), eventTimeStamp + TimeUnit.MINUTES.toMillis(1)));
-        } else {
-            long delta = (long) ((totalRange.getMax() - totalRange.getMin()) * 0.1);
-
-            if (totalRange.getMax() < eventTimeStamp + delta) {
-                setTotalRange(new Range<>(totalRange.getMin(), eventTimeStamp + delta));
-            } else if (totalRange.getMin() + delta > eventTimeStamp) {
-                setTotalRange(new Range<>(eventTimeStamp - delta, totalRange.getMax()));
-            }
-        }
-
-        events.add(event);
-        fireEventDataChanged();
-    }
-
-    public List<Event> getEvents() {
-        return events;
-    }
-
-    public void clearEvents() {
-        events.clear();
-    }
-
-    public void setDetailRange(Range<Long> range) {
-        if (detailRange != null && detailRange.equals(range)) {
-            return;
-        }
-
-        this.detailRange = range;
-
-        fireRangeChanged();
-    }
-
-    public Range<Long> getDetailRange() {
-        return detailRange;
-    }
-
-    public void addRangeChangeListener(EventTimelineRangeChangeListener listener) {
-        rangeListeners.add(listener);
-    }
-
-    public void removeRangeChangeListener(EventTimelineRangeChangeListener listener) {
-        rangeListeners.remove(listener);
-    }
-
-    private void fireRangeChanged() {
-        for (EventTimelineRangeChangeListener listener : rangeListeners) {
-            listener.rangeChanged(this.totalRange, this.detailRange);
-        }
-    }
-
-    public void addDataChangeListener(EventTimelineDataChangeListener listener) {
-        dataListeners.add(listener);
-    }
-
-    public void removeEventDataChangeListener(EventTimelineDataChangeListener listener) {
-        dataListeners.remove(listener);
-    }
-
-    private void fireEventDataChanged() {
-        for (EventTimelineDataChangeListener listener : dataListeners) {
-            listener.dataChanged();
-        }
-    }
-
-    public static class Event {
-
-        private final long timeStamp;
-        private final String description;
-
-        public Event(long timeStamp, String description) {
-            this.timeStamp = timeStamp;
-            this.description = description;
-        }
-
-        public long getTimeStamp() {
-            return timeStamp;
-        }
-
-        public String getDescription() {
-            return description;
-        }
-    }
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineRangeChangeListener.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import com.redhat.thermostat.common.model.Range;
-
-public interface EventTimelineRangeChangeListener {
-
-    void rangeChanged(Range<Long> overview, Range<Long> detail);
-
-}
\ No newline at end of file
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/EventTimelineUI.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import javax.swing.JComponent;
-import javax.swing.plaf.ComponentUI;
-
-public abstract class EventTimelineUI extends ComponentUI {
-
-    public void installUI(JComponent c) {
-        EventTimeline zc = (EventTimeline) c;
-        installComponents(zc);
-        installDefaults(zc);
-        installListeners(zc);
-    }
-
-    protected void installComponents(EventTimeline c) {}
-
-    protected void installDefaults(EventTimeline c) {}
-
-    protected void installListeners(EventTimeline c) {}
-
-    public void uninstallUI(JComponent c) {
-        EventTimeline zc = (EventTimeline) c;
-
-        uninstallListeners(zc);
-        uninstallDefaults(zc);
-        uninstallComponents(zc);
-    }
-
-    protected void uninstallListeners(EventTimeline c) {}
-
-    protected void uninstallDefaults(EventTimeline c) {}
-
-    protected void uninstallComponents(EventTimeline c) {}
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalMouseHandler.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,118 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2, or (at your option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work based on this
- * code. Thus, the terms and conditions of the GNU General Public License cover
- * the whole combination.
- *
- * As a special exception, the copyright holders of this code give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent modules, and
- * to copy and distribute the resulting executable under terms of your choice,
- * provided that you also meet, for each linked independent module, the terms
- * and conditions of the license of that module. An independent module is a
- * module which is not derived from or based on this code. If you modify this
- * code, you may extend this exception to your version of the library, but you
- * are not obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import java.awt.Cursor;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
-
-class TimelineIntervalMouseHandler extends MouseAdapter {
-
-    static interface TimeIntervalSelectorTarget {
-        void setCursor(Cursor cursor);
-        int getSelectionMargin();
-        int getLeftSelectionPosition();
-        int getRightSelectionPosition();
-        void updateSelectionPosition(int left, int right);
-    }
-
-    private final TimeIntervalSelectorTarget target;
-
-    private boolean moving = false;
-    private boolean movingLeft = false;
-    private boolean movingRight = false;
-
-    private int oldX = -1;
-    private int oldLeft = -1;
-    private int oldRight = -1;
-
-    public TimelineIntervalMouseHandler(TimeIntervalSelectorTarget target) {
-        this.target = target;
-    }
-
-    @Override
-    public void mouseMoved(MouseEvent e) {
-        if (Math.abs(e.getX() - target.getLeftSelectionPosition()) < target.getSelectionMargin()) {
-            target.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
-        } else if (Math.abs(e.getX() - target.getRightSelectionPosition()) < target.getSelectionMargin()) {
-            target.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
-        } else if ((e.getX() > target.getSelectionMargin() + target.getLeftSelectionPosition()) && (e.getX() < target.getRightSelectionPosition() - target.getSelectionMargin())) {
-            target.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
-        } else {
-            target.setCursor(Cursor.getDefaultCursor());
-        }
-
-    }
-
-    @Override
-    public void mouseDragged(MouseEvent e) {
-        if (moving || movingLeft || movingRight) {
-
-            int newLeft = oldLeft;
-            int newRight = oldRight;
-            if (movingLeft) {
-                newLeft = e.getX();
-            } else if (movingRight) {
-                newRight = e.getX();
-            } else if (moving) {
-                long delta = e.getX() - oldX;
-                newLeft += delta;
-                newRight += delta;
-            }
-
-            target.updateSelectionPosition(newLeft, newRight);
-        }
-    }
-
-    @Override
-    public void mouseReleased(MouseEvent e) {
-        moving = movingLeft = movingRight = false;
-        oldLeft = oldRight = oldX = -1;
-    }
-
-    @Override
-    public void mousePressed(MouseEvent e) {
-        if (Math.abs(e.getX() - target.getLeftSelectionPosition()) < target.getSelectionMargin()) {
-            movingLeft = true;
-        } else if (Math.abs(e.getX() - target.getRightSelectionPosition()) < target.getSelectionMargin()) {
-            movingRight = true;
-        } else if ((e.getX() > target.getLeftSelectionPosition() + target.getSelectionMargin()) && (e.getX() < target.getRightSelectionPosition() - target.getSelectionMargin())) {
-            moving = true;
-        }
-        oldLeft = target.getLeftSelectionPosition();
-        oldRight = target.getRightSelectionPosition();
-        oldX = e.getX();
-    }
-
-    // TODO implement wheel scrolling
-}
\ No newline at end of file
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelector.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2, or (at your option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work based on this
- * code. Thus, the terms and conditions of the GNU General Public License cover
- * the whole combination.
- *
- * As a special exception, the copyright holders of this code give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent modules, and
- * to copy and distribute the resulting executable under terms of your choice,
- * provided that you also meet, for each linked independent module, the terms
- * and conditions of the license of that module. An independent module is a
- * module which is not derived from or based on this code. If you modify this
- * code, you may extend this exception to your version of the library, but you
- * are not obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import java.awt.Paint;
-
-import javax.swing.JComponent;
-import javax.swing.UIManager;
-
-/**
- * A component that allows specifying a time range to select
- */
-public class TimelineIntervalSelector extends JComponent {
-
-    private static final String uiClassID = "TimelineIntervalSelectorUI";
-
-    private TimelineIntervalSelectorModel model;
-
-    private Paint linePaint;
-
-    private Paint fillPaint;
-
-    public TimelineIntervalSelector() {
-        model = new TimelineIntervalSelectorModel();
-
-        updateUI();
-    }
-
-    public TimelineIntervalSelectorModel getModel() {
-        return model;
-    }
-
-    public void setUI(TimelineIntervalSelectorUI ui) {
-        super.setUI(ui);
-    }
-
-    @Override
-    public void updateUI() {
-        if (UIManager.get(getUIClassID()) != null) {
-            setUI((TimelineIntervalSelectorUI) UIManager.getUI(this));
-        } else {
-            setUI(new TimelineIntervalSelectorUIBasic());
-        }
-    }
-
-    public TimelineIntervalSelectorUI getUI() {
-        return (TimelineIntervalSelectorUI) ui;
-    }
-
-    @Override
-    public String getUIClassID() {
-        return uiClassID;
-    }
-
-    public void setSelectionLinePaint(Paint paint) {
-        this.linePaint = paint;
-    }
-
-    public Paint getSelectionLinePaint() {
-        return this.linePaint;
-    }
-
-    public void setSelectionFillPaint(Paint paint) {
-        this.fillPaint = paint;
-    }
-
-    public Paint getSelectionFillPaint() {
-        return this.fillPaint;
-    }
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorModel.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,167 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2, or (at your option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work based on this
- * code. Thus, the terms and conditions of the GNU General Public License cover
- * the whole combination.
- *
- * As a special exception, the copyright holders of this code give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent modules, and
- * to copy and distribute the resulting executable under terms of your choice,
- * provided that you also meet, for each linked independent module, the terms
- * and conditions of the license of that module. An independent module is a
- * module which is not derived from or based on this code. If you modify this
- * code, you may extend this exception to your version of the library, but you
- * are not obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import java.util.EventListener;
-import java.util.concurrent.TimeUnit;
-
-import javax.swing.event.EventListenerList;
-
-public class TimelineIntervalSelectorModel {
-
-    public static interface ChangeListener extends EventListener {
-        public void changed();
-    }
-
-    private final EventListenerList listeners = new EventListenerList();
-
-    private final long CREATION_TIME = System.currentTimeMillis();
-
-    private long totalMinimum = CREATION_TIME;
-    private long totalMaximum = CREATION_TIME + TimeUnit.HOURS.toMillis(1);
-
-    private long selectedMinimum = CREATION_TIME;
-    private long selectedMaximum = CREATION_TIME + TimeUnit.MINUTES.toMillis(10);
-
-    public long getTotalMinimum() {
-        return totalMinimum;
-    }
-
-    public void setTotalMinimum(long totalMinimum) {
-        setTotalMinimum(totalMinimum, true);
-    }
-    public void setTotalMinimum(long totalMinimum, boolean notify) {
-        if (this.totalMinimum != totalMinimum) {
-            this.totalMinimum = totalMinimum;
-
-            if (this.totalMaximum < this.totalMinimum) {
-                this.totalMaximum = this.totalMinimum;
-            }
-
-            if (this.selectedMaximum < this.totalMinimum){
-                this.selectedMaximum = this.totalMinimum;
-            }
-
-            if (this.selectedMinimum < this.totalMinimum) {
-                this.selectedMinimum = this.totalMinimum;
-            }
-
-            if (notify) {
-                fireModelChanged();
-            }
-        }
-    }
-
-    public long getTotalMaximum() {
-        return totalMaximum;
-    }
-
-    public void setTotalMaximum(long totalMaximum) {
-        setTotalMaximum(totalMaximum, true);
-    }
-
-    public void setTotalMaximum(long totalMaximum, boolean notify) {
-        if (this.totalMaximum != totalMaximum) {
-            this.totalMaximum = totalMaximum;
-
-            if (this.totalMinimum > this.totalMaximum) {
-                this.totalMinimum = this.totalMaximum;
-            }
-
-            if (this.selectedMaximum > this.totalMaximum) {
-                this.selectedMaximum = this.totalMaximum;
-            }
-
-            if (this.selectedMinimum > this.totalMaximum) {
-                this.selectedMinimum = this.totalMaximum;
-            }
-
-            if (notify) {
-                fireModelChanged();
-            }
-        }
-    }
-
-    public long getSelectedMinimum() {
-        return selectedMinimum;
-    }
-
-    public void setSelectedMinimum(long selectedMinimum) {
-        setSelectedMinimum(selectedMinimum, true);
-    }
-
-    public void setSelectedMinimum(long selectedMinimum, boolean notify) {
-        if(this.selectedMinimum != selectedMinimum) {
-            this.selectedMinimum = selectedMinimum;
-            if (notify) {
-                fireModelChanged();
-            }
-        }
-    }
-
-    public long getSelectedMaximum() {
-        return selectedMaximum;
-    }
-
-    public void setSelectedMaximum(long selectedMaximum) {
-        setSelectedMaximum(selectedMaximum, true);
-    }
-
-    public void setSelectedMaximum(long selectedMaximum, boolean notify) {
-        if (this.selectedMaximum != selectedMaximum) {
-            this.selectedMaximum = selectedMaximum;
-            if (notify) {
-                fireModelChanged();
-            }
-        }
-    }
-
-    public void addChangeListener(ChangeListener l) {
-        listeners.add(ChangeListener.class, l);
-    }
-
-    public void removeChangeListener(ChangeListener l) {
-        listeners.remove(ChangeListener.class, l);
-    }
-
-    private void fireModelChanged() {
-        Object[] listeners = this.listeners.getListenerList();
-
-        for (int i = listeners.length - 2; i >= 0; i -= 2) {
-            if (listeners[i] == ChangeListener.class) {
-                ((ChangeListener) listeners[i + 1]).changed();
-            }
-        }
-    }
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorUI.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,39 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2, or (at your option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work based on this
- * code. Thus, the terms and conditions of the GNU General Public License cover
- * the whole combination.
- *
- * As a special exception, the copyright holders of this code give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent modules, and
- * to copy and distribute the resulting executable under terms of your choice,
- * provided that you also meet, for each linked independent module, the terms
- * and conditions of the license of that module. An independent module is a
- * module which is not derived from or based on this code. If you modify this
- * code, you may extend this exception to your version of the library, but you
- * are not obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import javax.swing.plaf.ComponentUI;
-
-public class TimelineIntervalSelectorUI extends ComponentUI {
-    // a marker class
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorUIBasic.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,292 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License as published by the Free Software
- * Foundation; either version 2, or (at your option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work based on this
- * code. Thus, the terms and conditions of the GNU General Public License cover
- * the whole combination.
- *
- * As a special exception, the copyright holders of this code give you
- * permission to link this code with independent modules to produce an
- * executable, regardless of the license terms of these independent modules, and
- * to copy and distribute the resulting executable under terms of your choice,
- * provided that you also meet, for each linked independent module, the terms
- * and conditions of the license of that module. An independent module is a
- * module which is not derived from or based on this code. If you modify this
- * code, you may extend this exception to your version of the library, but you
- * are not obligated to do so. If you do not wish to do so, delete this
- * exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Cursor;
-import java.awt.Dimension;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.geom.Rectangle2D;
-import java.awt.geom.RoundRectangle2D;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
-
-import javax.swing.Box;
-import javax.swing.BoxLayout;
-import javax.swing.JComponent;
-import javax.swing.JPanel;
-import javax.swing.border.EmptyBorder;
-
-import com.redhat.thermostat.client.swing.components.TimelineIntervalMouseHandler.TimeIntervalSelectorTarget;
-import com.redhat.thermostat.client.swing.components.TimelineIntervalSelectorModel.ChangeListener;
-import com.redhat.thermostat.client.swing.components.timeline.Timeline;
-import com.redhat.thermostat.client.ui.Palette;
-import com.redhat.thermostat.common.model.Range;
-
-public class TimelineIntervalSelectorUIBasic extends TimelineIntervalSelectorUI implements TimeIntervalSelectorTarget {
-
-    private static final int SIDE_PADDING = 10;
-
-    // the extra gap below the timeline header itself
-    private static final int GAP_BELOW = 15; /* pixels */
-
-    private TimelineIntervalSelector component;
-
-    private TimelineIntervalMouseHandler mouseListener = new TimelineIntervalMouseHandler(this);
-    private Timeline timeline = new Timeline(new Range<Long>(0l, 100l));
-    private JPanel customPaintingPanel = new CustomPaintPanel();
-
-    private Component topGlue;
-    private Component bottomGlue;
-
-    private ChangeListener timelineSelectionPainter = new ChangeListener() {
-        @Override
-        public void changed() {
-            component.repaint();
-        }
-    };
-    private ChangeListener timelineRangeUpdater = new ChangeListener() {
-        @Override
-        public void changed() {
-            TimelineIntervalSelectorModel model = component.getModel();
-            timeline.setRange(new Range<>(model.getTotalMinimum(), model.getTotalMaximum()));
-        }
-    };
-    private PropertyChangeListener enabledListener = new PropertyChangeListener() {
-        @Override
-        public void propertyChange(PropertyChangeEvent evt) {
-            if (evt.getPropertyName().equals("enabled")) {
-                boolean enabled = (Boolean) evt.getNewValue();
-                timeline.setEnabled(enabled);
-                if (enabled) {
-                    addUserInputListeners();
-                } else {
-                    removeUserInputListeners();
-                }
-            }
-        }
-    };
-
-    public TimelineIntervalSelectorUIBasic() {
-        customPaintingPanel.setLayout(new BorderLayout());
-        customPaintingPanel.setBorder(new EmptyBorder(0, SIDE_PADDING, 0, SIDE_PADDING));
-    }
-
-    @Override
-    public void installUI(JComponent c) {
-        super.installUI(c);
-
-        component = (TimelineIntervalSelector) c;
-
-        installDefaults();
-        installComponents();
-        installListeners();
-
-    }
-
-    protected void installDefaults() {
-        component.setLayout(new BoxLayout(component, BoxLayout.PAGE_AXIS));
-
-        component.setSelectionLinePaint(Color.BLACK);
-    }
-
-    protected void installComponents() {
-        topGlue = Box.createVerticalGlue();
-        bottomGlue = Box.createVerticalGlue();
-
-        customPaintingPanel.add(timeline, BorderLayout.CENTER);
-
-        component.add(topGlue);
-        component.add(customPaintingPanel);
-        component.add(bottomGlue);
-    }
-
-    protected void installListeners() {
-        component.getModel().addChangeListener(timelineSelectionPainter);
-        component.getModel().addChangeListener(timelineRangeUpdater);
-
-        component.addPropertyChangeListener(enabledListener);
-
-        addUserInputListeners();
-    }
-
-    @Override
-    public void uninstallUI(JComponent c) {
-        uninstallListeners();
-        uninstallComponents();
-        uninstallDefaults();
-
-        component = null;
-
-        super.uninstallUI(c);
-    }
-
-    protected void uninstallComponents() {
-        customPaintingPanel.remove(timeline);
-
-        component.remove(bottomGlue);
-        component.remove(customPaintingPanel);
-        component.remove(topGlue);
-    }
-
-    protected void uninstallListeners() {
-        removeUserInputListeners();
-
-        component.getModel().removeChangeListener(timelineRangeUpdater);
-        component.getModel().removeChangeListener(timelineSelectionPainter);
-    }
-
-    protected void uninstallDefaults() {
-        component.setSelectionLinePaint(null);
-
-        component.setLayout(null);
-    }
-
-    private void removeUserInputListeners() {
-        component.removeMouseWheelListener(mouseListener);
-        component.removeMouseMotionListener(mouseListener);
-        component.removeMouseListener(mouseListener);
-    }
-
-    private void addUserInputListeners() {
-        component.addMouseListener(mouseListener);
-        component.addMouseMotionListener(mouseListener);
-        component.addMouseWheelListener(mouseListener);
-    }
-
-    @Override
-    public int getLeftSelectionPosition() {
-        return domainToX(component.getModel().getSelectedMinimum());
-    }
-
-    @Override
-    public int getRightSelectionPosition() {
-        return domainToX(component.getModel().getSelectedMaximum());
-    }
-
-    @Override
-    public int getSelectionMargin() {
-        return 20;
-    }
-
-    @Override
-    public void setCursor(Cursor cursor) {
-        component.setCursor(cursor);
-    }
-
-    @Override
-    public void updateSelectionPosition(int left, int right) {
-        long min = xToDomain(left);
-        long max = xToDomain(right);
-        component.getModel().setSelectedMaximum(max);
-        component.getModel().setSelectedMinimum(min);
-
-        component.repaint();
-    }
-
-    private int domainToX(long domainValue) {
-        long domainMin = component.getModel().getTotalMinimum();
-        long domainMax = component.getModel().getTotalMaximum();
-        int width = timeline.getWidth();
-        return (int) (1.0 * (domainValue - domainMin) / (domainMax - domainMin) * (width - 1));
-    }
-
-    private long xToDomain(int x) {
-        long domainMin = component.getModel().getTotalMinimum();
-        long domainMax = component.getModel().getTotalMaximum();
-        int width = timeline.getWidth();
-        return (long) ((1.0 * x / (width - 1) * (domainMax - domainMin)) + domainMin);
-    }
-
-    private class CustomPaintPanel extends JPanel {
-
-        @Override
-        public Dimension getPreferredSize() {
-            return new Dimension(super.getPreferredSize().width, timeline.getPreferredSize().height + GAP_BELOW);
-        }
-
-        @Override
-        public void paint(Graphics g) {
-            super.paint(g);
-
-            Graphics2D g2 = (Graphics2D) g.create();
-
-            int left = domainToX(component.getModel().getSelectedMinimum()) + SIDE_PADDING ;
-            int right = domainToX(component.getModel().getSelectedMaximum()) + SIDE_PADDING;
-            int height = getHeight();
-            int width = getWidth() - 1;
-
-            boolean enabled = component.isEnabled();
-            if (enabled) {
-                g2.setPaint(component.getSelectionLinePaint());
-            } else {
-                g2.setPaint(Color.LIGHT_GRAY);
-            }
-
-            int pinchHeight = getHeight() - (GAP_BELOW / 2);
-
-            g2.drawLine(0, height, 0, pinchHeight);
-            g2.drawLine(0, pinchHeight, left, pinchHeight);
-            g2.drawLine(left, pinchHeight, left, 0);
-            paintHandle(g2, left, pinchHeight/2);
-
-            g2.drawLine(width, height, width, pinchHeight);
-            g2.drawLine(width, pinchHeight, right, pinchHeight);
-            g2.drawLine(right, pinchHeight, right, 0);
-            paintHandle(g2, right, pinchHeight/2);
-
-            g2.dispose();
-        }
-
-        private void paintHandle(Graphics2D g, int x, int y) {
-            g = (Graphics2D) g.create();
-            g.translate(x, y);
-
-            g.setColor(Palette.LIGHT_GRAY.getColor());
-            g.fill(new RoundRectangle2D.Float(-2, -10, 4, 20, 2, 2));
-
-            if (component.isEnabled()) {
-                g.setPaint(component.getSelectionLinePaint());
-            } else {
-                g.setPaint(Color.LIGHT_GRAY);
-            }
-
-            g.draw(new RoundRectangle2D.Float(-2, -10, 4, 20, 2, 2));
-
-            g.dispose();
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/BasicEventTimelineUI.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Paint;
+import java.awt.RenderingHints;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.HierarchyBoundsListener;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+
+import com.redhat.thermostat.client.swing.components.experimental.EventTimelineModel.Event;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineIntervalMouseHandler.TimeIntervalSelectorTarget;
+import com.redhat.thermostat.client.swing.internal.LocaleResources;
+import com.redhat.thermostat.common.model.LongRangeNormalizer;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.shared.locale.Translate;
+
+public class BasicEventTimelineUI extends EventTimelineUI {
+
+    private static final Translate<LocaleResources> translate = LocaleResources.createLocalizer();
+
+    private static final Color DEFAULT_FILL_COLOR = new Color(0,0,0,255);
+    private static final Color DEFAULT_EDGE_COLOR = Color.BLACK;
+    private static final Color DEFAULT_MARKER_COLOR = Color.BLACK;
+
+    private EventTimeline eventTimeline;
+
+    private OverviewPanel overviewPanel = new OverviewPanel();
+    private Timeline overviewRuler;
+
+    private Refresher refresher = new Refresher(overviewPanel);
+    private JButton moveLeftButton;
+    private JButton moveRightButton;
+    private JButton zoomInButton;
+    private JButton zoomOutButton;
+    private JButton resetZoomButton;
+
+    @Override
+    protected void installComponents(EventTimeline component) {
+        eventTimeline = component;
+
+        overviewRuler = new Timeline(new Range<Long>(1l, 2l));
+
+        moveLeftButton = new JButton("<");
+        moveLeftButton.setMargin(new Insets(0, 0, 0, 0));
+        moveRightButton = new JButton(">");
+        moveRightButton.setMargin(new Insets(0, 0, 0, 0));
+
+        JPanel buttonPanel = new JPanel();
+        buttonPanel.setLayout(new GridLayout());
+
+        zoomInButton = new JButton("+");
+        zoomInButton.setToolTipText(translate.localize(LocaleResources.ZOOM_IN).getContents());
+        zoomInButton.setMargin(new Insets(2, 2, 2, 2));
+
+        buttonPanel.add(zoomInButton);
+
+        zoomOutButton = new JButton("-");
+        zoomOutButton.setToolTipText(translate.localize(LocaleResources.ZOOM_OUT).getContents());
+        zoomOutButton.setMargin(new Insets(2, 2, 2, 2));
+        buttonPanel.add(zoomOutButton);
+
+        resetZoomButton = new JButton("R");
+        resetZoomButton.setToolTipText(translate.localize(LocaleResources.RESET_ZOOM).getContents());
+        resetZoomButton.setMargin(new Insets(2, 2, 2, 2));
+        buttonPanel.add(resetZoomButton);
+
+        GridBagLayout layout = new GridBagLayout();
+        component.setLayout(layout);
+        overviewPanel.setLayout(layout);
+
+        GridBagConstraints c = new GridBagConstraints();
+
+        c.gridx = 0;
+        c.gridy = 0;
+        c.fill = GridBagConstraints.VERTICAL;
+        c.weightx = 0;
+        c.weighty = 1;
+
+        component.add(moveLeftButton, c);
+
+        c.gridx = 1;
+        c.gridy = 0;
+        c.fill = GridBagConstraints.BOTH;
+        c.weighty = 1.0;
+        c.weightx = 1.0;
+
+        component.add(overviewPanel, c);
+
+        c.gridy++;
+        c.fill = GridBagConstraints.HORIZONTAL;
+        c.weighty = 0;
+        component.add(overviewRuler, c);
+
+        c.gridx = 2;
+        c.gridy = 0;
+        c.fill = GridBagConstraints.VERTICAL;
+        c.weighty = 1;
+        c.weightx = 0;
+        component.add(moveRightButton, c);
+
+        c.gridx = 0;
+        c.gridy = 3;
+        c.fill = GridBagConstraints.VERTICAL;
+        c.anchor = GridBagConstraints.LINE_START;
+        c.weightx = 1;
+        c.weighty = 0;
+        c.gridwidth = 4;
+        component.add(buttonPanel, c);
+    }
+
+    @Override
+    protected void installDefaults(EventTimeline c) {
+        c.setSelectionEdgePaint(DEFAULT_EDGE_COLOR);
+        c.setSelectionFillPaint(DEFAULT_FILL_COLOR);
+        c.setEventPaint(DEFAULT_MARKER_COLOR);
+    }
+
+    @Override
+    protected void installListeners(EventTimeline c) {
+        c.addHierarchyBoundsListener(refresher);
+        c.addHierarchyListener(refresher);
+        c.getModel().addDataChangeListener(refresher);
+        c.getModel().addRangeChangeListener(new EventTimelineRangeChangeListener() {
+            @Override
+            public void rangeChanged(Range<Long> overview, Range<Long> detail) {
+                overviewRuler.setRange(overview);
+                overviewRuler.repaint();
+
+            }
+        });
+        moveRightButton.addActionListener(new DetailChangeListener() {
+            @Override
+            protected Range<Long> computeNewDetailRange(long min, long max) {
+                long diff = (long) ((max - min) * 0.1);
+                return new Range<>(min + diff, max + diff);
+            }
+        });
+        moveLeftButton.addActionListener(new DetailChangeListener() {
+            protected Range<Long> computeNewDetailRange(long min, long max) {
+                long diff = (long) ((max - min) * 0.1);
+                return new Range<>(min - diff, max - diff);
+            };
+        });
+        zoomOutButton.addActionListener(new DetailChangeListener() {
+           protected Range<Long> computeNewDetailRange(long min, long max) {
+               long diff = max - min;
+               return new Range<>(min - diff / 2, max + diff / 2);
+           };
+        });
+
+        zoomInButton.addActionListener(new DetailChangeListener() {
+            protected Range<Long> computeNewDetailRange(long min, long max) {
+                long diff = max - min;
+                return new Range<>(min + diff / 4, max - diff / 4);
+            };
+        });
+
+        resetZoomButton.addActionListener(new DetailChangeListener() {
+            @Override
+            protected Range<Long> computeNewDetailRange(long min, long max) {
+                long timeDelta = max - min;
+
+                long tenMinutesInMillis = TimeUnit.MINUTES.toMillis(10);
+
+                if (timeDelta <= tenMinutesInMillis) {
+                    return new Range<>(min, max);
+                } else {
+                    return new Range<>(max - tenMinutesInMillis, max);
+                }
+            }
+        });
+    }
+
+    private abstract class DetailChangeListener implements ActionListener {
+        @Override
+        public void actionPerformed(ActionEvent arg0) {
+            Range<Long> range = eventTimeline.getModel().getDetailRange();
+
+            long min = range.getMin();
+            long max = range.getMax();
+
+            eventTimeline.getModel().setDetailRange(computeNewDetailRange(min, max));
+
+            overviewPanel.refresh();
+        }
+
+        protected abstract Range<Long> computeNewDetailRange(long min, long max);
+    }
+
+    protected void uninstallListeners(EventTimeline c) {
+        c.removeHierarchyBoundsListener(refresher);
+        c.removeHierarchyListener(refresher);
+    }
+
+    @Override
+    protected void uninstallComponents(EventTimeline c) {
+        c.remove(overviewPanel);
+
+        eventTimeline = null;
+    }
+
+    private long positionToTimeStamp(int position) {
+        Range<Long> range = eventTimeline.getModel().getTotalRange();
+        LongRangeNormalizer normalizer = new LongRangeNormalizer(new Range<>(0l, (long)overviewPanel.getWidth()), range);
+        long result = normalizer.getValueNormalized(position);
+        return result;
+    }
+
+    private int timeStampToPosition(long timeStamp) {
+        Range<Long> range = eventTimeline.getModel().getTotalRange();
+        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, new Range<>(0l, (long)overviewPanel.getWidth()));
+        int result = (int) normalizer.getValueNormalized(timeStamp);
+        return result;
+    }
+
+    private static class Refresher implements HierarchyBoundsListener, HierarchyListener, AdjustmentListener, EventTimelineDataChangeListener {
+
+        private OverviewPanel toRefresh;
+
+        public Refresher(OverviewPanel toRefresh) {
+            this.toRefresh = toRefresh;
+        }
+
+        @Override
+        public void dataChanged() {
+            refresh();
+        }
+
+        @Override
+        public void adjustmentValueChanged(AdjustmentEvent e) {
+            refresh();
+        }
+
+        @Override
+        public void ancestorMoved(HierarchyEvent e) {
+            refresh();
+        }
+
+        @Override
+        public void ancestorResized(HierarchyEvent e) {
+            refresh();
+        }
+
+        @Override
+        public void hierarchyChanged(HierarchyEvent e) {
+            refresh();
+        }
+
+        private void refresh() {
+            toRefresh.refresh();
+        }
+    }
+
+    private class OverviewPanel extends JPanel implements TimeIntervalSelectorTarget {
+
+        private int MOUSE_MARGIN = 10;
+
+        private int left;
+        private int right;
+
+        public OverviewPanel() {
+            TimelineIntervalMouseHandler chartMotionListener = new TimelineIntervalMouseHandler(this);
+            addMouseMotionListener(chartMotionListener);
+            addMouseListener(chartMotionListener);
+        }
+
+        public void refresh() {
+            recomputeBars();
+            repaint();
+        }
+
+        private void recomputeBars() {
+            Range<Long> range = eventTimeline.getModel().getDetailRange();
+            if (range != null) {
+                left = timeStampToPosition(range.getMin());
+                right = timeStampToPosition(range.getMax());
+            }
+        }
+
+        @Override
+        protected void paintComponent(Graphics g) {
+            super.paintComponent(g);
+
+            int width = (right - left);
+            g.clearRect(0, 0, getWidth(), getHeight());
+
+            Graphics2D g2 = (Graphics2D) g;
+            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+            EventTimelineModel model = eventTimeline.getModel();
+
+            int i = 0;
+            for (Event event : model.getEvents()) {
+                paintEvent(g2, event, i);
+                i++;
+            }
+
+            Paint fillColor = eventTimeline.getSelectionFillPaint();
+            g2.setPaint(fillColor);
+            g2.fillRect(left, 1, width, getHeight() - 2);
+
+            g2.setStroke(new BasicStroke(2));
+            Paint edgeColor = eventTimeline.getSelectionEdgePaint();
+            g2.setPaint(edgeColor);
+            g2.drawRect(left, 1, width, getHeight() - 2);
+
+            g2.dispose();
+        }
+
+        private void paintEvent(Graphics2D g, Event event, int count) {
+            int y = getYBandPosition(count);
+            int x = timeStampToPosition(event.getTimeStamp());
+            paintEvent(g, event.getDescription(), x, y);
+        }
+
+        private int getYBandPosition(int step) {
+            // TODO can we do better in determining the number of 'bands' ?
+            int TOTAL_STEPS = 10;
+            step = step % TOTAL_STEPS + 1;
+
+            return Math.round(1.0f * step * getHeight() / TOTAL_STEPS);
+        }
+
+        private void paintEvent(Graphics2D g, String text, int x, int y) {
+            g = (Graphics2D) g.create();
+
+            FontMetrics metrics = g.getFontMetrics();
+            int descent = metrics.getDescent();
+            int stringWidth = (int) Math.round(metrics.getStringBounds(text, g).getWidth());
+
+            g.setPaint(eventTimeline.getEventPaint());
+            g.drawLine(x, getHeight(), x, y + descent);
+            g.drawLine(x, y + descent, x + stringWidth, y + descent);
+            g.drawString(text, x, y);
+
+            g.dispose();
+        }
+
+        @Override
+        public int getSelectionMargin() {
+            return MOUSE_MARGIN;
+        }
+
+        @Override
+        public int getLeftSelectionPosition() {
+            return left;
+        }
+
+        @Override
+        public int getRightSelectionPosition() {
+            return right;
+        }
+
+        @Override
+        public void updateSelectionPosition(int newLeft, int newRight) {
+            left = newLeft;
+            right = newRight;
+            Range<Long> range = new Range<Long>(positionToTimeStamp(left), positionToTimeStamp(right));
+            eventTimeline.getModel().setDetailRange(range);
+            refresh();
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimeline.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.Paint;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+
+public class EventTimeline extends JComponent {
+
+    private static final String uiClassID = "EventTimelineUI";
+
+    private EventTimelineModel model = new EventTimelineModel();
+
+    private Paint selectionEdgePaint = null;
+    private Paint selectionFillPaint = null;
+    private Paint eventPaint;
+
+    public EventTimeline() {
+        updateUI();
+    }
+
+    public void setUI(EventTimelineUI newUI) {
+        super.setUI(newUI);
+    }
+
+    @Override
+    public void updateUI() {
+        if (UIManager.get(getUIClassID()) != null) {
+            setUI((EventTimelineUI) UIManager.getUI(this));
+        } else {
+            setUI(new BasicEventTimelineUI());
+        }
+    }
+
+    @Override
+    public String getUIClassID() {
+        return uiClassID;
+    }
+
+    public EventTimelineModel getModel() {
+        return model;
+    }
+
+    public Paint getSelectionEdgePaint() {
+        return selectionEdgePaint;
+    }
+
+    public void setSelectionEdgePaint(Paint edgePaint) {
+        this.selectionEdgePaint = edgePaint;
+    }
+
+    public Paint getSelectionFillPaint() {
+        return selectionFillPaint;
+    }
+
+    public void setSelectionFillPaint(Paint fillPaint) {
+        this.selectionFillPaint = fillPaint;
+    }
+
+    public void setEventPaint(Paint eventPaint) {
+        this.eventPaint = eventPaint;
+    }
+
+    public Paint getEventPaint() {
+        return eventPaint;
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineDataChangeListener.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+public interface EventTimelineDataChangeListener {
+
+    void dataChanged();
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineModel.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.common.model.Range;
+
+/**
+ * A time line with an total and a detail range.
+ */
+public class EventTimelineModel {
+
+    private final List<EventTimelineRangeChangeListener> rangeListeners = new CopyOnWriteArrayList<>();
+    private final List<EventTimelineDataChangeListener> dataListeners = new CopyOnWriteArrayList<>();
+
+    private Range<Long> totalRange;
+    private Range<Long> detailRange;;
+    private List<Event> events = new ArrayList<>();
+
+    private boolean isAdjusting = false;
+
+    public Range<Long> getTotalRange() {
+        return totalRange;
+    }
+
+    private void setTotalRange(Range<Long> newRange) {
+        if (totalRange != null && totalRange.equals(newRange)) {
+            return;
+        }
+
+        this.totalRange = newRange;
+        fireRangeChanged();
+    }
+
+    public void addEvent(long eventTimeStamp, String description) {
+        addEvent(new Event(eventTimeStamp, description));
+    }
+
+    public void addEvent(Event event) {
+        long eventTimeStamp = event.getTimeStamp();
+
+        if (totalRange == null) {
+            // some heuristics to get sane initial ranges automagically
+            setTotalRange(new Range<>(eventTimeStamp - TimeUnit.MINUTES.toMillis(1), eventTimeStamp + TimeUnit.MINUTES.toMillis(1)));
+            setDetailRange(new Range<>(eventTimeStamp - TimeUnit.MINUTES.toMillis(1), eventTimeStamp + TimeUnit.MINUTES.toMillis(1)));
+        } else {
+            long delta = (long) ((totalRange.getMax() - totalRange.getMin()) * 0.1);
+
+            if (totalRange.getMax() < eventTimeStamp + delta) {
+                setTotalRange(new Range<>(totalRange.getMin(), eventTimeStamp + delta));
+            } else if (totalRange.getMin() + delta > eventTimeStamp) {
+                setTotalRange(new Range<>(eventTimeStamp - delta, totalRange.getMax()));
+            }
+        }
+
+        events.add(event);
+        fireEventDataChanged();
+    }
+
+    public List<Event> getEvents() {
+        return events;
+    }
+
+    public void clearEvents() {
+        events.clear();
+    }
+
+    public void setDetailRange(Range<Long> range) {
+        if (detailRange != null && detailRange.equals(range)) {
+            return;
+        }
+
+        this.detailRange = range;
+
+        fireRangeChanged();
+    }
+
+    public Range<Long> getDetailRange() {
+        return detailRange;
+    }
+
+    public void addRangeChangeListener(EventTimelineRangeChangeListener listener) {
+        rangeListeners.add(listener);
+    }
+
+    public void removeRangeChangeListener(EventTimelineRangeChangeListener listener) {
+        rangeListeners.remove(listener);
+    }
+
+    private void fireRangeChanged() {
+        for (EventTimelineRangeChangeListener listener : rangeListeners) {
+            listener.rangeChanged(this.totalRange, this.detailRange);
+        }
+    }
+
+    public void addDataChangeListener(EventTimelineDataChangeListener listener) {
+        dataListeners.add(listener);
+    }
+
+    public void removeEventDataChangeListener(EventTimelineDataChangeListener listener) {
+        dataListeners.remove(listener);
+    }
+
+    private void fireEventDataChanged() {
+        for (EventTimelineDataChangeListener listener : dataListeners) {
+            listener.dataChanged();
+        }
+    }
+
+    public static class Event {
+
+        private final long timeStamp;
+        private final String description;
+
+        public Event(long timeStamp, String description) {
+            this.timeStamp = timeStamp;
+            this.description = description;
+        }
+
+        public long getTimeStamp() {
+            return timeStamp;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineRangeChangeListener.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import com.redhat.thermostat.common.model.Range;
+
+public interface EventTimelineRangeChangeListener {
+
+    void rangeChanged(Range<Long> overview, Range<Long> detail);
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/EventTimelineUI.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import javax.swing.JComponent;
+import javax.swing.plaf.ComponentUI;
+
+public abstract class EventTimelineUI extends ComponentUI {
+
+    public void installUI(JComponent c) {
+        EventTimeline zc = (EventTimeline) c;
+        installComponents(zc);
+        installDefaults(zc);
+        installListeners(zc);
+    }
+
+    protected void installComponents(EventTimeline c) {}
+
+    protected void installDefaults(EventTimeline c) {}
+
+    protected void installListeners(EventTimeline c) {}
+
+    public void uninstallUI(JComponent c) {
+        EventTimeline zc = (EventTimeline) c;
+
+        uninstallListeners(zc);
+        uninstallDefaults(zc);
+        uninstallComponents(zc);
+    }
+
+    protected void uninstallListeners(EventTimeline c) {}
+
+    protected void uninstallDefaults(EventTimeline c) {}
+
+    protected void uninstallComponents(EventTimeline c) {}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/Timeline.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.swing.components.GradientPanel;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.common.model.LongRangeNormalizer;
+import com.redhat.thermostat.common.model.Range;
+
+/**
+ * Displays a timeline between the specified start and stop ranges, dynamically
+ * adjusting itself as needed. The values of the ranges are interpreted as
+ * timestamps in milliseconds.
+ */
+@SuppressWarnings("serial")
+public class Timeline extends GradientPanel {
+
+    /** Default height of this component. Subclasses may use different values */
+    public static final int DEFAULT_HEIGHT = 25;
+
+    private Range<Long> range;
+
+    public Timeline(Range<Long> range) {
+
+        super(Palette.LIGHT_GRAY.getColor(), Palette.WHITE.getColor());
+        setFont(TimelineUtils.FONT);
+
+        this.range = range;
+    }
+
+    public Range<Long> getRange() {
+        return range;
+    }
+
+    public void setRange(Range<Long> newRange) {
+        this.range = newRange;
+        repaint();
+    }
+
+    @Override
+    public int getHeight() {
+        return DEFAULT_HEIGHT;
+    }
+
+    @Override
+    public Dimension getMaximumSize() {
+        Dimension dim = super.getMaximumSize();
+        dim.height = getHeight();
+        return dim;
+    }
+
+    @Override
+    public Dimension getMinimumSize() {
+        Dimension dim = super.getMinimumSize();
+        dim.height = getHeight();
+        return dim;
+    }
+
+    @Override
+    public Dimension getPreferredSize() {
+        Dimension dim = super.getPreferredSize();
+        dim.height = getHeight();
+        return dim;
+    }
+
+    @Override
+    public Dimension getSize() {
+        return getPreferredSize();
+    }
+
+    @Override
+    protected void paintComponent(Graphics g) {
+        super.paintComponent(g);
+
+        if (range == null) {
+            return;
+        }
+
+        Graphics2D graphics = GraphicsUtils.getInstance().createAAGraphics(g);
+
+        Rectangle bounds = g.getClipBounds();
+
+        TimeUnit timeUnitForTicks = getBestTimeUnit();
+        
+        drawTicks(graphics, bounds, timeUnitForTicks);
+
+        if (isEnabled()) {
+            graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+        } else {
+            graphics.setColor(Color.GRAY);
+        }
+        graphics.drawLine(bounds.x, bounds.height - 1, bounds.width, bounds.height - 1);
+
+        graphics.dispose();
+    }
+
+    /**
+     * Returns a TimeUnit and the number of subticks needed for that TimeUnit
+     * need to display the current range
+     */
+    private TimeUnit getBestTimeUnit() {
+        long min = range.getMin();
+        long max = range.getMax();
+
+        List<TimeUnit> units = new ArrayList<>();
+        units.add(TimeUnit.DAYS);
+        units.add(TimeUnit.HOURS);
+        units.add(TimeUnit.MINUTES);
+        units.add(TimeUnit.SECONDS);
+        units.add(TimeUnit.MILLISECONDS);
+
+        /* Find the largest unit of time suitable for the range */
+        for (TimeUnit unit: units) {
+            long millis = unit.toMillis(1);
+            if (Math.abs(max - min) >= millis) {
+                return unit;
+            }
+        }
+
+        return null;
+    }
+
+    private void drawTicks(Graphics2D graphics, Rectangle bounds, TimeUnit tickUnit) {
+        Font font = graphics.getFont();
+
+        int widthOfOneCharacter = (int) font.getStringBounds("0", graphics.getFontRenderContext()).getWidth();
+
+        long deltaInMilliseconds = Math.max(1, tickUnit.toMillis(1) / 10);
+
+        // some heuristics for spacing
+        int targetNumberOfIntervals = (int) (getWidth() / (10 * widthOfOneCharacter) / 1.3);
+
+        while ((range.getMax() - range.getMin()) / deltaInMilliseconds > targetNumberOfIntervals) {
+            deltaInMilliseconds *= 2;
+
+        }
+
+        long start = (range.getMin() / deltaInMilliseconds) * deltaInMilliseconds;
+        long end = range.getMax();
+
+        DateFormat df = null;
+
+        switch (tickUnit) {
+        case DAYS:
+            df = new SimpleDateFormat("YY-MM-dd");
+            break;
+        case HOURS:
+            df = new SimpleDateFormat("hh:mm a");
+            break;
+        case MINUTES:
+            df = new SimpleDateFormat("hh:mm a");
+            break;
+        case SECONDS:
+            df = new SimpleDateFormat("mm.ss");
+            break;
+        default:
+            df = new SimpleDateFormat("hh:mm:ss a");
+        }
+
+        Paint gradient = new GradientPaint(0, 0, Palette.WHITE.getColor(), 0,
+                getHeight(), Palette.GRAY.getColor());
+
+        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, new Range<Long>(0l, (long)bounds.width));
+
+        for (long i = start; i < end; i += deltaInMilliseconds) {
+            int x = (int) normalizer.getValueNormalized(i);
+
+            if (isEnabled()) {
+                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+            } else {
+                graphics.setColor(Color.GRAY);
+            }
+
+            graphics.drawLine(x, 0, x, bounds.height);
+
+            graphics.setPaint(gradient);
+            String value = df.format(new Date(i));
+
+            int stringWidth = (int) font.getStringBounds(value,
+                    graphics.getFontRenderContext()).getWidth() - 1;
+            int stringHeight = (int) font.getStringBounds(value,
+                    graphics.getFontRenderContext()).getHeight();
+            graphics.fillRect(x + 1, bounds.y + 5, stringWidth + 4, stringHeight +
+                    4);
+
+            if (isEnabled()) {
+                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+            } else {
+                graphics.setColor(Color.GRAY);
+            }
+
+            graphics.drawString(value, x + 1, bounds.y + stringHeight + 5);
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalMouseHandler.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work based on this
+ * code. Thus, the terms and conditions of the GNU General Public License cover
+ * the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent modules, and
+ * to copy and distribute the resulting executable under terms of your choice,
+ * provided that you also meet, for each linked independent module, the terms
+ * and conditions of the license of that module. An independent module is a
+ * module which is not derived from or based on this code. If you modify this
+ * code, you may extend this exception to your version of the library, but you
+ * are not obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.Cursor;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+class TimelineIntervalMouseHandler extends MouseAdapter {
+
+    static interface TimeIntervalSelectorTarget {
+        void setCursor(Cursor cursor);
+        int getSelectionMargin();
+        int getLeftSelectionPosition();
+        int getRightSelectionPosition();
+        void updateSelectionPosition(int left, int right);
+    }
+
+    private final TimeIntervalSelectorTarget target;
+
+    private boolean moving = false;
+    private boolean movingLeft = false;
+    private boolean movingRight = false;
+
+    private int oldX = -1;
+    private int oldLeft = -1;
+    private int oldRight = -1;
+
+    public TimelineIntervalMouseHandler(TimeIntervalSelectorTarget target) {
+        this.target = target;
+    }
+
+    @Override
+    public void mouseMoved(MouseEvent e) {
+        if (Math.abs(e.getX() - target.getLeftSelectionPosition()) < target.getSelectionMargin()) {
+            target.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
+        } else if (Math.abs(e.getX() - target.getRightSelectionPosition()) < target.getSelectionMargin()) {
+            target.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
+        } else if ((e.getX() > target.getSelectionMargin() + target.getLeftSelectionPosition()) && (e.getX() < target.getRightSelectionPosition() - target.getSelectionMargin())) {
+            target.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
+        } else {
+            target.setCursor(Cursor.getDefaultCursor());
+        }
+
+    }
+
+    @Override
+    public void mouseDragged(MouseEvent e) {
+        if (moving || movingLeft || movingRight) {
+
+            int newLeft = oldLeft;
+            int newRight = oldRight;
+            if (movingLeft) {
+                newLeft = e.getX();
+            } else if (movingRight) {
+                newRight = e.getX();
+            } else if (moving) {
+                long delta = e.getX() - oldX;
+                newLeft += delta;
+                newRight += delta;
+            }
+
+            target.updateSelectionPosition(newLeft, newRight);
+        }
+    }
+
+    @Override
+    public void mouseReleased(MouseEvent e) {
+        moving = movingLeft = movingRight = false;
+        oldLeft = oldRight = oldX = -1;
+    }
+
+    @Override
+    public void mousePressed(MouseEvent e) {
+        if (Math.abs(e.getX() - target.getLeftSelectionPosition()) < target.getSelectionMargin()) {
+            movingLeft = true;
+        } else if (Math.abs(e.getX() - target.getRightSelectionPosition()) < target.getSelectionMargin()) {
+            movingRight = true;
+        } else if ((e.getX() > target.getLeftSelectionPosition() + target.getSelectionMargin()) && (e.getX() < target.getRightSelectionPosition() - target.getSelectionMargin())) {
+            moving = true;
+        }
+        oldLeft = target.getLeftSelectionPosition();
+        oldRight = target.getRightSelectionPosition();
+        oldX = e.getX();
+    }
+
+    // TODO implement wheel scrolling
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelector.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work based on this
+ * code. Thus, the terms and conditions of the GNU General Public License cover
+ * the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent modules, and
+ * to copy and distribute the resulting executable under terms of your choice,
+ * provided that you also meet, for each linked independent module, the terms
+ * and conditions of the license of that module. An independent module is a
+ * module which is not derived from or based on this code. If you modify this
+ * code, you may extend this exception to your version of the library, but you
+ * are not obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.Paint;
+
+import javax.swing.JComponent;
+import javax.swing.UIManager;
+
+/**
+ * A component that allows specifying a time range to select
+ */
+public class TimelineIntervalSelector extends JComponent {
+
+    private static final String uiClassID = "TimelineIntervalSelectorUI";
+
+    private TimelineIntervalSelectorModel model;
+
+    private Paint linePaint;
+
+    private Paint fillPaint;
+
+    public TimelineIntervalSelector() {
+        model = new TimelineIntervalSelectorModel();
+
+        updateUI();
+    }
+
+    public TimelineIntervalSelectorModel getModel() {
+        return model;
+    }
+
+    public void setUI(TimelineIntervalSelectorUI ui) {
+        super.setUI(ui);
+    }
+
+    @Override
+    public void updateUI() {
+        if (UIManager.get(getUIClassID()) != null) {
+            setUI((TimelineIntervalSelectorUI) UIManager.getUI(this));
+        } else {
+            setUI(new TimelineIntervalSelectorUIBasic());
+        }
+    }
+
+    public TimelineIntervalSelectorUI getUI() {
+        return (TimelineIntervalSelectorUI) ui;
+    }
+
+    @Override
+    public String getUIClassID() {
+        return uiClassID;
+    }
+
+    public void setSelectionLinePaint(Paint paint) {
+        this.linePaint = paint;
+    }
+
+    public Paint getSelectionLinePaint() {
+        return this.linePaint;
+    }
+
+    public void setSelectionFillPaint(Paint paint) {
+        this.fillPaint = paint;
+    }
+
+    public Paint getSelectionFillPaint() {
+        return this.fillPaint;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelectorModel.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work based on this
+ * code. Thus, the terms and conditions of the GNU General Public License cover
+ * the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent modules, and
+ * to copy and distribute the resulting executable under terms of your choice,
+ * provided that you also meet, for each linked independent module, the terms
+ * and conditions of the license of that module. An independent module is a
+ * module which is not derived from or based on this code. If you modify this
+ * code, you may extend this exception to your version of the library, but you
+ * are not obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.util.EventListener;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.event.EventListenerList;
+
+public class TimelineIntervalSelectorModel {
+
+    public static interface ChangeListener extends EventListener {
+        public void changed();
+    }
+
+    private final EventListenerList listeners = new EventListenerList();
+
+    private final long CREATION_TIME = System.currentTimeMillis();
+
+    private long totalMinimum = CREATION_TIME;
+    private long totalMaximum = CREATION_TIME + TimeUnit.HOURS.toMillis(1);
+
+    private long selectedMinimum = CREATION_TIME;
+    private long selectedMaximum = CREATION_TIME + TimeUnit.MINUTES.toMillis(10);
+
+    public long getTotalMinimum() {
+        return totalMinimum;
+    }
+
+    public void setTotalMinimum(long totalMinimum) {
+        setTotalMinimum(totalMinimum, true);
+    }
+    public void setTotalMinimum(long totalMinimum, boolean notify) {
+        if (this.totalMinimum != totalMinimum) {
+            this.totalMinimum = totalMinimum;
+
+            if (this.totalMaximum < this.totalMinimum) {
+                this.totalMaximum = this.totalMinimum;
+            }
+
+            if (this.selectedMaximum < this.totalMinimum){
+                this.selectedMaximum = this.totalMinimum;
+            }
+
+            if (this.selectedMinimum < this.totalMinimum) {
+                this.selectedMinimum = this.totalMinimum;
+            }
+
+            if (notify) {
+                fireModelChanged();
+            }
+        }
+    }
+
+    public long getTotalMaximum() {
+        return totalMaximum;
+    }
+
+    public void setTotalMaximum(long totalMaximum) {
+        setTotalMaximum(totalMaximum, true);
+    }
+
+    public void setTotalMaximum(long totalMaximum, boolean notify) {
+        if (this.totalMaximum != totalMaximum) {
+            this.totalMaximum = totalMaximum;
+
+            if (this.totalMinimum > this.totalMaximum) {
+                this.totalMinimum = this.totalMaximum;
+            }
+
+            if (this.selectedMaximum > this.totalMaximum) {
+                this.selectedMaximum = this.totalMaximum;
+            }
+
+            if (this.selectedMinimum > this.totalMaximum) {
+                this.selectedMinimum = this.totalMaximum;
+            }
+
+            if (notify) {
+                fireModelChanged();
+            }
+        }
+    }
+
+    public long getSelectedMinimum() {
+        return selectedMinimum;
+    }
+
+    public void setSelectedMinimum(long selectedMinimum) {
+        setSelectedMinimum(selectedMinimum, true);
+    }
+
+    public void setSelectedMinimum(long selectedMinimum, boolean notify) {
+        if(this.selectedMinimum != selectedMinimum) {
+            this.selectedMinimum = selectedMinimum;
+            if (notify) {
+                fireModelChanged();
+            }
+        }
+    }
+
+    public long getSelectedMaximum() {
+        return selectedMaximum;
+    }
+
+    public void setSelectedMaximum(long selectedMaximum) {
+        setSelectedMaximum(selectedMaximum, true);
+    }
+
+    public void setSelectedMaximum(long selectedMaximum, boolean notify) {
+        if (this.selectedMaximum != selectedMaximum) {
+            this.selectedMaximum = selectedMaximum;
+            if (notify) {
+                fireModelChanged();
+            }
+        }
+    }
+
+    public void addChangeListener(ChangeListener l) {
+        listeners.add(ChangeListener.class, l);
+    }
+
+    public void removeChangeListener(ChangeListener l) {
+        listeners.remove(ChangeListener.class, l);
+    }
+
+    private void fireModelChanged() {
+        Object[] listeners = this.listeners.getListenerList();
+
+        for (int i = listeners.length - 2; i >= 0; i -= 2) {
+            if (listeners[i] == ChangeListener.class) {
+                ((ChangeListener) listeners[i + 1]).changed();
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelectorUI.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work based on this
+ * code. Thus, the terms and conditions of the GNU General Public License cover
+ * the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent modules, and
+ * to copy and distribute the resulting executable under terms of your choice,
+ * provided that you also meet, for each linked independent module, the terms
+ * and conditions of the license of that module. An independent module is a
+ * module which is not derived from or based on this code. If you modify this
+ * code, you may extend this exception to your version of the library, but you
+ * are not obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import javax.swing.plaf.ComponentUI;
+
+public class TimelineIntervalSelectorUI extends ComponentUI {
+    // a marker class
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineIntervalSelectorUIBasic.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2, or (at your option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * Thermostat; see the file COPYING. If not see <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work based on this
+ * code. Thus, the terms and conditions of the GNU General Public License cover
+ * the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give you
+ * permission to link this code with independent modules to produce an
+ * executable, regardless of the license terms of these independent modules, and
+ * to copy and distribute the resulting executable under terms of your choice,
+ * provided that you also meet, for each linked independent module, the terms
+ * and conditions of the license of that module. An independent module is a
+ * module which is not derived from or based on this code. If you modify this
+ * code, you may extend this exception to your version of the library, but you
+ * are not obligated to do so. If you do not wish to do so, delete this
+ * exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.RoundRectangle2D;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.border.EmptyBorder;
+
+import com.redhat.thermostat.client.swing.components.experimental.TimelineIntervalMouseHandler.TimeIntervalSelectorTarget;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineIntervalSelectorModel.ChangeListener;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.common.model.Range;
+
+public class TimelineIntervalSelectorUIBasic extends TimelineIntervalSelectorUI implements TimeIntervalSelectorTarget {
+
+    private static final int SIDE_PADDING = 10;
+
+    // the extra gap below the timeline header itself
+    private static final int GAP_BELOW = 15; /* pixels */
+
+    private TimelineIntervalSelector component;
+
+    private TimelineIntervalMouseHandler mouseListener = new TimelineIntervalMouseHandler(this);
+    private Timeline timeline = new Timeline(new Range<Long>(0l, 100l));
+    private JPanel customPaintingPanel = new CustomPaintPanel();
+
+    private Component topGlue;
+    private Component bottomGlue;
+
+    private ChangeListener timelineSelectionPainter = new ChangeListener() {
+        @Override
+        public void changed() {
+            component.repaint();
+        }
+    };
+    private ChangeListener timelineRangeUpdater = new ChangeListener() {
+        @Override
+        public void changed() {
+            TimelineIntervalSelectorModel model = component.getModel();
+            timeline.setRange(new Range<>(model.getTotalMinimum(), model.getTotalMaximum()));
+        }
+    };
+    private PropertyChangeListener enabledListener = new PropertyChangeListener() {
+        @Override
+        public void propertyChange(PropertyChangeEvent evt) {
+            if (evt.getPropertyName().equals("enabled")) {
+                boolean enabled = (Boolean) evt.getNewValue();
+                timeline.setEnabled(enabled);
+                if (enabled) {
+                    addUserInputListeners();
+                } else {
+                    removeUserInputListeners();
+                }
+            }
+        }
+    };
+
+    public TimelineIntervalSelectorUIBasic() {
+        customPaintingPanel.setLayout(new BorderLayout());
+        customPaintingPanel.setBorder(new EmptyBorder(0, SIDE_PADDING, 0, SIDE_PADDING));
+    }
+
+    @Override
+    public void installUI(JComponent c) {
+        super.installUI(c);
+
+        component = (TimelineIntervalSelector) c;
+
+        installDefaults();
+        installComponents();
+        installListeners();
+
+    }
+
+    protected void installDefaults() {
+        component.setLayout(new BoxLayout(component, BoxLayout.PAGE_AXIS));
+
+        component.setSelectionLinePaint(Color.BLACK);
+    }
+
+    protected void installComponents() {
+        topGlue = Box.createVerticalGlue();
+        bottomGlue = Box.createVerticalGlue();
+
+        customPaintingPanel.add(timeline, BorderLayout.CENTER);
+
+        component.add(topGlue);
+        component.add(customPaintingPanel);
+        component.add(bottomGlue);
+    }
+
+    protected void installListeners() {
+        component.getModel().addChangeListener(timelineSelectionPainter);
+        component.getModel().addChangeListener(timelineRangeUpdater);
+
+        component.addPropertyChangeListener(enabledListener);
+
+        addUserInputListeners();
+    }
+
+    @Override
+    public void uninstallUI(JComponent c) {
+        uninstallListeners();
+        uninstallComponents();
+        uninstallDefaults();
+
+        component = null;
+
+        super.uninstallUI(c);
+    }
+
+    protected void uninstallComponents() {
+        customPaintingPanel.remove(timeline);
+
+        component.remove(bottomGlue);
+        component.remove(customPaintingPanel);
+        component.remove(topGlue);
+    }
+
+    protected void uninstallListeners() {
+        removeUserInputListeners();
+
+        component.getModel().removeChangeListener(timelineRangeUpdater);
+        component.getModel().removeChangeListener(timelineSelectionPainter);
+    }
+
+    protected void uninstallDefaults() {
+        component.setSelectionLinePaint(null);
+
+        component.setLayout(null);
+    }
+
+    private void removeUserInputListeners() {
+        component.removeMouseWheelListener(mouseListener);
+        component.removeMouseMotionListener(mouseListener);
+        component.removeMouseListener(mouseListener);
+    }
+
+    private void addUserInputListeners() {
+        component.addMouseListener(mouseListener);
+        component.addMouseMotionListener(mouseListener);
+        component.addMouseWheelListener(mouseListener);
+    }
+
+    @Override
+    public int getLeftSelectionPosition() {
+        return domainToX(component.getModel().getSelectedMinimum());
+    }
+
+    @Override
+    public int getRightSelectionPosition() {
+        return domainToX(component.getModel().getSelectedMaximum());
+    }
+
+    @Override
+    public int getSelectionMargin() {
+        return 20;
+    }
+
+    @Override
+    public void setCursor(Cursor cursor) {
+        component.setCursor(cursor);
+    }
+
+    @Override
+    public void updateSelectionPosition(int left, int right) {
+        long min = xToDomain(left);
+        long max = xToDomain(right);
+        component.getModel().setSelectedMaximum(max);
+        component.getModel().setSelectedMinimum(min);
+
+        component.repaint();
+    }
+
+    private int domainToX(long domainValue) {
+        long domainMin = component.getModel().getTotalMinimum();
+        long domainMax = component.getModel().getTotalMaximum();
+        int width = timeline.getWidth();
+        return (int) (1.0 * (domainValue - domainMin) / (domainMax - domainMin) * (width - 1));
+    }
+
+    private long xToDomain(int x) {
+        long domainMin = component.getModel().getTotalMinimum();
+        long domainMax = component.getModel().getTotalMaximum();
+        int width = timeline.getWidth();
+        return (long) ((1.0 * x / (width - 1) * (domainMax - domainMin)) + domainMin);
+    }
+
+    private class CustomPaintPanel extends JPanel {
+
+        @Override
+        public Dimension getPreferredSize() {
+            return new Dimension(super.getPreferredSize().width, timeline.getPreferredSize().height + GAP_BELOW);
+        }
+
+        @Override
+        public void paint(Graphics g) {
+            super.paint(g);
+
+            Graphics2D g2 = (Graphics2D) g.create();
+
+            int left = domainToX(component.getModel().getSelectedMinimum()) + SIDE_PADDING ;
+            int right = domainToX(component.getModel().getSelectedMaximum()) + SIDE_PADDING;
+            int height = getHeight();
+            int width = getWidth() - 1;
+
+            boolean enabled = component.isEnabled();
+            if (enabled) {
+                g2.setPaint(component.getSelectionLinePaint());
+            } else {
+                g2.setPaint(Color.LIGHT_GRAY);
+            }
+
+            int pinchHeight = getHeight() - (GAP_BELOW / 2);
+
+            g2.drawLine(0, height, 0, pinchHeight);
+            g2.drawLine(0, pinchHeight, left, pinchHeight);
+            g2.drawLine(left, pinchHeight, left, 0);
+            paintHandle(g2, left, pinchHeight/2);
+
+            g2.drawLine(width, height, width, pinchHeight);
+            g2.drawLine(width, pinchHeight, right, pinchHeight);
+            g2.drawLine(right, pinchHeight, right, 0);
+            paintHandle(g2, right, pinchHeight/2);
+
+            g2.dispose();
+        }
+
+        private void paintHandle(Graphics2D g, int x, int y) {
+            g = (Graphics2D) g.create();
+            g.translate(x, y);
+
+            g.setColor(Palette.LIGHT_GRAY.getColor());
+            g.fill(new RoundRectangle2D.Float(-2, -10, 4, 20, 2, 2));
+
+            if (component.isEnabled()) {
+                g.setPaint(component.getSelectionLinePaint());
+            } else {
+                g.setPaint(Color.LIGHT_GRAY);
+            }
+
+            g.draw(new RoundRectangle2D.Float(-2, -10, 4, 20, 2, 2));
+
+            g.dispose();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineRulerHeader.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Rectangle;
+import java.beans.Transient;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.redhat.thermostat.client.swing.GraphicsUtils;
+import com.redhat.thermostat.client.swing.components.GradientPanel;
+import com.redhat.thermostat.client.ui.Palette;
+import com.redhat.thermostat.common.model.Range;
+
+@SuppressWarnings("serial")
+public abstract class TimelineRulerHeader extends GradientPanel {
+
+    /** Default height of this component. Subclasses may use different values */
+    public static final int DEFAULT_HEIGHT = 25;
+    
+    /**
+     * Default increment is 20 pixels per units.
+     * Subclasses may use different values.
+     * 
+     * @see #DEFAULT_INCREMENT_IN_MILLIS
+     */
+    public static final int DEFAULT_INCREMENT_IN_PIXELS = 20;
+
+    /**
+     * Default increments is 1 second (1000 ms) per pixel unit.
+     * Subclasses may use different values.
+     * 
+     * @see #DEFAULT_INCREMENT_IN_PIXELS
+     */
+    public static final long DEFAULT_INCREMENT_IN_MILLIS = 1_000;
+    
+    private Range<Long> range;
+    
+    public TimelineRulerHeader(Range<Long> range) {
+        
+        super(Palette.LIGHT_GRAY.getColor(), Palette.WHITE.getColor());
+        setFont(TimelineUtils.FONT);
+        
+        this.range = range;
+    }
+    
+    public Range<Long> getRange() {
+        return range;
+    }
+
+    public void setRange(Range<Long> range) {
+        this.range = range;
+    }
+
+    @Override
+    public int getHeight() {
+        return DEFAULT_HEIGHT;
+    }
+    
+    @Override
+    @Transient
+    public Dimension getPreferredSize() {
+        Dimension dim = super.getPreferredSize();
+        dim.height = getHeight();
+        return dim;
+    }
+    
+    @Override
+    public Dimension getSize() {
+        return getPreferredSize();
+    }
+    
+    /**
+     * Defines the distance, in pixels, between one tick mark and the other.
+     */
+    public int getUnitIncrementInPixels() {
+        return DEFAULT_INCREMENT_IN_PIXELS;
+    }
+    
+    /**
+     * Defines how many milliseconds pass between two tick marks.
+     */
+    public long getUnitIncrementInMillis() {
+        return DEFAULT_INCREMENT_IN_MILLIS;
+    }
+    
+    protected abstract int getCurrentDisplayValue();
+    
+    @Override
+    protected void paintComponent(Graphics g) {
+        
+        super.paintComponent(g);
+
+        Graphics2D graphics = GraphicsUtils.getInstance().createAAGraphics(g);
+
+        int currentValue = getCurrentDisplayValue();
+
+        Rectangle bounds = g.getClipBounds();
+        
+        int unitIncrement = getUnitIncrementInPixels();
+        
+        TimelineUtils.drawMarks(graphics, bounds, currentValue, false, unitIncrement);
+        drawTimelineStrings(graphics, currentValue, bounds, unitIncrement);
+        
+        graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+        graphics.drawLine(bounds.x, bounds.height - 1, bounds.width, bounds.height - 1);
+        
+        graphics.dispose();
+    }
+    
+    private void drawTimelineStrings(Graphics2D graphics, int currentValue, Rectangle bounds, int totalInc) {
+        
+        Font font = graphics.getFont();
+                
+        DateFormat df = new SimpleDateFormat("HH:mm:ss");
+        
+        Paint gradient = new GradientPaint(0, 0, Palette.WHITE.getColor(), 0, getHeight(), Palette.GRAY.getColor());
+        
+        graphics.setColor(Palette.EARL_GRAY.getColor());
+
+        long incrementInMillis = getUnitIncrementInMillis();
+        
+        long round = range.getMin() % (10 * incrementInMillis);
+        
+        int shift = (int) (round / incrementInMillis) * totalInc;
+        long currentTime = range.getMin() - round;
+        
+        int lowerBound = bounds.x - (4 * totalInc);
+        int x = ((bounds.x - currentValue) - shift);
+        
+        long increment = 0;
+        int height = getHeight();
+        for (int i = x; i < bounds.width; i += totalInc) {
+            if (increment % 10 == 0 && i >= lowerBound) {
+                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
+                graphics.drawLine(i, 0, i, height);
+                
+                graphics.setPaint(gradient);
+
+                String value = df.format(new Date(currentTime));
+
+                int stringWidth = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getWidth() - 1;
+                int stringHeight = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getHeight();
+                graphics.fillRect(i + 1, bounds.y + 5, stringWidth + 4, stringHeight + 4);
+                
+                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());                
+                graphics.drawString(value, i + 1, bounds.y + stringHeight + 5);
+            }
+            currentTime += incrementInMillis;
+            increment++;
+        }
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/experimental/TimelineUtils.java	Thu Nov 07 14:55:48 2013 -0500
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.client.swing.components.experimental;
+
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+
+import com.redhat.thermostat.client.ui.Palette;
+
+public class TimelineUtils {
+    public static final Font FONT = new Font("SansSerif", Font.PLAIN, 10);
+ 
+    public static void drawMarks(Graphics2D graphics, Rectangle bounds,
+                                 int currentValue, boolean darkerTop, int increment)
+    {
+        int inc = currentValue % increment;
+        int x = (bounds.x - inc);
+        
+        graphics.setColor(Palette.GRAY.getColor());
+        int upperBound = (bounds.x + bounds.width);
+
+        for (int i = x; i < upperBound; i += increment) {
+            graphics.drawLine(i, 0, i, bounds.height);
+            if (darkerTop) {
+                graphics.setColor(Palette.DARK_GRAY.getColor());
+                graphics.drawLine(i, 0, i, 5);
+                graphics.setColor(Palette.GRAY.getColor());
+            }
+        }
+    }
+
+}
+
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/timeline/Timeline.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,246 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components.timeline;
-
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.GradientPaint;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Paint;
-import java.awt.Rectangle;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import com.redhat.thermostat.client.swing.GraphicsUtils;
-import com.redhat.thermostat.client.swing.components.GradientPanel;
-import com.redhat.thermostat.client.ui.Palette;
-import com.redhat.thermostat.common.model.LongRangeNormalizer;
-import com.redhat.thermostat.common.model.Range;
-
-/**
- * Displays a timeline between the specified start and stop ranges, dynamically
- * adjusting itself as needed. The values of the ranges are interpreted as
- * timestamps in milliseconds.
- */
-@SuppressWarnings("serial")
-public class Timeline extends GradientPanel {
-
-    /** Default height of this component. Subclasses may use different values */
-    public static final int DEFAULT_HEIGHT = 25;
-
-    private Range<Long> range;
-
-    public Timeline(Range<Long> range) {
-
-        super(Palette.LIGHT_GRAY.getColor(), Palette.WHITE.getColor());
-        setFont(TimelineUtils.FONT);
-
-        this.range = range;
-    }
-
-    public Range<Long> getRange() {
-        return range;
-    }
-
-    public void setRange(Range<Long> newRange) {
-        this.range = newRange;
-        repaint();
-    }
-
-    @Override
-    public int getHeight() {
-        return DEFAULT_HEIGHT;
-    }
-
-    @Override
-    public Dimension getMaximumSize() {
-        Dimension dim = super.getMaximumSize();
-        dim.height = getHeight();
-        return dim;
-    }
-
-    @Override
-    public Dimension getMinimumSize() {
-        Dimension dim = super.getMinimumSize();
-        dim.height = getHeight();
-        return dim;
-    }
-
-    @Override
-    public Dimension getPreferredSize() {
-        Dimension dim = super.getPreferredSize();
-        dim.height = getHeight();
-        return dim;
-    }
-
-    @Override
-    public Dimension getSize() {
-        return getPreferredSize();
-    }
-
-    @Override
-    protected void paintComponent(Graphics g) {
-        super.paintComponent(g);
-
-        if (range == null) {
-            return;
-        }
-
-        Graphics2D graphics = GraphicsUtils.getInstance().createAAGraphics(g);
-
-        Rectangle bounds = g.getClipBounds();
-
-        TimeUnit timeUnitForTicks = getBestTimeUnit();
-        
-        drawTicks(graphics, bounds, timeUnitForTicks);
-
-        if (isEnabled()) {
-            graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
-        } else {
-            graphics.setColor(Color.GRAY);
-        }
-        graphics.drawLine(bounds.x, bounds.height - 1, bounds.width, bounds.height - 1);
-
-        graphics.dispose();
-    }
-
-    /**
-     * Returns a TimeUnit and the number of subticks needed for that TimeUnit
-     * need to display the current range
-     */
-    private TimeUnit getBestTimeUnit() {
-        long min = range.getMin();
-        long max = range.getMax();
-
-        List<TimeUnit> units = new ArrayList<>();
-        units.add(TimeUnit.DAYS);
-        units.add(TimeUnit.HOURS);
-        units.add(TimeUnit.MINUTES);
-        units.add(TimeUnit.SECONDS);
-        units.add(TimeUnit.MILLISECONDS);
-
-        /* Find the largest unit of time suitable for the range */
-        for (TimeUnit unit: units) {
-            long millis = unit.toMillis(1);
-            if (Math.abs(max - min) >= millis) {
-                return unit;
-            }
-        }
-
-        return null;
-    }
-
-    private void drawTicks(Graphics2D graphics, Rectangle bounds, TimeUnit tickUnit) {
-        Font font = graphics.getFont();
-
-        int widthOfOneCharacter = (int) font.getStringBounds("0", graphics.getFontRenderContext()).getWidth();
-
-        long deltaInMilliseconds = Math.max(1, tickUnit.toMillis(1) / 10);
-
-        // some heuristics for spacing
-        int targetNumberOfIntervals = (int) (getWidth() / (10 * widthOfOneCharacter) / 1.3);
-
-        while ((range.getMax() - range.getMin()) / deltaInMilliseconds > targetNumberOfIntervals) {
-            deltaInMilliseconds *= 2;
-
-        }
-
-        long start = (range.getMin() / deltaInMilliseconds) * deltaInMilliseconds;
-        long end = range.getMax();
-
-        DateFormat df = null;
-
-        switch (tickUnit) {
-        case DAYS:
-            df = new SimpleDateFormat("YY-MM-dd");
-            break;
-        case HOURS:
-            df = new SimpleDateFormat("hh:mm a");
-            break;
-        case MINUTES:
-            df = new SimpleDateFormat("hh:mm a");
-            break;
-        case SECONDS:
-            df = new SimpleDateFormat("mm.ss");
-            break;
-        default:
-            df = new SimpleDateFormat("hh:mm:ss a");
-        }
-
-        Paint gradient = new GradientPaint(0, 0, Palette.WHITE.getColor(), 0,
-                getHeight(), Palette.GRAY.getColor());
-
-        LongRangeNormalizer normalizer = new LongRangeNormalizer(range, new Range<Long>(0l, (long)bounds.width));
-
-        for (long i = start; i < end; i += deltaInMilliseconds) {
-            int x = (int) normalizer.getValueNormalized(i);
-
-            if (isEnabled()) {
-                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
-            } else {
-                graphics.setColor(Color.GRAY);
-            }
-
-            graphics.drawLine(x, 0, x, bounds.height);
-
-            graphics.setPaint(gradient);
-            String value = df.format(new Date(i));
-
-            int stringWidth = (int) font.getStringBounds(value,
-                    graphics.getFontRenderContext()).getWidth() - 1;
-            int stringHeight = (int) font.getStringBounds(value,
-                    graphics.getFontRenderContext()).getHeight();
-            graphics.fillRect(x + 1, bounds.y + 5, stringWidth + 4, stringHeight +
-                    4);
-
-            if (isEnabled()) {
-                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
-            } else {
-                graphics.setColor(Color.GRAY);
-            }
-
-            graphics.drawString(value, x + 1, bounds.y + stringHeight + 5);
-        }
-    }
-
-}
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/timeline/TimelineRulerHeader.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,196 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components.timeline;
-
-import java.awt.Dimension;
-import java.awt.Font;
-import java.awt.GradientPaint;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Paint;
-import java.awt.Rectangle;
-import java.beans.Transient;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
-import com.redhat.thermostat.client.swing.GraphicsUtils;
-import com.redhat.thermostat.client.swing.components.GradientPanel;
-import com.redhat.thermostat.client.ui.Palette;
-import com.redhat.thermostat.common.model.Range;
-
-@SuppressWarnings("serial")
-public abstract class TimelineRulerHeader extends GradientPanel {
-
-    /** Default height of this component. Subclasses may use different values */
-    public static final int DEFAULT_HEIGHT = 25;
-    
-    /**
-     * Default increment is 20 pixels per units.
-     * Subclasses may use different values.
-     * 
-     * @see #DEFAULT_INCREMENT_IN_MILLIS
-     */
-    public static final int DEFAULT_INCREMENT_IN_PIXELS = 20;
-
-    /**
-     * Default increments is 1 second (1000 ms) per pixel unit.
-     * Subclasses may use different values.
-     * 
-     * @see #DEFAULT_INCREMENT_IN_PIXELS
-     */
-    public static final long DEFAULT_INCREMENT_IN_MILLIS = 1_000;
-    
-    private Range<Long> range;
-    
-    public TimelineRulerHeader(Range<Long> range) {
-        
-        super(Palette.LIGHT_GRAY.getColor(), Palette.WHITE.getColor());
-        setFont(TimelineUtils.FONT);
-        
-        this.range = range;
-    }
-    
-    public Range<Long> getRange() {
-        return range;
-    }
-
-    public void setRange(Range<Long> range) {
-        this.range = range;
-    }
-
-    @Override
-    public int getHeight() {
-        return DEFAULT_HEIGHT;
-    }
-    
-    @Override
-    @Transient
-    public Dimension getPreferredSize() {
-        Dimension dim = super.getPreferredSize();
-        dim.height = getHeight();
-        return dim;
-    }
-    
-    @Override
-    public Dimension getSize() {
-        return getPreferredSize();
-    }
-    
-    /**
-     * Defines the distance, in pixels, between one tick mark and the other.
-     */
-    public int getUnitIncrementInPixels() {
-        return DEFAULT_INCREMENT_IN_PIXELS;
-    }
-    
-    /**
-     * Defines how many milliseconds pass between two tick marks.
-     */
-    public long getUnitIncrementInMillis() {
-        return DEFAULT_INCREMENT_IN_MILLIS;
-    }
-    
-    protected abstract int getCurrentDisplayValue();
-    
-    @Override
-    protected void paintComponent(Graphics g) {
-        
-        super.paintComponent(g);
-
-        Graphics2D graphics = GraphicsUtils.getInstance().createAAGraphics(g);
-
-        int currentValue = getCurrentDisplayValue();
-
-        Rectangle bounds = g.getClipBounds();
-        
-        int unitIncrement = getUnitIncrementInPixels();
-        
-        TimelineUtils.drawMarks(graphics, bounds, currentValue, false, unitIncrement);
-        drawTimelineStrings(graphics, currentValue, bounds, unitIncrement);
-        
-        graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
-        graphics.drawLine(bounds.x, bounds.height - 1, bounds.width, bounds.height - 1);
-        
-        graphics.dispose();
-    }
-    
-    private void drawTimelineStrings(Graphics2D graphics, int currentValue, Rectangle bounds, int totalInc) {
-        
-        Font font = graphics.getFont();
-                
-        DateFormat df = new SimpleDateFormat("HH:mm:ss");
-        
-        Paint gradient = new GradientPaint(0, 0, Palette.WHITE.getColor(), 0, getHeight(), Palette.GRAY.getColor());
-        
-        graphics.setColor(Palette.EARL_GRAY.getColor());
-
-        long incrementInMillis = getUnitIncrementInMillis();
-        
-        long round = range.getMin() % (10 * incrementInMillis);
-        
-        int shift = (int) (round / incrementInMillis) * totalInc;
-        long currentTime = range.getMin() - round;
-        
-        int lowerBound = bounds.x - (4 * totalInc);
-        int x = ((bounds.x - currentValue) - shift);
-        
-        long increment = 0;
-        int height = getHeight();
-        for (int i = x; i < bounds.width; i += totalInc) {
-            if (increment % 10 == 0 && i >= lowerBound) {
-                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());
-                graphics.drawLine(i, 0, i, height);
-                
-                graphics.setPaint(gradient);
-
-                String value = df.format(new Date(currentTime));
-
-                int stringWidth = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getWidth() - 1;
-                int stringHeight = (int) font.getStringBounds(value, graphics.getFontRenderContext()).getHeight();
-                graphics.fillRect(i + 1, bounds.y + 5, stringWidth + 4, stringHeight + 4);
-                
-                graphics.setColor(Palette.THERMOSTAT_BLU.getColor());                
-                graphics.drawString(value, i + 1, bounds.y + stringHeight + 5);
-            }
-            currentTime += incrementInMillis;
-            increment++;
-        }
-    }
-
-}
-
--- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/components/timeline/TimelineUtils.java	Thu Nov 07 19:10:06 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.client.swing.components.timeline;
-
-import java.awt.Font;
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
-
-import com.redhat.thermostat.client.ui.Palette;
-
-public class TimelineUtils {
-    public static final Font FONT = new Font("SansSerif", Font.PLAIN, 10);
- 
-    public static void drawMarks(Graphics2D graphics, Rectangle bounds,
-                                 int currentValue, boolean darkerTop, int increment)
-    {
-        int inc = currentValue % increment;
-        int x = (bounds.x - inc);
-        
-        graphics.setColor(Palette.GRAY.getColor());
-        int upperBound = (bounds.x + bounds.width);
-
-        for (int i = x; i < upperBound; i += increment) {
-            graphics.drawLine(i, 0, i, bounds.height);
-            if (darkerTop) {
-                graphics.setColor(Palette.DARK_GRAY.getColor());
-                graphics.drawLine(i, 0, i, 5);
-                graphics.setColor(Palette.GRAY.getColor());
-            }
-        }
-    }
-
-}
-
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/EventTimelineModelTest.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/EventTimelineModelTest.java	Thu Nov 07 14:55:48 2013 -0500
@@ -47,7 +47,10 @@
 
 import org.junit.Test;
 
-import com.redhat.thermostat.client.swing.components.EventTimelineModel.Event;
+import com.redhat.thermostat.client.swing.components.experimental.EventTimelineDataChangeListener;
+import com.redhat.thermostat.client.swing.components.experimental.EventTimelineModel;
+import com.redhat.thermostat.client.swing.components.experimental.EventTimelineRangeChangeListener;
+import com.redhat.thermostat.client.swing.components.experimental.EventTimelineModel.Event;
 import com.redhat.thermostat.common.model.Range;
 
 public class EventTimelineModelTest {
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorModelTest.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorModelTest.java	Thu Nov 07 14:55:48 2013 -0500
@@ -37,6 +37,8 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.client.swing.components.experimental.TimelineIntervalSelectorModel;
+
 public class TimelineIntervalSelectorModelTest {
 
     @Test
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorTest.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/TimelineIntervalSelectorTest.java	Thu Nov 07 14:55:48 2013 -0500
@@ -50,7 +50,8 @@
 import javax.swing.WindowConstants;
 import javax.swing.border.EmptyBorder;
 
-import com.redhat.thermostat.client.swing.components.TimelineIntervalSelectorModel.ChangeListener;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineIntervalSelector;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineIntervalSelectorModel.ChangeListener;
 
 public class TimelineIntervalSelectorTest {
 
--- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/timeline/TimelineTest.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/components/timeline/TimelineTest.java	Thu Nov 07 14:55:48 2013 -0500
@@ -46,6 +46,7 @@
 import javax.swing.SwingUtilities;
 import javax.swing.WindowConstants;
 
+import com.redhat.thermostat.client.swing.components.experimental.Timeline;
 import com.redhat.thermostat.common.model.Range;
 
 public class TimelineTest {
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/SwingThreadTimelineView.java	Thu Nov 07 14:55:48 2013 -0500
@@ -51,7 +51,7 @@
 
 import com.redhat.thermostat.client.swing.ComponentVisibleListener;
 import com.redhat.thermostat.client.swing.SwingComponent;
-import com.redhat.thermostat.client.swing.components.timeline.TimelineRulerHeader;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineRulerHeader;
 import com.redhat.thermostat.common.model.Range;
 import com.redhat.thermostat.thread.client.common.Timeline;
 import com.redhat.thermostat.thread.client.common.view.ThreadTimelineView;
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/ThreadTimelineHeader.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/ThreadTimelineHeader.java	Thu Nov 07 14:55:48 2013 -0500
@@ -38,7 +38,7 @@
 
 import javax.swing.JScrollPane;
 
-import com.redhat.thermostat.client.swing.components.timeline.TimelineRulerHeader;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineRulerHeader;
 import com.redhat.thermostat.common.model.Range;
 
 /**
--- a/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineComponent.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/thread/client-swing/src/main/java/com/redhat/thermostat/thread/client/swing/impl/timeline/TimelineComponent.java	Thu Nov 07 14:55:48 2013 -0500
@@ -50,7 +50,7 @@
 
 import com.redhat.thermostat.client.swing.GraphicsUtils;
 import com.redhat.thermostat.client.swing.components.GradientPanel;
-import com.redhat.thermostat.client.swing.components.timeline.TimelineUtils;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineUtils;
 import com.redhat.thermostat.client.ui.Palette;
 import com.redhat.thermostat.common.model.LongRangeNormalizer;
 import com.redhat.thermostat.common.model.Range;
--- a/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/OverlayComponent.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/vm-heap-analysis/client-swing/src/main/java/com/redhat/thermostat/vm/heap/analysis/client/swing/internal/stats/OverlayComponent.java	Thu Nov 07 14:55:48 2013 -0500
@@ -52,7 +52,7 @@
 import com.redhat.thermostat.client.swing.components.CompositeIcon;
 import com.redhat.thermostat.client.swing.components.Icon;
 import com.redhat.thermostat.client.swing.components.ShadowLabel;
-import com.redhat.thermostat.client.swing.components.timeline.TimelineUtils;
+import com.redhat.thermostat.client.swing.components.experimental.TimelineUtils;
 import com.redhat.thermostat.client.ui.Palette;
 import com.redhat.thermostat.shared.locale.LocalizedString;
 import com.redhat.thermostat.vm.heap.analysis.client.core.HeapIconResources;
--- a/vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java	Thu Nov 07 19:10:06 2013 +0100
+++ b/vm-jmx/client-swing/src/main/java/com/redhat/thermostat/vm/jmx/client/swing/internal/JmxNotificationsSwingView.java	Thu Nov 07 14:55:48 2013 -0500
@@ -68,11 +68,11 @@
 import com.redhat.thermostat.client.swing.IconResource;
 import com.redhat.thermostat.client.swing.SwingComponent;
 import com.redhat.thermostat.client.swing.components.ActionToggleButton;
-import com.redhat.thermostat.client.swing.components.EventTimeline;
-import com.redhat.thermostat.client.swing.components.EventTimelineRangeChangeListener;
 import com.redhat.thermostat.client.swing.components.HeaderPanel;
 import com.redhat.thermostat.client.swing.components.LocalizedLabel;
-import com.redhat.thermostat.client.swing.components.timeline.Timeline;
+import com.redhat.thermostat.client.swing.components.experimental.EventTimeline;
+import com.redhat.thermostat.client.swing.components.experimental.EventTimelineRangeChangeListener;
+import com.redhat.thermostat.client.swing.components.experimental.Timeline;
 import com.redhat.thermostat.client.ui.Palette;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;