/* * Copyright (c) 2004-2011 Marco Maccaferri and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Marco Maccaferri - initial API and implementation */ package org.eclipsetrader.ui.charts; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Status; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Cursor; import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.printing.Printer; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.ScrollBar; import org.eclipsetrader.core.feed.TimeSpan; import org.eclipsetrader.ui.internal.UIActivator; import org.eclipsetrader.ui.internal.charts.ChartObjectHitVisitor; public class BaseChartViewer implements ISelectionProvider { static final String K_NEEDS_REDRAW = "needs_redraw"; //$NON-NLS-1$ private Composite composite; private SashForm sashForm; private DateScaleCanvas dateScaleCanvas; private Label verticalScaleLabel; private IChartObject[][] input; private ChartCanvas[] chartCanvas = new ChartCanvas[0]; DateValuesAxis datesAxis; private ChartCanvas selectedChartCanvas; private IChartObject selectedObject; private ListenerList selectionListeners = new ListenerList(ListenerList.IDENTITY); private boolean showTooltips; private boolean showScaleTooltips; private SummaryBarDecorator summaryDecorator; private CrosshairDecorator decorator; private int decoratorMode; private ChartToolEditor activeEditor = new ChartToolEditor(); public BaseChartViewer(Composite parent, int style) { composite = new Composite(parent, style | SWT.H_SCROLL); GridLayout gridLayout = new GridLayout(2, false); gridLayout.marginWidth = gridLayout.marginHeight = 0; gridLayout.horizontalSpacing = gridLayout.verticalSpacing = 3; composite.setLayout(gridLayout); GC gc = new GC(composite); FontMetrics fontMetrics = gc.getFontMetrics(); gc.dispose(); sashForm = new SashForm(composite, SWT.VERTICAL | SWT.NO_FOCUS); sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); datesAxis = new DateValuesAxis(); datesAxis.additionalSpace = 15; dateScaleCanvas = new DateScaleCanvas(composite); dateScaleCanvas.getControl().setVisible(false); ((GridData) dateScaleCanvas.getControl().getLayoutData()).exclude = true; dateScaleCanvas.getControl().addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { updateScrollbars(); revalidate(); redraw(); } }); verticalScaleLabel = new Label(composite, SWT.NONE); verticalScaleLabel.setLayoutData(new GridData(Dialog.convertWidthInCharsToPixels(fontMetrics, 12), SWT.DEFAULT)); verticalScaleLabel.setVisible(false); ((GridData) verticalScaleLabel.getLayoutData()).exclude = true; Label label = new Label(sashForm, SWT.NONE); label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_LIST_BACKGROUND)); composite.getHorizontalBar().setVisible(false); composite.getHorizontalBar().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { revalidate(); redraw(); composite.update(); } }); composite.addControlListener(new ControlAdapter() { @Override public void controlResized(ControlEvent e) { redraw(); } }); summaryDecorator = new SummaryBarDecorator(); decorator = new CrosshairDecorator(); decorator.createSummaryLabel(sashForm); } public void dispose() { composite.dispose(); } public boolean isDisposed() { return composite.isDisposed(); } public Control getControl() { return composite; } public ChartCanvas[] getChildren() { return chartCanvas; } public Display getDisplay() { return composite.getDisplay(); } protected Point getLocation() { return new Point(composite.getHorizontalBar().getSelection(), 0); } void revalidate() { Rectangle clientArea = dateScaleCanvas.getCanvas().getClientArea(); ScrollBar hScroll = composite.getHorizontalBar(); Date firstDate = (Date) datesAxis.mapToValue(hScroll.getSelection()); Date lastDate = (Date) datesAxis.mapToValue(hScroll.getSelection() + clientArea.width); List<Date> l = new ArrayList<Date>(); Object[] values = datesAxis.getValues(); for (int i = 0; i < values.length; i++) { Date date = (Date) values[i]; if ((firstDate == null || !date.before(firstDate)) && (lastDate == null || !date.after(lastDate))) { l.add(date); } } Date[] visibleDates = l.toArray(new Date[l.size()]); dateScaleCanvas.setDatesAxis(datesAxis); dateScaleCanvas.setVisibleDates(visibleDates); dateScaleCanvas.setLocation(getLocation()); for (int i = 0; i < chartCanvas.length; i++) { if (chartCanvas[i] != null && !chartCanvas[i].isDisposed()) { chartCanvas[i].setDatesAxis(datesAxis); chartCanvas[i].setVisibleDates(visibleDates); chartCanvas[i].setDateRange(firstDate, lastDate); chartCanvas[i].setLocation(getLocation()); } } } public void print(Printer printer) { GC gc = new GC(printer); try { Rectangle printerBounds = printer.getClientArea(); Rectangle trimBounds = printer.computeTrim(printerBounds.x, printerBounds.y, printerBounds.width, printerBounds.height); printerBounds.x -= trimBounds.x; printerBounds.y -= trimBounds.y; printerBounds.width -= printerBounds.x; printerBounds.height -= printerBounds.y; System.out.println(printerBounds); System.out.println(printerBounds); double ratio = (double) printerBounds.width / (double) printerBounds.height; int y = printerBounds.y; for (int i = 0; i < chartCanvas.length; i++) { Image image = chartCanvas[i].getImage(); if (image != null) { Rectangle imageBounds = image.getBounds(); int destHeight = (int) (imageBounds.height * ratio); gc.drawImage(image, 0, 0, imageBounds.width, imageBounds.height, printerBounds.x, y, printerBounds.width, destHeight); y += destHeight; gc.drawLine(printerBounds.x, y, printerBounds.x + printerBounds.width, y); } } } catch (Exception e) { e.printStackTrace(); } } protected void updateScrollbars() { Rectangle clientArea = dateScaleCanvas.getCanvas().getClientArea(); if (clientArea.width == 0) { return; } ScrollBar hScroll = composite.getHorizontalBar(); boolean wasVisible = hScroll.getVisible(); Point chartSize = new Point(datesAxis.computeSize(clientArea.width), clientArea.height); for (int i = 0; i < 2; i++) { if (hScroll != null) { if (chartSize.x > clientArea.width) { hScroll.setVisible(true); } else { hScroll.setVisible(false); hScroll.setValues(0, 0, 1, 1, 1, 1); } } clientArea = dateScaleCanvas.getCanvas().getClientArea(); chartSize = new Point(datesAxis.computeSize(clientArea.width), clientArea.height); } if (hScroll.getVisible()) { int hiddenArea = chartSize.x - clientArea.width + 1; int currentSelection = hScroll.getSelection(); int rightAnchor = hScroll.getMaximum() - hScroll.getThumb(); int selection = Math.min(currentSelection, hiddenArea - 1); if (!wasVisible || currentSelection == rightAnchor) { selection = hiddenArea; } hScroll.setValues(selection, 0, chartSize.x, clientArea.width, 5, clientArea.width); } } public Object getInput() { return input; } public void setInput(IChartObject[][] input) { this.input = input; refresh(); } public void refresh() { ChartCanvas[] newCanvas = new ChartCanvas[input.length]; int length = Math.min(chartCanvas.length, newCanvas.length); System.arraycopy(chartCanvas, 0, newCanvas, 0, length); for (int i = length; i < chartCanvas.length; i++) { chartCanvas[i].getCanvas().setMenu(null); chartCanvas[i].dispose(); } if (chartCanvas.length == 0) { Control[] c = sashForm.getChildren(); if (c.length != 0) { c[0].dispose(); } } chartCanvas = newCanvas; selectedChartCanvas = null; datesAxis.clear(); for (int i = 0; i < input.length; i++) { for (int c = 0; c < input[i].length; c++) { input[i][c].accept(new IChartObjectVisitor() { @Override public boolean visit(IChartObject object) { if (object.getDataSeries() != null) { datesAxis.addValues(object.getDataSeries().getValues()); } return true; } }); } } for (int i = 0; i < input.length; i++) { if (chartCanvas[i] == null || chartCanvas[i].isDisposed()) { chartCanvas[i] = new ChartCanvas(sashForm); chartCanvas[i].getCanvas().setMenu(composite.getMenu()); chartCanvas[i].getCanvas().addMouseTrackListener(new MouseTrackAdapter() { @Override public void mouseExit(MouseEvent e) { ChartCanvas chartCanvas = (ChartCanvas) e.widget.getData(); if (activeEditor.isActive()) { return; } chartCanvas.hideToolTip(); dateScaleCanvas.hideToolTip(); } }); chartCanvas[i].getCanvas().addMouseMoveListener(new MouseMoveListener() { private ChartObjectHitVisitor visitor = new ChartObjectHitVisitor(); @Override public void mouseMove(MouseEvent e) { ChartCanvas chartCanvas = (ChartCanvas) e.widget.getData(); if (showTooltips && !decorator.isVisible()) { String s = null; if (visitor.getChartObject() != null) { s = visitor.getChartObject().getToolTip(); } if (s != null && !s.equals(chartCanvas.getCanvas().getToolTipText()) || s == null && chartCanvas.getCanvas().getToolTipText() != null) { chartCanvas.getCanvas().setToolTipText(s); } } if (showScaleTooltips) { chartCanvas.showToolTip(e.x, e.y); int x = e.x + composite.getHorizontalBar().getSelection(); Date value = (Date) datesAxis.mapToValue(x); if (value != null) { dateScaleCanvas.showToolTip(e.x, e.y, value); } } if (activeEditor != null && !activeEditor.isActive()) { visitor.setLocation(e.x, e.y); chartCanvas.accept(visitor); if (visitor.getChartObject() instanceof IEditableChartObject) { IEditableChartObject editableObject = (IEditableChartObject) visitor.getChartObject(); if (editableObject.isOnEditHandle(e.x, e.y)) { Cursor cursor = Display.getCurrent().getSystemCursor(SWT.CURSOR_CROSS); if (chartCanvas.getCanvas().getCursor() != cursor) { chartCanvas.getCanvas().setCursor(cursor); } } else if (editableObject.isOnDragHandle(e.x, e.y)) { Cursor cursor = Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND); if (chartCanvas.getCanvas().getCursor() != cursor) { chartCanvas.getCanvas().setCursor(cursor); } } } else { if (visitor.getChartObject() != null && chartCanvas.getCanvas().getCursor() == null) { chartCanvas.getCanvas().setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_HAND)); } else if (visitor.getChartObject() == null && chartCanvas.getCanvas().getCursor() != null) { chartCanvas.getCanvas().setCursor(null); } } } } }); chartCanvas[i].getCanvas().addMouseListener(new MouseAdapter() { private ChartObjectHitVisitor visitor = new ChartObjectHitVisitor(); @Override public void mouseDown(MouseEvent e) { ChartCanvas eventCanvas = (ChartCanvas) e.widget.getData(); visitor.setLocation(e.x, e.y); eventCanvas.accept(visitor); if (selectedObject != visitor.getChartObject()) { if (decorator.getMode() != CrosshairDecorator.MODE_MOUSE_HOVER) { decorator.deactivate(); } } else { if (selectedObject == null) { decorator.activate(); } } handleSelectionChanged(eventCanvas, visitor.getChartObject()); if (visitor.getChartObject() instanceof IEditableChartObject && e.button == 1) { eventCanvas.getCanvas().update(); decorator.setMode(CrosshairDecorator.MODE_OFF); IEditableChartObject object = (IEditableChartObject) visitor.getChartObject(); activeEditor.activate(BaseChartViewer.this, eventCanvas, object); activeEditor.handleMouseDown(activeEditor.createEvent(e)); } } }); summaryDecorator.decorateCanvas(chartCanvas[i]); decorator.decorateCanvas(chartCanvas[i]); } if (selectedChartCanvas == null) { selectedChartCanvas = chartCanvas[i]; } chartCanvas[i].setResolutionTimeSpan(getResolutionTimeSpan()); chartCanvas[i].setChartObject(input[i]); chartCanvas[i].redraw(); } int[] weights = new int[chartCanvas.length]; if (weights.length != 0) { weights[0] = 100; for (int i = 1; i < weights.length; i++) { weights[i] = 25; } sashForm.setWeights(weights); } sashForm.layout(); if (chartCanvas.length != 0) { dateScaleCanvas.getControl().setVisible(true); ((GridData) dateScaleCanvas.getControl().getLayoutData()).exclude = false; verticalScaleLabel.setVisible(true); ((GridData) verticalScaleLabel.getLayoutData()).exclude = false; } else { dateScaleCanvas.getControl().setVisible(false); ((GridData) dateScaleCanvas.getControl().getLayoutData()).exclude = true; verticalScaleLabel.setVisible(false); ((GridData) verticalScaleLabel.getLayoutData()).exclude = true; } composite.layout(); final Map<String, Object> set = new HashMap<String, Object>(); for (int i = 0; i < chartCanvas.length; i++) { chartCanvas[i].accept(new IChartObjectVisitor() { @Override public boolean visit(IChartObject object) { if (object == selectedObject) { set.put("selectedObject", object); //$NON-NLS-1$ } return true; } }); } handleSelectionChanged(selectedChartCanvas, (IChartObject) set.get("selectedObject")); //$NON-NLS-1$ updateScrollbars(); revalidate(); redraw(); } protected void handleSelectionChanged(ChartCanvas newChartCanvas, IChartObject newSelection) { if (selectedObject != newSelection) { ChartObjectFocusEvent event = new ChartObjectFocusEvent(selectedObject, newSelection); if (selectedObject != null) { selectedObject.handleFocusLost(event); } selectedObject = newSelection; selectedChartCanvas = newChartCanvas; if (selectedObject != null) { selectedObject.handleFocusGained(event); } fireSelectionChangedEvent(new SelectionChangedEvent(BaseChartViewer.this, getSelection())); for (int i = 0; i < chartCanvas.length; i++) { chartCanvas[i].redraw(); } } } public void redraw() { for (int i = 0; i < chartCanvas.length; i++) { if (chartCanvas[i] != null && !chartCanvas[i].isDisposed()) { chartCanvas[i].redraw(); } } dateScaleCanvas.redraw(); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ISelectionProvider#addSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionListeners.add(listener); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ISelectionProvider#removeSelectionChangedListener(org.eclipse.jface.viewers.ISelectionChangedListener) */ @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { selectionListeners.remove(listener); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ISelectionProvider#getSelection() */ @Override public ISelection getSelection() { return selectedObject != null ? new StructuredSelection(selectedObject) : StructuredSelection.EMPTY; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ISelectionProvider#setSelection(org.eclipse.jface.viewers.ISelection) */ @Override public void setSelection(ISelection newSelection) { if (!newSelection.isEmpty() && newSelection instanceof IStructuredSelection) { final Object element = ((IStructuredSelection) newSelection).getFirstElement(); if (element instanceof IChartObject) { for (int i = 0; i < chartCanvas.length; i++) { final ChartCanvas currentCanvas = chartCanvas[i]; ; currentCanvas.accept(new IChartObjectVisitor() { @Override public boolean visit(IChartObject object) { if (object == element) { handleSelectionChanged(currentCanvas, object); } return true; } }); } } } if (selectedObject != null && newSelection.isEmpty()) { handleSelectionChanged(selectedChartCanvas, null); } } protected void fireSelectionChangedEvent(SelectionChangedEvent event) { Object[] l = selectionListeners.getListeners(); for (int i = 0; i < l.length; i++) { try { ((ISelectionChangedListener) l[i]).selectionChanged(event); } catch (Throwable e) { Status status = new Status(IStatus.ERROR, UIActivator.PLUGIN_ID, Messages.BaseChartViewer_ExceptionErrorMessage, e); UIActivator.log(status); } } } public void setShowTooltips(boolean showTooltips) { this.showTooltips = showTooltips; } public void setCrosshairMode(int mode) { this.decoratorMode = mode; decorator.setMode(mode); } public void setShowScaleTooltips(boolean showScaleTooltips) { this.showScaleTooltips = showScaleTooltips; } public void activateEditor(IEditableChartObject object) { if (activeEditor.isActive()) { activeEditor.cancelEditing(); } if (selectedObject != null) { handleSelectionChanged(selectedChartCanvas, null); selectedChartCanvas.getCanvas().update(); } selectedChartCanvas.getCanvas().setCursor(Display.getCurrent().getSystemCursor(SWT.CURSOR_CROSS)); decorator.setMode(CrosshairDecorator.MODE_OFF); activeEditor.activate(this, selectedChartCanvas, object); } public void deactivateEditor() { if (activeEditor != null) { selectedChartCanvas.getCanvas().setCursor(null); decorator.setMode(decoratorMode); } } public ChartToolEditor getEditor() { return activeEditor; } public void setEditor(ChartToolEditor activeEditor) { if (this.activeEditor != null) { this.activeEditor.cancelEditing(); } this.activeEditor = activeEditor; } public ChartCanvas getSelectedChartCanvas() { return selectedChartCanvas; } public int getSelectedChartCanvasIndex() { for (int i = 0; i < chartCanvas.length; i++) { if (selectedChartCanvas == chartCanvas[i]) { return i; } } return -1; } /** * Returns the canvas at the given point in the receiver or null if * no such canvas exists. The point is in the coordinate system of * the receiver. * * @param x the x coordinate. * @param y the y coordinate. * @return the canvas at the given point, or null if no such canvas exists. */ public ChartCanvas getChartCanvas(int x, int y) { Point p = getControl().toDisplay(x, y); for (int i = 0; i < chartCanvas.length; i++) { Rectangle bounds = chartCanvas[i].getCanvas().getBounds(); if (bounds.contains(chartCanvas[i].getCanvas().toControl(p))) { return chartCanvas[i]; } } return null; } public void setDecoratorSummaryTooltips(boolean show) { decorator.setShowSummaryTooltip(show); } public int getZoomFactor() { return datesAxis.getZoomFactor(); } public void setZoomFactor(int zoomFactor) { datesAxis.setZoomFactor(zoomFactor); updateScrollbars(); revalidate(); redraw(); } public void setFillAvailableSpace(boolean fill) { datesAxis.fillAvailableSpace = fill; updateScrollbars(); revalidate(); redraw(); } public TimeSpan getResolutionTimeSpan() { return dateScaleCanvas.getResolutionTimeSpan(); } public void setResolutionTimeSpan(TimeSpan resolutionTimeSpan) { dateScaleCanvas.setResolutionTimeSpan(resolutionTimeSpan); } public int[] getWeights() { return sashForm.getWeights(); } public void setWeights(int[] weights) { sashForm.setWeights(weights); sashForm.layout(); } public void setSummaryVisible(boolean visible) { for (int i = 0; i < chartCanvas.length; i++) { if (chartCanvas[i] != null && !chartCanvas[i].isDisposed()) { chartCanvas[i].setSummaryVisible(visible); } } sashForm.layout(); } }