/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2008 A.Brochard
*
* 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; version 2
* of the License only.
*
* 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.pms.newgui;
import com.jgoodies.forms.builder.PanelBuilder;
import com.jgoodies.forms.factories.Borders;
import com.jgoodies.forms.layout.*;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
import net.pms.Messages;
import net.pms.PMS;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.util.BasicPlayer;
import net.pms.util.FormLayoutUtil;
import net.pms.util.StringUtil;
import net.pms.util.UMSUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StatusTab {
private static final Logger LOGGER = LoggerFactory.getLogger(StatusTab.class);
private static final Color memColor = new Color(119, 119, 119, 128);
private static final Color bufColor = new Color(75, 140, 181, 128);
public static class RendererItem implements ActionListener {
public ImagePanel icon;
public JLabel label;
public GuiUtil.MarqueeLabel playingLabel;
// public GuiUtil.ScrollLabel playingLabel;
public GuiUtil.FixedPanel playing;
public JLabel time;
public JFrame frame;
public GuiUtil.SmoothProgressBar rendererProgressBar;
public RendererPanel panel;
public String name = " ";
private JPanel _panel = null;
public RendererItem(RendererConfiguration r) {
icon = addRendererIcon(r.getRendererIcon());
icon.enableRollover();
label = new JLabel(r.getRendererName());
playingLabel = new GuiUtil.MarqueeLabel(" ");
// playingLabel = new GuiUtil.ScrollLabel(" ");
playingLabel.setForeground(Color.gray);
int h = (int) playingLabel.getSize().getHeight();
playing = new GuiUtil.FixedPanel(0, h);
playing.add(playingLabel);
time = new JLabel(" ");
time.setForeground(Color.gray);
rendererProgressBar = new GuiUtil.SmoothProgressBar(0, 100, new GuiUtil.SimpleProgressUI(Color.gray, Color.gray));
rendererProgressBar.setStringPainted(true);
rendererProgressBar.setBorderPainted(false);
rendererProgressBar.setString(r.getAddress().getHostAddress());
rendererProgressBar.setForeground(bufColor);
}
@Override
public void actionPerformed(final ActionEvent e) {
BasicPlayer.State state = ((BasicPlayer) e.getSource()).getState();
time.setText((state.playback == BasicPlayer.STOPPED || StringUtil.isZeroTime(state.position)) ? " " :
UMSUtils.playedDurationStr(state.position, state.duration));
rendererProgressBar.setValue((int) (100 * state.buffer / bufferSize));
String n = (state.playback == BasicPlayer.STOPPED || StringUtils.isBlank(state.name)) ? " " : state.name;
if (!name.equals(n)) {
name = n;
playingLabel.setText(name);
}
}
public void addTo(Container parent) {
parent.add(getPanel());
parent.validate();
// Maximize the playing label width
int w = _panel.getWidth() - _panel.getInsets().left - _panel.getInsets().right;
playing.setSize(w, (int) playingLabel.getSize().getHeight());
playingLabel.setMaxWidth(w);
}
public void delete() {
try {
// Delete the popup if open
if (frame != null) {
frame.dispose();
frame = null;
}
Container parent = _panel.getParent();
parent.remove(_panel);
parent.revalidate();
parent.repaint();
} catch (Exception e) {
}
}
public JPanel getPanel() {
if (_panel == null) {
PanelBuilder b = new PanelBuilder(new FormLayout(
"center:pref",
"max(140px;pref), 3dlu, pref, 2dlu, pref, 2dlu, pref, 2dlu, pref"
));
b.opaque(true);
CellConstraints cc = new CellConstraints();
b.add(icon, cc.xy(1, 1));
b.add(label, cc.xy(1, 3, CellConstraints.CENTER, CellConstraints.DEFAULT));
b.add(rendererProgressBar, cc.xy(1, 5));
b.add(playing, cc.xy(1, 7, CellConstraints.CENTER, CellConstraints.DEFAULT));
b.add(time, cc.xy(1, 9));
_panel = b.getPanel();
}
return _panel;
}
}
private ImagePanel imagePanel;
private JPanel renderers;
private JLabel jl;
private JProgressBar memoryProgressBar;
private GuiUtil.SegmentedProgressBarUI memBarUI;
private JLabel bitrateLabel;
private JLabel currentBitrate;
private JLabel currentBitrateLabel;
private JLabel peakBitrate;
private JLabel peakBitrateLabel;
private long rc = 0;
private long peak;
private static DecimalFormat formatter = new DecimalFormat("#,###");
private static int bufferSize;
StatusTab(PmsConfiguration configuration) {
bufferSize = configuration.getMaxMemoryBufferSize();
}
public void updateCurrentBitrate() {
long total = 0;
List<RendererConfiguration> foundRenderers = PMS.get().getFoundRenderers();
synchronized(foundRenderers) {
for (RendererConfiguration r : foundRenderers) {
total += r.getBuffer();
}
}
if (total == 0) {
currentBitrate.setText("0");
}
}
public JLabel getJl() {
return jl;
}
public ImagePanel getImagePanel() {
return imagePanel;
}
public JComponent build() {
// Apply the orientation for the locale
ComponentOrientation orientation = ComponentOrientation.getOrientation(PMS.getLocale());
String colSpec = FormLayoutUtil.getColSpec("pref, 30dlu, fill:pref:grow, 30dlu, pref", orientation);
// 1 2 3 4 5
FormLayout layout = new FormLayout(colSpec,
// 1 2 3 4 5
// //////////////////////////////////////////////////
"p," // Detected Media Renderers --------------------// 1
+ "9dlu," // //
+ "fill:p:grow," // <renderers> // 3
+ "3dlu," // //
+ "p," // ---------------------------------------------// 5
+ "3dlu," // | | //
+ "p," // Connected | Memory Usage |<bitrate> // 7
+ "3dlu," // | | //
+ "p," // <icon> | <statusbar> | // 9
//////////////////////////////////////////////////
);
PanelBuilder builder = new PanelBuilder(layout);
builder.border(Borders.DIALOG);
builder.opaque(true);
CellConstraints cc = new CellConstraints();
// Renderers
JComponent cmp = builder.addSeparator(Messages.getString("StatusTab.9"), FormLayoutUtil.flip(cc.xyw(1, 1, 5), colSpec, orientation));
cmp = (JComponent) cmp.getComponent(0);
Font bold = cmp.getFont().deriveFont(Font.BOLD);
Color fgColor = new Color(68, 68, 68);
cmp.setFont(bold);
renderers = new JPanel(new GuiUtil.WrapLayout(FlowLayout.CENTER, 20, 10));
JScrollPane rsp = new JScrollPane(
renderers,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
rsp.setBorder(BorderFactory.createEmptyBorder());
rsp.setPreferredSize(new Dimension(0, 260));
rsp.getHorizontalScrollBar().setLocation(0,250);
builder.add(rsp, cc.xyw(1, 3, 5));
cmp = builder.addSeparator(null, FormLayoutUtil.flip(cc.xyw(1, 5, 5), colSpec, orientation));
// Connected
jl = new JLabel(Messages.getString("StatusTab.3"));
builder.add(jl, FormLayoutUtil.flip(cc.xy(1, 7, "center, top"), colSpec, orientation));
jl.setFont(bold);
jl.setForeground(fgColor);
imagePanel = buildImagePanel("/resources/images/icon-status-connecting.png");
builder.add(imagePanel, FormLayoutUtil.flip(cc.xy(1, 9), colSpec, orientation));
// Memory
memBarUI = new GuiUtil.SegmentedProgressBarUI(Color.white, Color.gray);
memBarUI.setActiveLabel("{}", Color.white, 0);
memBarUI.setActiveLabel("{}", Color.red, 90);
memBarUI.addSegment("", memColor);
memBarUI.addSegment("", bufColor);
memBarUI.setTickMarks(getTickMarks(), "{}");
memoryProgressBar = new GuiUtil.CustomUIProgressBar(0, 100, memBarUI);
memoryProgressBar.setStringPainted(true);
memoryProgressBar.setForeground(new Color(75, 140, 181));
memoryProgressBar.setString(Messages.getString("StatusTab.5"));
JLabel mem = builder.addLabel("<html><b>" + Messages.getString("StatusTab.6") + "</b> (" + Messages.getString("StatusTab.12") + ")</html>", FormLayoutUtil.flip(cc.xy(3, 7), colSpec, orientation));
mem.setForeground(fgColor);
builder.add(memoryProgressBar, FormLayoutUtil.flip(cc.xyw(3, 9, 1), colSpec, orientation));
// Bitrate
String bitColSpec = "left:pref, 3dlu, right:pref:grow";
PanelBuilder bitrateBuilder = new PanelBuilder(new FormLayout(bitColSpec, "p, 1dlu, p, 1dlu, p"));
bitrateLabel = new JLabel("<html><b>" + Messages.getString("StatusTab.13") + "</b> (" + Messages.getString("StatusTab.11") + ")</html>");
bitrateLabel.setForeground(fgColor);
bitrateBuilder.add(bitrateLabel, FormLayoutUtil.flip(cc.xy(1, 1), bitColSpec, orientation));
currentBitrateLabel = new JLabel(Messages.getString("StatusTab.14"));
currentBitrateLabel.setForeground(fgColor);
bitrateBuilder.add(currentBitrateLabel, FormLayoutUtil.flip(cc.xy(1, 3), bitColSpec, orientation));
currentBitrate = new JLabel("0");
currentBitrate.setForeground(fgColor);
bitrateBuilder.add(currentBitrate, FormLayoutUtil.flip(cc.xy(3, 3), bitColSpec, orientation));
peakBitrateLabel = new JLabel(Messages.getString("StatusTab.15"));
peakBitrateLabel.setForeground(fgColor);
bitrateBuilder.add(peakBitrateLabel, FormLayoutUtil.flip(cc.xy(1, 5), bitColSpec, orientation));
peakBitrate = new JLabel("0");
peakBitrate.setForeground(fgColor);
bitrateBuilder.add(peakBitrate, FormLayoutUtil.flip(cc.xy(3, 5), bitColSpec, orientation));
builder.add(bitrateBuilder.getPanel(), FormLayoutUtil.flip(cc.xywh(5, 7, 1, 3, "left, top"), colSpec, orientation));
JPanel panel = builder.getPanel();
// Apply the orientation to the panel and all components in it
panel.applyComponentOrientation(orientation);
JScrollPane scrollPane = new JScrollPane(
panel,
JScrollPane.VERTICAL_SCROLLBAR_NEVER,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
startMemoryUpdater();
return scrollPane;
}
public void setReadValue(long v, String msg) {
if (v < rc) {
rc = v;
} else {
int sizeinMb = (int) ((v - rc) / 125) / 1024;
if (sizeinMb > peak) {
peak = sizeinMb;
}
currentBitrate.setText(formatter.format(sizeinMb));
peakBitrate.setText(formatter.format(peak));
rc = v;
}
}
public ImagePanel buildImagePanel(String url) {
BufferedImage bi = null;
if (url != null) {
try {
bi = ImageIO.read(LooksFrame.class.getResourceAsStream(url));
} catch (IOException e) {
LOGGER.debug("Caught exception", e);
}
}
return new ImagePanel(bi);
}
public void addRenderer(final RendererConfiguration renderer) {
final RendererItem r = new RendererItem(renderer);
r.addTo(renderers);
renderer.setGuiComponents(r);
r.icon.setAction(new AbstractAction() {
private static final long serialVersionUID = -6316055325551243347L;
@Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (r.frame == null) {
JFrame top = (JFrame) SwingUtilities.getWindowAncestor((Component) PMS.get().getFrame());
// We're using JFrame instead of JDialog here so as to have a minimize button. Since the player panel
// is intrinsically a freestanding module this approach seems valid to me but some frown on it: see
// http://stackoverflow.com/questions/9554636/the-use-of-multiple-jframes-good-bad-practice
r.frame = new JFrame();
r.panel = new RendererPanel(renderer);
r.frame.add(r.panel);
r.panel.update();
r.frame.setResizable(false);
r.frame.setIconImage(((JFrame) PMS.get().getFrame()).getIconImage());
r.frame.setLocationRelativeTo(top);
r.frame.setVisible(true);
} else {
r.frame.setVisible(true);
r.frame.toFront();
}
}
});
}
});
}
public static void updateRenderer(final RendererConfiguration renderer) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (renderer.gui != null) {
renderer.gui.icon.set(getRendererIcon(renderer.getRendererIcon()));
renderer.gui.label.setText(renderer.getRendererName());
// Update the popup panel if it's been opened
if (renderer.gui.panel != null) {
renderer.gui.panel.update();
}
}
}
});
}
public static ImagePanel addRendererIcon(String icon) {
BufferedImage bi = getRendererIcon(icon);
return bi != null ? new ImagePanel(bi) : null;
}
public static BufferedImage getRendererIcon(String icon) {
BufferedImage bi = null;
if (icon != null) {
if (icon.matches(".*\\S+://.*")) {
try {
bi = ImageIO.read(new URL(icon));
} catch (IOException e) {
LOGGER.debug("Error reading icon url: " + e);
}
if (bi != null) {
return bi;
} else {
LOGGER.debug("Unable to read icon url \"{}\", using \"{}\" instead.", icon, RendererConfiguration.UNKNOWN_ICON);
icon = RendererConfiguration.UNKNOWN_ICON;
}
}
try {
InputStream is = null;
/**
* Check for a custom icon file first
*
* The file can be a) the name of a file in the renderers directory b) a path relative
* to the PMS working directory or c) an absolute path. If no file is found,
* the built-in resource (if any) is used instead.
*
* The File constructor does the right thing for the relative and absolute path cases,
* so we only need to detect the bare filename case.
*
* RendererIcon = foo.png // e.g. $PMS/renderers/foo.png
* RendererIcon = images/foo.png // e.g. $PMS/images/foo.png
* RendererIcon = /path/to/foo.png
*/
File f = new File(icon);
if (!f.isAbsolute() && f.getParent() == null) { // filename
f = new File("renderers", icon);
}
if (f.isFile()) {
is = new FileInputStream(f);
}
if (is == null) {
is = LooksFrame.class.getResourceAsStream("/resources/images/clients/" + icon);
}
if (is == null) {
is = LooksFrame.class.getResourceAsStream("/renderers/" + icon);
}
if (is == null) {
LOGGER.debug("Unable to read icon \"{}\", using \"{}\" instead.", icon, RendererConfiguration.UNKNOWN_ICON);
is = LooksFrame.class.getResourceAsStream("/resources/images/clients/" + RendererConfiguration.UNKNOWN_ICON);
}
if (is != null) {
bi = ImageIO.read(is);
}
} catch (IOException e) {
LOGGER.debug("Caught exception", e);
}
}
if (bi == null) {
LOGGER.debug("Failed to load icon: " + icon);
}
return bi;
}
private int getTickMarks() {
int mb = (int) (Runtime.getRuntime().maxMemory() / 1048576);
return mb < 1000 ? 100 : mb < 2500 ? 250 : mb < 5000 ? 500 : 1000;
}
public void updateMemoryUsage() {
final long max = Runtime.getRuntime().maxMemory() / 1048576;
final long used = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1048576;
long buf = 0;
List<RendererConfiguration> foundRenderers = PMS.get().getFoundRenderers();
synchronized (foundRenderers) {
for (RendererConfiguration r : PMS.get().getFoundRenderers()) {
buf += (r.getBuffer());
}
}
final long buffer = buf;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
memBarUI.setValues(0, (int) max, (int) (used - buffer), (int) buffer);
}
});
}
private void startMemoryUpdater() {
Runnable r = new Runnable() {
@Override
public void run() {
for(;;) {
updateMemoryUsage();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
return;
}
}
}
};
new Thread(r).start();
}
}