/* * sulky-modules - several general-purpose modules. * Copyright (C) 2007-2015 Joern Huxhorn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* * Copyright 2007-2015 Joern Huxhorn * * 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 de.huxhorn.sulky.swing; import de.huxhorn.sulky.formatting.HumanReadable; import de.huxhorn.sulky.io.IOUtilities; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Insets; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.Transparency; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.image.BufferedImage; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.border.Border; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MemoryStatus extends JComponent { private static final long serialVersionUID = -7977658722158059284L; private static final int GRADIENT_PIXELS = 3; private final Logger logger = LoggerFactory.getLogger(MemoryStatus.class); private final Runtime runtime; private MemoryInfo memoryInfo; private boolean paused; private boolean usingTotal; private boolean usingBinaryUnits; private BufferedImage offscreenImage; private static final Color USED_COLOR = new Color(20, 255, 20); // 20 instead of 0 so brighter works properly private static final Color TOTAL_COLOR = new Color(255, 255, 20, 192); // 20 instead of 0 so brighter works properly public MemoryStatus() { runtime = Runtime.getRuntime(); paused = true; JLabel fontLabel = new JLabel("8,888.88 XXX"); setFont(fontLabel.getFont()); //setMemoryInfo(new MemoryInfo(runtime)); updateMemoryBar(); addMouseListener(new GcMouseListener()); if (logger.isDebugEnabled()) logger.debug("Font: {}", getFont()); //initUi(); Thread t = new Thread(new PollRunnable(), "MemoryStatus-Poller"); t.setDaemon(true); t.start(); } public boolean isUsingBinaryUnits() { return usingBinaryUnits; } public void setUsingBinaryUnits(boolean usingBinaryUnits) { this.usingBinaryUnits = usingBinaryUnits; } @Override public void setFont(Font font) { super.setFont(font); calculatePreferredSize(); } @Override public void setBorder(Border border) { super.setBorder(border); calculatePreferredSize(); } private void calculatePreferredSize() { JLabel label = new JLabel("8,888.88 XXX"); label.setFont(getFont()); label.setBorder(getBorder()); Dimension size = label.getPreferredSize(); size.height += 2 * GRADIENT_PIXELS; setPreferredSize(size); } public boolean isUsingTotal() { return usingTotal; } public void setUsingTotal(boolean usingTotal) { this.usingTotal = usingTotal; } public synchronized boolean isPaused() { return paused; } /** * Calls the UI delegate's paint method, if the UI delegate * is non-<code>null</code>. We pass the delegate a copy of the * <code>Graphics</code> object to protect the rest of the * paint code from irrevocable changes * (for example, <code>Graphics.translate</code>). * <p> * If you override this in a subclass you should not make permanent * changes to the passed in <code>Graphics</code>. For example, you * should not alter the clip <code>Rectangle</code> or modify the * transform. If you need to do these operations you may find it * easier to create a new <code>Graphics</code> from the passed in * <code>Graphics</code> and manipulate it. Further, if you do not * invoker super's implementation you must honor the opaque property, * that is * if this component is opaque, you must completely fill in the background * in a non-opaque color. If you do not honor the opaque property you * will likely see visual artifacts. * <p> * The passed in <code>Graphics</code> object might * have a transform other than the identify transform * installed on it. In this case, you might get * unexpected results if you cumulatively apply * another transform. * * @param g the <code>Graphics</code> object to protect * @see #paint * @see javax.swing.plaf.ComponentUI */ @Override protected void paintComponent(Graphics g) { Insets insets = getInsets(); Dimension size = getSize(); if (isOpaque()) { g.setColor(getBackground()); g.fillRect(0, 0, size.width, size.height); } Rectangle paintingBounds = new Rectangle(); paintingBounds.x = insets.left; paintingBounds.y = insets.top; paintingBounds.width = size.width - insets.left - insets.right; paintingBounds.height = size.height - insets.top - insets.bottom; if (offscreenImage == null || offscreenImage.getWidth() != paintingBounds.width || offscreenImage.getHeight() != paintingBounds.height) { if (offscreenImage != null) { offscreenImage.flush(); offscreenImage = null; } if (paintingBounds.width > 0 && paintingBounds.height > 0) { GraphicsConfiguration gc = getGraphicsConfiguration(); offscreenImage = gc .createCompatibleImage(paintingBounds.width, paintingBounds.height, Transparency.TRANSLUCENT); if (logger.isDebugEnabled()) logger.debug("Created offscreen-image..."); } } if (offscreenImage != null) { Graphics gr = offscreenImage.getGraphics(); paintMemoryStatus(gr, paintingBounds); gr.dispose(); g.drawImage(offscreenImage, paintingBounds.x, paintingBounds.y, null); } } private void paintMemoryStatus(Graphics g, Rectangle paintingBounds) { MemoryInfo info = this.memoryInfo; Graphics2D g2 = (Graphics2D) g; //g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setComposite(AlphaComposite.Clear); g2.fillRect(0, 0, paintingBounds.width, paintingBounds.height); g2.setComposite(AlphaComposite.SrcOver); if (info != null) { if (!usingTotal) { double usedFraction = (((double) info.getUsed() / (double) info.getMax())); double totalFraction = (((double) info.getTotal() / (double) info.getMax())); int usedWidth = (int) (paintingBounds.width * usedFraction + 0.5); int totalWidth = (int) (paintingBounds.width * totalFraction + 0.5); drawBar(g2, 0, usedWidth, paintingBounds.height, USED_COLOR); //g.setColor(new Color(255, 255, 0,192)); //g.fillRect(usedWidth, 0, totalWidth-usedWidth, paintingBounds.height); drawBar(g2, usedWidth, totalWidth, paintingBounds.height, TOTAL_COLOR); } else { double usedFraction = (((double) info.getUsed() / (double) info.getTotal())); int usedWidth = (int) (paintingBounds.width * usedFraction + 0.5); drawBar(g2, 0, usedWidth, paintingBounds.height, USED_COLOR); } // text { String text = HumanReadable.getHumanReadableSize(info.getUsed(), usingBinaryUnits, true) + "B"; FontRenderContext frc = g2.getFontRenderContext(); TextLayout tl = new TextLayout(text, getFont(), frc); Shape s = tl.getOutline(null); Rectangle textBounds = s.getBounds(); if (logger.isDebugEnabled()) logger.debug("textBounds: {}", textBounds); textBounds.x = (textBounds.x * -1) + (paintingBounds.width - textBounds.width) / 2; textBounds.y = (textBounds.y * -1) + (paintingBounds.height - textBounds.height) / 2; g.translate(textBounds.x, textBounds.y); if (logger.isDebugEnabled()) logger.debug("corrected textBounds: {}", textBounds); //FontMetrics fm = g.getFontMetrics(); //Rectangle2D lm = fm.getStringBounds(text, g); //int textX=(int) (paintingBounds.width-lm.getWidth()); //int textBase=halfHeight-fm.getHeight()/2; g.setColor(Color.WHITE); GraphicsUtilities.drawHighlight(g2, s, GRADIENT_PIXELS, 0.2f); g.setColor(Color.BLACK); //g.drawString(text,textX,textBase); g2.fill(s); } } } private void drawBar(Graphics2D g2, int startX, int endX, int height, Color c) { int halfHeight = height / 2; int gradientHeight = Math.min(GRADIENT_PIXELS, halfHeight); if (2 * gradientHeight < height) { g2.setColor(c); g2.fillRect(startX, gradientHeight, endX, height - 2 * gradientHeight); } GradientPaint p; Color brighter = c.brighter().brighter(); Color darker = c.darker().darker(); int colorAlpha = c.getAlpha(); if (colorAlpha < 255) { brighter = new Color(brighter.getRed(), brighter.getGreen(), brighter.getBlue(), colorAlpha); darker = new Color(darker.getRed(), darker.getGreen(), darker.getBlue(), colorAlpha); if (logger.isDebugEnabled()) logger.debug("Corrected alpha-values."); } if (logger.isDebugEnabled()) logger.debug("original: {}\nbrighter: {}\ndarker: {}", c, brighter, darker); p = new GradientPaint(0, 0, brighter, 0, gradientHeight, c); g2.setPaint(p); g2.fillRect(startX, 0, endX, gradientHeight); p = new GradientPaint(0, height - gradientHeight, c, 0, height, darker); g2.setPaint(p); g2.fillRect(startX, height - gradientHeight, endX, gradientHeight); } public synchronized void setPaused(boolean paused) { this.paused = paused; notifyAll(); } private static class MemoryInfo { private long total; private long used; private long max; MemoryInfo(Runtime runtime) { total = runtime.totalMemory(); used = total - runtime.freeMemory(); max = runtime.maxMemory(); } long getTotal() { return total; } long getUsed() { return used; } long getMax() { return max; } } private void updateMemoryBar() { this.memoryInfo = new MemoryInfo(runtime); // Tooltip setToolTipText("<html>Used memory: " + HumanReadable.getHumanReadableSize(memoryInfo.getUsed(), usingBinaryUnits, false) + "bytes<br>Total memory: " + HumanReadable.getHumanReadableSize(memoryInfo.getTotal(), usingBinaryUnits, false) + "bytes<br>Maximum memory: " + HumanReadable.getHumanReadableSize(memoryInfo.getMax(), usingBinaryUnits, false) + "bytes<br><br>Double-click to garbage-collect.</html>"); repaint(); } public void addNotify() { super.addNotify(); setPaused(false); } public void removeNotify() { super.removeNotify(); setPaused(true); } class PollRunnable implements Runnable { Runnable updateRunnable; private long frequency = 5000; PollRunnable() { updateRunnable = new UpdateRunnable(); } public void run() { for (;;) { synchronized (MemoryStatus.this) { while (isPaused()) { try { MemoryStatus.this.wait(); } catch (InterruptedException e) { if (logger.isDebugEnabled()) logger.debug("Interrupted...", e); IOUtilities.interruptIfNecessary(e); return; } } } EventQueue.invokeLater(updateRunnable); try { Thread.sleep(frequency); } catch (InterruptedException e) { if (logger.isDebugEnabled()) logger.debug("Interrupted...", e); IOUtilities.interruptIfNecessary(e); return; } } } } class UpdateRunnable implements Runnable { public void run() { updateMemoryBar(); } } //@edu.umd.cs.findbugs.annotations.SuppressWarnings(value="DM_GC",justification="") class GcMouseListener extends MouseAdapter { public void mouseClicked(MouseEvent evt) { if (evt.getClickCount() >= 2 && evt.getButton() == MouseEvent.BUTTON1) { // this is not a bug! - Performance - Explicit garbage collection; extremely dubious except in benchmarking code System.gc(); //NOSONAR if (logger.isInfoEnabled()) logger.info("Executed garbage-collection."); updateMemoryBar(); } } } }