/* GanttProject is an opensource project management tool. Copyright (C) 2011 GanttProject Team This program 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 3 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.sourceforge.ganttproject; import biz.ganttproject.core.chart.grid.Offset; import biz.ganttproject.core.option.FontOption; import biz.ganttproject.core.option.GPOptionGroup; import biz.ganttproject.core.option.IntegerOption; import biz.ganttproject.core.time.CalendarFactory; import biz.ganttproject.core.time.TimeDuration; import biz.ganttproject.core.time.TimeUnit; import biz.ganttproject.core.time.impl.GPTimeUnitStack; import net.sourceforge.ganttproject.chart.Chart; import net.sourceforge.ganttproject.chart.ChartModel; import net.sourceforge.ganttproject.chart.ChartModelBase; import net.sourceforge.ganttproject.chart.ChartRendererBase; import net.sourceforge.ganttproject.chart.ChartSelection; import net.sourceforge.ganttproject.chart.ChartSelectionListener; import net.sourceforge.ganttproject.chart.ChartUIConfiguration; import net.sourceforge.ganttproject.chart.TimelineChart; import net.sourceforge.ganttproject.chart.export.ChartDimensions; import net.sourceforge.ganttproject.chart.export.ChartImageBuilder; import net.sourceforge.ganttproject.chart.export.ChartImageVisitor; import net.sourceforge.ganttproject.chart.export.RenderedChartImage; import net.sourceforge.ganttproject.chart.mouse.MouseInteraction; import net.sourceforge.ganttproject.chart.mouse.ScrollViewInteraction; import net.sourceforge.ganttproject.chart.mouse.TimelineFacadeImpl; import net.sourceforge.ganttproject.gui.UIFacade; import net.sourceforge.ganttproject.gui.zoom.ZoomEvent; import net.sourceforge.ganttproject.gui.zoom.ZoomListener; import net.sourceforge.ganttproject.language.GanttLanguage; import net.sourceforge.ganttproject.task.Task; import net.sourceforge.ganttproject.task.TaskSelectionManager; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import javax.swing.*; import javax.swing.plaf.LayerUI; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.util.Date; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.Timer; import java.util.TimerTask; public class AbstractChartImplementation implements TimelineChart, ZoomListener { private final ChartModelBase myChartModel; private final IGanttProject myProject; private Set<ChartSelectionListener> mySelectionListeners = new LinkedHashSet<>(); private final ChartComponentBase myChartComponent; private MouseInteraction myActiveInteraction; private final UIFacade myUiFacade; private VScrollController myVScrollController; private final Timer myTimer = new Timer(); private Runnable myTimerTask = null; public class MouseHoverLayerUi extends LayerUI<ChartComponentBase> { private Point myHoverPoint; @Override public void installUI(JComponent c) { super.installUI(c); JLayer jlayer = (JLayer)c; jlayer.setLayerEventMask( AWTEvent.MOUSE_MOTION_EVENT_MASK ); } @Override protected void processMouseMotionEvent(MouseEvent e, JLayer l) { myHoverPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l); l.repaint(); } @Override public void paint(Graphics g, JComponent c) { Graphics2D g2 = (Graphics2D)g.create(); super.paint(g2, c); if (myHoverPoint == null) { return; } ChartModelBase chartModel = getChartModel(); if (chartModel.getBottomUnit() == GPTimeUnitStack.DAY) { return; } Offset offset = chartModel.getOffsetAt(myHoverPoint.x); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .4f)); Font chartFont = chartModel.getChartUIConfiguration().getChartFont(); g2.setFont(chartFont.deriveFont(0.9f * chartFont.getSize())); g2.setColor(Color.BLACK); int offsetMidPx = (offset.getStartPixels() + offset.getOffsetPixels()) / 2; int headerBottomPx = chartModel.getChartUIConfiguration().getHeaderHeight(); int pointerSize = (int)(chartModel.getChartUIConfiguration().getBaseFontSize() * 0.6f); int[] xPoints = new int[] {offsetMidPx - pointerSize/2, offsetMidPx, offsetMidPx + pointerSize/2}; int[] yPoints = new int[] {headerBottomPx + pointerSize, headerBottomPx, headerBottomPx + pointerSize}; g2.fillPolygon(xPoints, yPoints, 3); g2.drawString(GanttLanguage.getInstance().formatShortDate(CalendarFactory.createGanttCalendar(offset.getOffsetStart())), offsetMidPx, headerBottomPx + (int)(chartModel.getChartUIConfiguration().getBaseFontSize() * 1.4f)); } } public AbstractChartImplementation(IGanttProject project, UIFacade uiFacade, ChartModelBase chartModel, ChartComponentBase chartComponent) { assert chartModel != null; myUiFacade = uiFacade; myChartModel = chartModel; myProject = project; myChartComponent = chartComponent; uiFacade.getTaskSelectionManager().addSelectionListener(new TaskSelectionManager.Listener() { @Override public void userInputConsumerChanged(Object newConsumer) { fireSelectionChanged(); } @Override public void selectionChanged(List<Task> currentSelection) { fireSelectionChanged(); } }); myTimer.schedule(new TimerTask() { @Override public void run() { if (myTimerTask != null) { SwingUtilities.invokeLater(myTimerTask); myTimerTask = null; } } }, 1000, 1000); } @Override public void init(IGanttProject project, IntegerOption dpiOption, FontOption chartFontOption) { // Skip as we already have a project instance. } protected void setCursor(Cursor cursor) { myChartComponent.setCursor(cursor); } protected UIFacade getUIFacade() { return myUiFacade; } @Override public IGanttProject getProject() { return myProject; } @Override public void setVScrollController(TimelineChart.VScrollController vscrollController) { myVScrollController = vscrollController; } public void beginScrollViewInteraction(MouseEvent e) { TimelineFacadeImpl timelineFacade = new TimelineFacadeImpl(getChartModel(), myProject.getTaskManager()); timelineFacade.setVScrollController(myVScrollController); setActiveInteraction(new ScrollViewInteraction(e, timelineFacade)); } public MouseInteraction finishInteraction() { try { if (getActiveInteraction() != null) { getActiveInteraction().finish(); } return getActiveInteraction(); } finally { setActiveInteraction(null); } } protected void setActiveInteraction(MouseInteraction myActiveInteraction) { this.myActiveInteraction = myActiveInteraction; } public MouseInteraction getActiveInteraction() { return myActiveInteraction; } @Override public void zoomChanged(ZoomEvent e) { myChartComponent.invalidate(); myChartComponent.repaint(); } public void paintChart(Graphics g) { getChartModel().paint(g); } protected ChartModelBase getChartModel() { return myChartModel; } protected void scheduleTask(Runnable task) { myTimerTask = task; } protected ChartComponentBase getChartComponent() { return myChartComponent; } private Image getLogo() { return myUiFacade.getLogo(); } // /////////////////////////////////////////////////////////// // interface Chart @Override public void buildImage(GanttExportSettings settings, ChartImageVisitor imageVisitor) { ChartModelBase modelCopy = getChartModel().createCopy(); modelCopy.setBounds(myChartComponent.getSize()); if (settings.getStartDate() == null) { settings.setStartDate(modelCopy.getStartDate()); } if (settings.getEndDate() == null) { settings.setEndDate(modelCopy.getEndDate()); } if (settings.isCommandLineMode()) { myChartComponent.getTreeTable().getTable().getTableHeader().setVisible(true); myChartComponent.getTreeTable().doLayout(); myChartComponent.getTreeTable().getTable().setRowHeight(modelCopy.calculateRowHeight()); myChartComponent.getTreeTable().autoFitColumns(); } settings.setLogo(getLogo()); ChartImageBuilder builder = new ChartImageBuilder(settings, modelCopy, myChartComponent.getTreeTable()); builder.buildImage(imageVisitor); } @Override public RenderedImage getRenderedImage(GanttExportSettings settings) { class ChartImageVisitorImpl implements ChartImageVisitor { private RenderedChartImage myRenderedImage; private Graphics2D myGraphics; private BufferedImage myTreeImage; @Override public void acceptLogo(ChartDimensions d, Image logo) { if (d.getTreeWidth() <= 0) { return; } Graphics2D g = getGraphics(d); g.setBackground(Color.WHITE); g.clearRect(0, 0, d.getTreeWidth(), d.getLogoHeight()); // Hack: by adding 35, the left part of the logo becomes visible, // otherwise it gets chopped off g.drawImage(logo, 35, 0, null); } @Override public void acceptTable(ChartDimensions d, Component header, Component table) { if (d.getTreeWidth() <= 0) { return; } Graphics2D g = getGraphics(d); g.translate(0, d.getLogoHeight()); header.print(g); g.translate(0, d.getTableHeaderHeight()); table.print(g); } @Override public void acceptChart(ChartDimensions d, ChartModel model) { if (myTreeImage == null) { myTreeImage = new BufferedImage(1, d.getChartHeight() + d.getLogoHeight(), BufferedImage.TYPE_INT_RGB); } myRenderedImage = new RenderedChartImage(model, myTreeImage, d.getChartWidth(), d.getChartHeight() + d.getLogoHeight(), d.getLogoHeight()); } private Graphics2D getGraphics(ChartDimensions d) { if (myGraphics == null) { myTreeImage = new BufferedImage(d.getTreeWidth(), d.getChartHeight() + d.getLogoHeight(), BufferedImage.TYPE_INT_RGB); myGraphics = myTreeImage.createGraphics(); } return myGraphics; } } ChartImageVisitorImpl visitor = new ChartImageVisitorImpl(); buildImage(settings, visitor); return visitor.myRenderedImage; } @Override public Date getStartDate() { return getChartModel().getStartDate(); } @Override public void setStartDate(Date startDate) { startDate = getBottomTimeUnit().adjustLeft(startDate); getChartModel().setStartDate(startDate); } @Override public void scrollBy(TimeDuration duration) { setStartDate(getChartModel().getTaskManager().shift(getStartDate(), duration)); } @Override public void setStartOffset(int pixels) { getChartModel().setHorizontalOffset(pixels); } private TimeUnit getBottomTimeUnit() { return getChartModel().getBottomUnit(); } @Override public Date getEndDate() { return getChartModel().getEndDate(); } @Override public void setDimensions(int height, int width) { Dimension bounds = new Dimension(width, height); getChartModel().setBounds(bounds); } @Override public void setBottomUnit(TimeUnit bottomUnit) { getChartModel().setBottomTimeUnit(bottomUnit); } @Override public void setTopUnit(TimeUnit topUnit) { getChartModel().setTopTimeUnit(topUnit); } @Override public void setBottomUnitWidth(int width) { getChartModel().setBottomUnitWidth(width); } @Override public String getName() { return myChartComponent.getName(); } @Override public void reset() { myChartComponent.reset(); } @Override public GPOptionGroup[] getOptionGroups() { return getChartModel().getChartOptionGroups(); } @Override public Chart createCopy() { return new AbstractChartImplementation(myProject, myUiFacade, getChartModel().createCopy(), myChartComponent); } @Override public Object getAdapter(Class arg0) { return null; } @Override public ChartSelection getSelection() { throw new UnsupportedOperationException(); } @Override public IStatus canPaste(ChartSelection selection) { throw new UnsupportedOperationException(); } @Override public void paste(ChartSelection selection) { throw new UnsupportedOperationException(); } @Override public void addSelectionListener(ChartSelectionListener listener) { mySelectionListeners.add(listener); } @Override public void removeSelectionListener(ChartSelectionListener listener) { mySelectionListeners.remove(listener); } private void fireSelectionChanged() { for (ChartSelectionListener nextListener : mySelectionListeners) { nextListener.selectionChanged(); } } @Override public void addRenderer(ChartRendererBase renderer) { myChartModel.addRenderer(renderer); } @Override public void resetRenderers() { myChartModel.resetRenderers(); } @Override public ChartModel getModel() { return myChartModel; } @Override public ChartUIConfiguration getStyle() { return myChartModel.getChartUIConfiguration(); } private Integer myCachedHeaderHeight = 30; int getHeaderHeight(final JComponent tableContainer, final JComponent table) { return myCachedHeaderHeight; } public void setTimelineHeight(int height) { myCachedHeaderHeight = height; } public abstract static class ChartSelectionImpl implements ChartSelection { private boolean isTransactionRunning; @Override public abstract boolean isEmpty(); @Override public IStatus isDeletable() { return Status.OK_STATUS; } @Override public void startCopyClipboardTransaction() { if (isTransactionRunning) { throw new IllegalStateException("Transaction is already running"); } isTransactionRunning = true; } @Override public void startMoveClipboardTransaction() { if (isTransactionRunning) { throw new IllegalStateException("Transaction is already running"); } isTransactionRunning = true; } @Override public void cancelClipboardTransaction() { isTransactionRunning = false; } @Override public void commitClipboardTransaction() { isTransactionRunning = false; } } MouseHoverLayerUi createMouseHoverLayer() { return new MouseHoverLayerUi(); } }