/*
Copyright 2008-2010 Gephi
Authors : Julian Bilcke <julian.bilcke@gephi.org>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.desktop.timeline;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Date;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.event.ChangeEvent;
import org.gephi.timeline.api.TimelineAnimator;
import org.gephi.timeline.api.TimelineAnimatorListener;
import org.gephi.timeline.api.TimelineModel;
import org.gephi.timeline.api.TimelineModelListener;
import org.gephi.timeline.spi.TimelineDrawer;
import org.joda.time.DateTime;
import org.joda.time.Days;
import org.joda.time.Hours;
import org.joda.time.Interval;
import org.joda.time.Months;
import org.joda.time.Period;
import org.joda.time.PeriodType;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.openide.util.lookup.ServiceProvider;
/**
*
* @author jbilcke
*/
@ServiceProvider(service = TimelineDrawer.class)
public class MinimalDrawer extends JPanel
implements TimelineDrawer,
MouseListener,
MouseMotionListener,
TimelineAnimatorListener {
private static final long serialVersionUID = 1L;
private MinimalDrawerSettings settings = new MinimalDrawerSettings();
private TimelineModel model = null;
private TimelineAnimator animator = null;
private Integer latestMousePositionX = null;
private int currentMousePositionX = 0;
private static Cursor CURSOR_DEFAULT = new Cursor(Cursor.DEFAULT_CURSOR);
private static Cursor CURSOR_LEFT_HOOK = new Cursor(Cursor.E_RESIZE_CURSOR);
private static Cursor CURSOR_CENTRAL_HOOK = new Cursor(Cursor.MOVE_CURSOR);
private static Cursor CURSOR_RIGHT_HOOK = new Cursor(Cursor.W_RESIZE_CURSOR);
private static Locale LOCALE = Locale.ENGLISH;
private double newfrom = 0;
private double newto = 1;
private double sf = -1;
private double st = -1;
private Timer viewToModelSync = null;
private Timer modelToViewSync = null;
private double oldfrom = -1;
private double oldto = -1;
public Action updateModelAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (model != null) {
if (newfrom != oldfrom || newto != oldto) {
model.setRangeFromFloat(newfrom, newto);
oldfrom = newfrom;
oldto = newto;
}
}
}
};
public Action updateViewAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
if (model != null) {
double tsf = model.getFromFloat() * (double) getWidth();
double tst = model.getToFloat() * (double) getWidth();
if (tsf != sf) {
sf = tsf;
}
if (tst != st) {
st = tst;
}
}
}
};
private boolean mouseInside = false;
public enum TimelineLevel {
MILLISECOND,
SECOND,
MINUTE,
HOUR,
DAY,
WEEK,
MONTH,
YEAR,
YEAR_10,
YEAR_20,
YEAR_50,
YEAR_100
}
public enum TimelineState {
IDLE,
MOVING,
RESIZE_FROM,
RESIZE_TO
}
TimelineState currentState = TimelineState.IDLE;
public enum HighlightedComponent {
NONE,
LEFT_HOOK,
RIGHT_HOOK,
CENTER_HOOK
}
HighlightedComponent highlightedComponent = HighlightedComponent.NONE;
/** Creates new form MinimalDrawer */
public MinimalDrawer() {
//System.out.println("width: " + getWidth());
//System.out.println("height: " + getHeight());
setVisible(true);
addMouseMotionListener(this);
addMouseListener(this);
// setEnabled(true);
viewToModelSync = new Timer(150, updateModelAction);
viewToModelSync.setRepeats(true);
viewToModelSync.start();
// setEnabled(true);
// modelToViewSync = new Timer(2000, updateViewAction);
}
public void setModel(TimelineModel model) {
if (model == null) {
return;
}
if (model != this.model) {
if (this.model != null) {
}
this.model = model;
sf = model.getFromFloat() * (double) getWidth();
st = model.getToFloat() * (double) getWidth();
repaint();
}
}
public TimelineModel getModel() {
return model;
}
public void setAnimator(TimelineAnimator animator) {
this.animator = animator;
}
public TimelineAnimator getAnimator() {
return animator;
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setMinimumSize(new java.awt.Dimension(300, 28));
setLayout(new java.awt.BorderLayout());
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
// initialization
if (st == 0) {
if (model != null) {
sf = 0.0 * (double) width;
st = 1.0 * (double) width;
newfrom = sf * (1.0 / width);
newto = st * (1.0 / width);
}
}
settings.update(width, height);
Graphics2D g2d = (Graphics2D) g;
g2d.setBackground(settings.background.top);
g2d.setPaint(settings.background.paint);
g2d.fillRect(0, settings.tmMarginTop + 1, width, height - settings.tmMarginBottom - 2);
if (!this.isEnabled()) {
return;
}
if (model == null) {
return;
}
long min = (long) model.getMinValue();
long max = (long) model.getMaxValue();
/*
System.out.println("\nall min: " + min);
System.out.println("all max: " + max);
System.out.println("all date min: " + new DateTime(new Date(min)));
System.out.println("all date max: " + new DateTime(new Date(max)));
*/
if (max <= min
|| min == Double.NEGATIVE_INFINITY
|| max == Double.POSITIVE_INFINITY
|| max == Double.NEGATIVE_INFINITY
|| min == Double.POSITIVE_INFINITY) {
System.out.println("cannot show a model with negative values");
return;
}
if (model.getFromFloat() == Double.NEGATIVE_INFINITY
|| model.getToFloat() == Double.POSITIVE_INFINITY) {
System.out.println("cannot show a selection with negative values");
return;
}
/*
System.out.println("min: " + min);
System.out.println("max: " + max);
System.out.println("date min: " + new DateTime(new Date(min)));
System.out.println("date max: " + new DateTime(new Date(max)));
*/
g2d.setRenderingHints(settings.renderingHints);
// VISIBLE HOOK (THE LITTLE GREEN RECTANGLE ON EACH SIDE) WIDTH
int vhw = settings.selection.visibleHookWidth;
// SELECTED ZONE WIDTH, IN PIXELS
int sw = (int) st - (int) sf;
if (highlightedComponent != HighlightedComponent.NONE) {
g2d.setPaint(settings.selection.mouseOverPaint);
switch (highlightedComponent) {
case LEFT_HOOK:
g2d.fillRect(
(int) sf,
settings.tmMarginTop,
vhw,
height - settings.tmMarginBottom - 1);
g2d.setPaint(settings.selection.paint);
g2d.fillRect(
(int) sf + vhw,
settings.tmMarginTop,
sw - vhw,
height - settings.tmMarginBottom - 1);
break;
case CENTER_HOOK:
g2d.setPaint(settings.selection.paint);
g2d.fillRect(
(int) sf,
settings.tmMarginTop,
vhw,
height - settings.tmMarginBottom - 1);
g2d.setPaint(settings.selection.mouseOverPaint);
g2d.fillRect(
(int) sf + vhw,
settings.tmMarginTop,
sw - vhw * 2,
height - settings.tmMarginBottom - 1);
g2d.setPaint(settings.selection.paint);
g2d.fillRect(
(int) st - vhw,
settings.tmMarginTop,
vhw,
height - settings.tmMarginBottom - 1);
break;
case RIGHT_HOOK:
g2d.setPaint(settings.selection.paint);
g2d.fillRect(
(int) sf,
settings.tmMarginTop,
sw - vhw,
height - settings.tmMarginBottom - 1);
g2d.setPaint(settings.selection.mouseOverPaint);
g2d.fillRect(
(int) st - vhw,
settings.tmMarginTop,
vhw,
height - settings.tmMarginBottom - 1);
break;
}
} else {
g2d.setPaint(settings.selection.paint);
g2d.fillRect((int) sf, settings.tmMarginTop, sw, height - settings.tmMarginBottom - 1);
}
//DateTime dtFrom = new DateTime(1455, 1, 1, 1, 1, 1, 1);
//DateTime dtTo = new DateTime(1960, 2, 10, 1, 1, 1, 1);
if (model.getUnit() == DateTime.class) {
paintUpperRulerForInterval(g2d,
new DateTime(new Date(min)),
new DateTime(new Date(max)));
}
g2d.setColor(settings.defaultStrokeColor);
g2d.drawRect((int) sf, settings.tmMarginTop, sw, height - settings.tmMarginBottom - 1);
double v = model.getValueFromFloat(currentMousePositionX * (1.0 / width));
v += model.getMinValue();
if (v != Double.NEGATIVE_INFINITY && v != Double.POSITIVE_INFINITY) {
String str = "";
int strw = 0;
if (model.getUnit() == DateTime.class) {
DateTime d = new DateTime(new Date((long) v));
if (d != null) {
DateTimeFormatter fmt = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
str = fmt.withLocale(LOCALE).print(d);
strw = (int) (settings.tip.fontMetrics.getStringBounds(str, null)).getWidth() + 4;
}
} else {
str = new Double(v).toString();
strw = (int) (settings.tip.fontMetrics.getStringBounds(str, null)).getWidth() + 4;
}
int px = currentMousePositionX;
if (px + strw >= width) {
px = width - strw;
}
if (mouseInside) {
g2d.setPaint(settings.tip.backgroundColor);
g2d.fillRect(px, 1, strw, 18);
g2d.setPaint(settings.tip.fontColor);
g2d.drawRect(px, 1, strw, 18);
g2d.setColor(settings.tip.fontColor);
g2d.drawString(str, px + 4, 16);
}
}
}
private void paintUpperRulerForInterval(Graphics2D g2d, DateTime dtFrom, DateTime dtTo) {
g2d.setFont(settings.graduations.font);
g2d.setColor(settings.graduations.fontColor);
int leftMargin = settings.graduations.leftMargin;
int textTopPosition = settings.graduations.textTopPosition;
int width = getWidth();
int height = getHeight();
// TODO take these from the model
Interval interval = new Interval(dtFrom, dtTo);
Period p = interval.toPeriod(PeriodType.days());
// try to determine length if we had to show milliseconds
int n = p.getDays();
int unitSize = (int) (settings.graduations.fontMetrics.getStringBounds("wednesday ", null)).getWidth();
if (n < (width / unitSize)) {
//System.out.println("jour");
for (int i = 0; i < n; i++) {
g2d.drawString(dtFrom.plusDays(i).dayOfWeek().getAsText(LOCALE),
leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Hours.hoursBetween(dtFrom.plusDays(i), dtFrom.plusDays(i + 1)).getHours());
}
return;
}
unitSize = (int) (settings.graduations.fontMetrics.getStringBounds("wed ", null)).getWidth();
if (n < (width / unitSize)) {
//System.out.println("jou");
for (int i = 0; i < n; i++) {
g2d.drawString(dtFrom.plusDays(i).dayOfWeek().getAsShortText(LOCALE),
leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Hours.hoursBetween(dtFrom.plusDays(i), dtFrom.plusDays(i + 1)).getHours());
}
return;
}
p = interval.toPeriod(PeriodType.days());
n = p.getDays();
unitSize = (int) (settings.graduations.fontMetrics.getStringBounds("30", null)).getWidth();
if (n < (width / unitSize)) {
//System.out.println("j");
for (int i = 0; i < n; i++) {
g2d.drawString("" + (dtFrom.getDayOfMonth() + i),
leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Hours.hoursBetween(dtFrom.plusDays(i), dtFrom.plusDays(i + 1)).getHours());
}
return;
}
p = interval.toPeriod(PeriodType.months());
n = p.getMonths();
unitSize = (int) (settings.graduations.fontMetrics.getStringBounds("September ", null)).getWidth();
if (n < (width / unitSize)) {
//System.out.println("mois");
for (int i = 0; i < n; i++) {
g2d.drawString(dtFrom.plusMonths(i).monthOfYear().getAsText(LOCALE),
leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Days.daysBetween(dtFrom.plusMonths(i), dtFrom.plusMonths(i + 1)).getDays());
}
return;
}
unitSize = (int) (settings.graduations.fontMetrics.getStringBounds("dec ", null)).getWidth();
if (n < (width / unitSize)) {
//System.out.println("mo");
for (int i = 0; i < n; i++) {
g2d.drawString(dtFrom.plusMonths(i).monthOfYear().getAsShortText(LOCALE),
leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Days.daysBetween(dtFrom.plusMonths(i), dtFrom.plusMonths(i + 1)).getDays());
}
return;
}
unitSize = (int) (settings.graduations.fontMetrics.getStringBounds("29 ", null)).getWidth();
if (n < (width / unitSize)) {
//System.out.println("m");
for (int i = 0; i < n; i++) {
g2d.drawString("" + (dtFrom.getMonthOfYear() + i), leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Days.daysBetween(dtFrom.plusMonths(i), dtFrom.plusMonths(i + 1)).getDays());
}
return;
}
p = interval.toPeriod(PeriodType.years());
n = p.getYears();
unitSize = (int) (settings.graduations.fontMetrics.getStringBounds("1980 ", null)).getWidth();
if (n < (width / unitSize)) {
//System.out.println("year");
for (int i = 0; i < n; i++) {
g2d.drawString("" + (dtFrom.getYear() + i), leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Months.monthsBetween(dtFrom.plusYears(i), dtFrom.plusYears(i + 1)).getMonths());
}
return;
}
int group = 10;
n = p.getYears() / group;
if (n < (width / unitSize)) {
//System.out.println("10 years");
for (int i = 0; i < n; i++) {
g2d.drawString("" + (dtFrom.getYear() + i * group), leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Months.monthsBetween(dtFrom.plusYears(i * group), dtFrom.plusYears((i + 1) * group)).getMonths());
}
return;
}
group = 20;
n = p.getYears() / group;
if (n < (width / unitSize)) {
//System.out.println("20 years");
for (int i = 0; i < n; i++) {
g2d.drawString("" + (dtFrom.getYear() + i * group), leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Months.monthsBetween(dtFrom.plusYears(i * group), dtFrom.plusYears((i + 1) * group)).getMonths());
}
return;
}
group = 50;
n = p.getYears() / group;
if (n < (width / unitSize)) {
//System.out.println("50 years");
for (int i = 0; i < n; i++) {
g2d.drawString("" + (dtFrom.getYear() + i * group), leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Months.monthsBetween(dtFrom.plusYears(i * group), dtFrom.plusYears((i + 1) * group)).getMonths());
}
return;
}
group = 100;
n = p.getYears() / group;
if (n / 100 < (width / unitSize)) {
//System.out.println("100 years");
for (int i = 0; i < n; i++) {
g2d.drawString("" + (dtFrom.getYear() + i * group), leftMargin + 2 + i * (width / n),
textTopPosition);
g2d.drawLine(leftMargin + i * (width / n), 2, leftMargin + i * (width / n), height - settings.graduations.textBottomMargin);
paintSmallGraduations(g2d,
leftMargin + i * (width / n),
leftMargin + (i + 1) * (width / n),
Months.monthsBetween(dtFrom.plusYears(i * group), dtFrom.plusYears((i + 1) * group)).getMonths());
}
}
return;
}
private void paintSmallGraduations(Graphics2D g2d, int x, int y, int numOfGrads) {
double width = y - x;
int height = getHeight();
int topMargin = height - settings.graduations.fontSize - 2;
int bottomMargin = height - settings.graduations.textBottomMargin;
int unitSize = 3;
if (numOfGrads > (width / unitSize)) {
return;
}
for (double i = 1; i < numOfGrads; i++) {
double xi = x + i * (width / (double) numOfGrads);
g2d.drawLine((int) xi, topMargin, (int) xi, bottomMargin);
}
}
private boolean inRange(int x, int a, int b) {
return (a < x && x < b);
}
public void mouseClicked(MouseEvent e) {
latestMousePositionX = e.getX();
currentMousePositionX = latestMousePositionX;
//throw new UnsupportedOperationException("Not supported yet.");
/*
int w = getWidth();
// small feature: when the zone is too small, and if we double-click,
// we want to expand the timeline
if (e.getClickCount() == 2) {
if (w != 0) {
if (sf > 0.1 && st < 0.9) {
newfrom = 0;
newto = w;
repaint();
}
}
} else if (e.getClickCount() == 2) {
if (w != 0) {
newto = st * (1.0 / w);
repaint(); // so it will repaint all panels
}
}
*/
}
public void mousePressed(MouseEvent e) {
if (model == null) {
return;
}
int x = e.getX();
latestMousePositionX = x;
currentMousePositionX = latestMousePositionX;
int r = 16;//skin.getSelectionHookSideLength();
if (currentState == TimelineState.IDLE) {
if (inRange(x, (int) sf - 1, (int) sf + r + 1)) {
highlightedComponent = HighlightedComponent.LEFT_HOOK;
currentState = TimelineState.RESIZE_FROM;
} else if (inRange(x, (int) sf + r, (int) st - r)) {
highlightedComponent = HighlightedComponent.CENTER_HOOK;
currentState = TimelineState.MOVING;
} else if (inRange(x, (int) st - r - 1, (int) st + 1)) {
highlightedComponent = HighlightedComponent.RIGHT_HOOK;
currentState = TimelineState.RESIZE_TO;
}
}
}
public void mouseEntered(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet.");
if (currentState == TimelineState.IDLE) {
latestMousePositionX = e.getX();
currentMousePositionX = latestMousePositionX;
}
mouseInside = true;
}
public void mouseExited(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet.");
if (currentState == TimelineState.IDLE) {
highlightedComponent = HighlightedComponent.NONE;
latestMousePositionX = e.getX();
currentMousePositionX = latestMousePositionX;
}
mouseInside = false;
repaint();
}
public void timelineAnimatorChanged(ChangeEvent event) {
repaint();
}
public void mouseReleased(MouseEvent evt) {
latestMousePositionX = evt.getX();
currentMousePositionX = latestMousePositionX;
//highlightedComponent = HighlightedComponent.NONE;
currentState = TimelineState.IDLE;
this.getParent().repaint(); // so it will repaint upper and bottom panes
}
public void mouseMoved(MouseEvent evt) {
if (model == null) {
return;
}
//System.out.println("mouse moved");
currentMousePositionX = evt.getX();
int x = currentMousePositionX;
float w = getWidth();
int r = settings.selection.visibleHookWidth;
// SELECTED ZONE BEGIN POSITION, IN PIXELS
// int sf = (int) (model.getFromFloat() * (double) w);
// SELECTED ZONE END POSITION, IN PIXELS
//int st = (int) (model.getToFloat() * (double) w);
HighlightedComponent old = highlightedComponent;
Cursor newCursor = null;
int a = 0;//settings.selection.invisibleHookMargin;
if (inRange(x, (int) sf - 1, (int) sf + r + 1)) {
newCursor = CURSOR_LEFT_HOOK;
highlightedComponent = HighlightedComponent.LEFT_HOOK;
} else if (inRange(x, (int) sf + r, (int) st - r)) {
highlightedComponent = HighlightedComponent.CENTER_HOOK;
newCursor = CURSOR_CENTRAL_HOOK;
} else if (inRange(x, (int) st - r - 1, (int) st + 1)) {
highlightedComponent = HighlightedComponent.RIGHT_HOOK;
newCursor = CURSOR_RIGHT_HOOK;
} else {
highlightedComponent = HighlightedComponent.NONE;
newCursor = CURSOR_DEFAULT;
}
if (newCursor != getCursor()) {
setCursor(newCursor);
}
// only repaint if highlight has changed (save a lot of fps)
//if (highlightedComponent != old) {
// repaint();
//}
// now we always repaint, because of the tooltip
repaint();
}
public void mouseDragged(MouseEvent evt) {
if (model == null) {
return;
}
double w = getWidth();
currentMousePositionX = evt.getX();
if (currentMousePositionX > (int) w) {
currentMousePositionX = (int) w;
}
if (currentMousePositionX < 0) {
currentMousePositionX = 0;
}
int x = currentMousePositionX;
int r = settings.selection.visibleHookWidth;
// SELECTED ZONE BEGIN POSITION, IN PIXELS
// sf = (model.getFromFloat() * w);
// SELECTED ZONE END POSITION, IN PIXELS
//st = (model.getToFloat() * w);
if (currentState == TimelineState.IDLE) {
if (inRange(x, (int) sf - 1, (int) sf + r + 1)) {
highlightedComponent = HighlightedComponent.LEFT_HOOK;
currentState = TimelineState.RESIZE_FROM;
} else if (inRange(x, (int) sf + r, (int) st - r)) {
highlightedComponent = HighlightedComponent.CENTER_HOOK;
currentState = TimelineState.MOVING;
} else if (inRange(x, (int) st - r - 1, (int) st + 1)) {
highlightedComponent = HighlightedComponent.RIGHT_HOOK;
currentState = TimelineState.RESIZE_TO;
}
}
double delta = 0;
if (latestMousePositionX != null) {
delta = x - latestMousePositionX;
}
latestMousePositionX = x;
// minimal selection zone width (a security to not crush it!)
int s = settings.selection.minimalWidth;
switch (currentState) {
case RESIZE_FROM:
//problem: moving the left part will crush the security zone
if ((sf + delta) >= (st - s)) {
sf = st - s;
} else {
if (sf + delta <= 0) {
sf = 0;
} else {
sf += delta;
}
}
break;
case RESIZE_TO:
if ((st + delta) <= (sf + s)) {
st = sf + s;
} else {
if ((st + delta >= w)) {
st = w;
} else {
st += delta;
}
}
break;
case MOVING:
// collision on the left..
if ((sf + delta) < 0) {
st = (st - sf);
sf = 0;
// .. or the right
} else if ((st + delta) >= w) {
sf = w - (st - sf);
st = w;
} else {
sf += delta;
st += delta;
}
break;
}
if (w != 0) {
newfrom = sf * (1.0 / w);
newto = st * (1.0 / w);
}
this.repaint(); // so it will repaint all panels
}
}