/*
* Copyright 2008-2014 by Emeric Vernat
*
* This file is part of Java Melody.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bull.javamelody;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingWorker;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.bull.javamelody.swing.MButton;
import net.bull.javamelody.swing.MTransferableLabel;
import net.bull.javamelody.swing.Utilities;
import net.bull.javamelody.swing.util.MSwingUtilities;
/**
* Panel d'un graphique zoomé.
* @author Emeric Vernat
*/
class ChartPanel extends MelodyPanel {
private static final long serialVersionUID = 1L;
private static final int CHART_WIDTH = 960;
private static final int CHART_HEIGHT = 400;
private final String graphLabel;
private final String graphName;
private final MButton refreshButton;
private ImageIcon imageIcon;
private MTransferableLabel imageLabel;
private int zoomValue;
ChartPanel(RemoteCollector remoteCollector, String graphName, String graphLabel)
throws IOException {
this(remoteCollector, graphName, graphLabel, null);
}
ChartPanel(RemoteCollector remoteCollector, String graphName, String graphLabel,
MButton refreshButton) throws IOException {
super(remoteCollector);
this.graphName = graphName;
this.graphLabel = graphLabel;
if (refreshButton == null) {
this.refreshButton = createRefreshButton();
this.refreshButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
refresh();
} catch (final IOException ex) {
showException(ex);
}
}
});
} else {
this.refreshButton = refreshButton;
}
refresh();
}
final void refresh() throws IOException {
removeAll();
setName(graphLabel);
final JPanel southPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
southPanel.setOpaque(false);
zoomValue = 0;
final byte[] imageData = getRemoteCollector().collectJRobin(graphName, CHART_WIDTH,
CHART_HEIGHT);
if (imageData != null) {
this.imageIcon = new ImageIcon(imageData);
this.imageLabel = new MTransferableLabel(imageIcon);
// ce name sera utilisé comme nom de fichier pour le drag and drop de l'image
this.imageLabel.setName(graphLabel);
final JScrollPane scrollPane = new JScrollPane(imageLabel);
scrollPane
.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
add(scrollPane, BorderLayout.CENTER);
southPanel.add(createSlider());
} else {
this.imageIcon = null;
this.imageLabel = null;
}
southPanel.add(createButtonsPanel());
add(southPanel, BorderLayout.SOUTH);
}
private JSlider createSlider() {
final JSlider slider = new JSlider();
slider.setOpaque(false);
slider.setMinimum(10);
slider.setMaximum(200);
slider.setValue(100);
slider.setLabelTable(slider.createStandardLabels(50));
slider.setMajorTickSpacing(100);
slider.setMinorTickSpacing(10);
slider.setExtent(20);
// slider.setPaintLabels(true);
// slider.setPaintTicks(true);
// slider.setSnapToTicks(true);
slider.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
final int value = slider.getValue();
refreshZoom(value);
}
});
return slider;
}
private JPanel createButtonsPanel() {
if (getImageLabel() != null) {
final MButton exportButton = new MButton(getString("Exporter") + "...");
exportButton.setToolTipText(exportButton.getText() + " (F12)");
exportButton.setActionCommand("export");
exportButton.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke("F12"), "doExport");
exportButton.getActionMap().put("doExport", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
exportButton.doClick();
}
});
exportButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
export();
} catch (final IOException ex) {
showException(ex);
}
}
});
return Utilities.createButtonsPanel(refreshButton, exportButton);
}
// TODO ajouter boutons périodes ? ainsi que ci-dessus
return Utilities.createButtonsPanel(refreshButton);
}
final void refreshZoom(final int value) {
this.zoomValue = value;
final int width = Math.max(1, imageIcon.getIconWidth() * value / 100);
final int height = Math.max(1, imageIcon.getIconHeight() * value / 100);
final ImageIcon scaledImageIcon = MSwingUtilities.getScaledInstance(imageIcon, width,
height);
// setIcon appelle déjà revalidate() et repaint()
getImageLabel().setIcon(scaledImageIcon);
// SwingWorker pour recharger le graphique dans la bonne dimension en tâche de fond
final SwingWorker<byte[], Object> swingWorker = new SwingWorker<byte[], Object>() {
@Override
protected byte[] doInBackground() throws IOException, InterruptedException {
return collectJRobin(value, width, height);
}
@Override
protected void done() {
try {
final byte[] imageData = get();
// si la valeur de zoom a déjà rechangé, imageData peut être null
if (imageData != null) {
getImageLabel().setIcon(new ImageIcon(imageData));
}
} catch (final Exception e) {
showException(e);
}
}
};
swingWorker.execute();
}
final byte[] collectJRobin(int value, int width, int height) throws IOException,
InterruptedException {
// on attend 300 ms avant de voir si l'utilisateur se stabilise sur une valeur de zoom
// et sinon inutile de charger l'image avec cette valeur de zoom
Thread.sleep(300);
// if (zoomValue == value && imageLabel.getIcon().getIconWidth() != width
// && imageLabel.getIcon().getIconHeight() != height) {
if (zoomValue == value) {
zoomValue = -1;
return getRemoteCollector().collectJRobin(graphName, width, height);
}
return null;
}
final void export() throws IOException {
getImageLabel().export();
}
final MTransferableLabel getImageLabel() {
return imageLabel;
}
}