/* * Copyright (C) 2009-2010 Shashank Tulsyan * * 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, see <http://www.gnu.org/licenses/>. * * * * Linking this library statically or * dynamically with other modules is making a combined work based on this library. * Thus, the terms and conditions of the GNU General Public License cover the whole combination. * * * As a special exception, the copyright holders of this library give you permission to * link this library with independent modules to produce an executable, regardless of * the license terms of these independent modules, and to copy and * distribute the resulting executable under terms of your choice, * provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. * An independent module is a module which is not derived from or based on this library. * If you modify this library, you may extend this exception to your version of the library, * but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. */ /* * todo : * implement orient -- * if(super.getOrientation()==JProgressBar.HORIZONTAL) */ package neembuu.swing; import neembuu.rangearray.ModificationType; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Area; import java.awt.geom.Rectangle2D; import java.text.NumberFormat; import javax.swing.JProgressBar; import javax.swing.ToolTipManager; import neembuu.rangearray.Range; import neembuu.rangearray.RangeArray; import neembuu.rangearray.RangeArrayListener; import neembuu.rangearray.RangeUtils; /** * * @author Shashank Tulsyan */ public class RangeArrayProgressBar extends JProgressBar implements MouseMotionListener, MouseListener, RangeArrayListener{ private RangeArray array; private volatile long instantaneousMousePosition = -1; private int darknessAlpha = 100; private RangeArray darkRegion = null; public RangeArrayProgressBar(RangeArray array) { this(array,true); } /** * For dynamic plotting/painting of the RangeArray * the RangeArray should be an instance of * {@link ListenableRangeArray} or {@link RangeArrayProgressBar#repaintAround(neembuu.common.RangeArrayElement) } invoked * as changes are made ( repaintAround is more efficient/faster than a simple repaint ) * @param array the RangeArray that has to be plotted/painted */ public RangeArrayProgressBar(final RangeArray array, boolean updateQuickly) { super(0,1); super.setValue(0);//we have overriden, so we cannot use this.setValue //don't want endless repaint() calls from calling setValue() within //paintComponent() method for(javax.swing.event.ChangeListener list : this.getChangeListeners()){ removeChangeListener(list); } array.addRangeArrayListener(this); this.array = array; this.addMouseMotionListener(this); this.addMouseListener(this); ToolTipManager.sharedInstance().registerComponent(this); } public RangeArrayProgressBar() { this(null); } //public RangeArrayProgressBar(int/*enum/boolean instead ?*/ orient , final RangeArray array){} /*public void setRangeArray(RangeArray array){ this.array.removeListener(this);//we don't want anymore updates if(array instanceof ListenableRangeArray ){ ((ListenableRangeArray)array).addListener(this); } this.array = array; }*/ protected final RangeArray getRangeArray(){ return array; } @Override protected void paintComponent(Graphics componentGraphics) { Graphics2D g2 = (Graphics2D) componentGraphics; super.setValue(0); super.paintComponent(g2); if(array == null)return; if(array.size()==0)return; Area progressedRegionArea = new Area(); Area currentMousePositionArea = defineClipArea(g2,progressedRegionArea); g2.setClip(progressedRegionArea); super.setValue(1); super.paintComponent(g2); if(currentMousePositionArea!=null){ g2.setColor(new Color( 0,0,0, darknessAlpha )); g2.fill(currentMousePositionArea); } super.setValue(0); } private Area defineClipArea(Graphics2D g2,Area progressedRegionArea){ Dimension size = getSize();//componentGraphics.getClipBounds().getSize(); Area currentMousePositionArea = null; //if(true)return; //set rescale constant double k; long fileSize ; if(array.size()==0){ progressedRegionArea.add(new Area(new Rectangle2D.Double(0,0,getWidth(),getHeight()))); return null; } if(array.getFileSize()==RangeArray.MAX_VALUE_SUPPORTED){ fileSize=array.get(array.size()-1).ending(); } else fileSize=array.getFileSize(); k = size.getHeight()*size.getWidth() / (double)fileSize; int x1=0,x2=0; //(scaling ensures that this in an integer ) double y1=0,y2=0; double txy; Range currentElement; boolean highlightAreaSet = false, setHighlightAreaInThisLoop = false; boolean customDarkRegionExists = darkRegion!=null ; Color veryFineRegionColor = new Color( Color.orange.getRed(),Color.orange.getGreen(),Color.orange.getBlue(),100 ); Color defaultColor = g2.getColor(); for(int arrayIndex = 0;arrayIndex<array.size();arrayIndex++){ currentElement = array.get(arrayIndex); long rangeArrayAbsolutePosition = instantaneousMousePosition; setHighlightAreaInThisLoop = false; if(!highlightAreaSet){ if(currentElement.starting()<=rangeArrayAbsolutePosition && rangeArrayAbsolutePosition<=currentElement.ending()){ if(currentMousePositionArea==null){ currentMousePositionArea = new Area(); setHighlightAreaInThisLoop = true; } } } if(customDarkRegionExists) if(darkRegion.contains(currentElement)) setHighlightAreaInThisLoop = true; txy = currentElement.starting()*k; x1 = (int) Math.ceil(txy / getHeight()); y1 = txy % getHeight(); txy = currentElement.ending()*k; x2 = (int) Math.ceil(txy / getHeight()); y2 = txy % getHeight(); //optimize painting by skipping regions if(x2 < g2.getClipBounds().x)continue; if(x1 > g2.getClipBounds().x + g2.getClipBounds().width )break;//all required regions added //if(arrayIndex < 20000 || arrayIndex > 22000)continue; // single strip // mark it and then continue; if(x1==x2){ if(array.size()>1000) if( ((double)RangeUtils.getSize(currentElement)/(double)fileSize) < 0.00001 ){ g2.setColor(veryFineRegionColor); g2.fill(new Rectangle2D.Double( x1, y1, 1, (y2-y1) )); g2.setColor(defaultColor); continue; }//0.001 % //progressedRegionArea.add(new Area(new Line2D.Double(x1, y1, x1, y2))); progressedRegionArea.add(new Area(new Rectangle2D.Double( x1, y1, 1, y2-y1 ))); if(setHighlightAreaInThisLoop){ currentMousePositionArea.add(new Area(new Rectangle2D.Double( x1, y1, 1, (y2-y1) ))); } highlightAreaSet = true; continue; } //first strip progressedRegionArea.add(new Area(new Rectangle2D.Double( x1, y1, 1, getHeight()-y1 ))); if(setHighlightAreaInThisLoop){ currentMousePositionArea.add(new Area(new Rectangle2D.Double( x1, y1, 1, (getHeight()-y1) ))); } if(x2>=x1+2){ //middle rectangle progressedRegionArea.add(new Area(new Rectangle2D.Double( x1+1, 0, x2-x1-1, getHeight() ))); if(setHighlightAreaInThisLoop ){ currentMousePositionArea.add(new Area(new Rectangle2D.Double( x1+1, 0, x2-x1-1, getHeight() ))); } } //last strip progressedRegionArea.add(new Area(new Rectangle2D.Double( x2, 0, 1, y2 ))); if(setHighlightAreaInThisLoop){ currentMousePositionArea.add(new Area(new Rectangle2D.Double( x2, 0, 1, y2 ))); highlightAreaSet = true; setHighlightAreaInThisLoop = false; } } return currentMousePositionArea; } @Override public final void setValue(int n) { throw new UnsupportedOperationException( "Please modify the RangeArray instead"); } @Override public String getToolTipText(MouseEvent event) { if(event==null)return null; long rangeArrayAbsolutePosition = getAbsolutePositionFor(event); Range rangeArrayElement = array.get(rangeArrayAbsolutePosition); if(rangeArrayElement==null){ return getStringForEmptyRangeArrayRegion(rangeArrayAbsolutePosition); } return getRangeArraElementToolTipText(rangeArrayElement,rangeArrayAbsolutePosition); } protected long getAbsolutePositionFor(MouseEvent event){ double txy = event.getX()*getHeight()+event.getY(); long rangeArrayAbsolutePosition = (long) Math.ceil( txy * array.getFileSize() / (getWidth()*getHeight()) );return rangeArrayAbsolutePosition; } /** * Override this to change the string that is displayed in * unprogressed areas * @param absolutePosition * @param fileSize * @return */ protected String getStringForEmptyRangeArrayRegion(long absolutePosition){ return makeStringForEmptyRangeArrayRegion(absolutePosition, array.getFileSize() ); } protected static final String makeStringForEmptyRangeArrayRegion(long absolutePosition, long fileSize){ StringBuilder sb = new StringBuilder(100); sb.append("<html><body><u>"); /*int numSpace = getNumberOfDigits(fileSize) - getNumberOfDigits(absolutePosition); for (int i = 0; i < numSpace; i++) { sb.append(" "); }*/ sb.append(NumberFormat.getInstance().format((double)absolutePosition)); sb.append("</u><p>"); sb.append(NumberFormat.getInstance().format((double)fileSize)); sb.append("</body></html>"); return sb.toString(); } /** * * @param absolutePosition * @param fileSize * @return */ public String getRangeArraElementToolTipText(Range rae,long absolutePosition){ return makeRangeArraElementToolTipText(rae, absolutePosition, array.getFileSize()); } private static final String makeRangeArraElementToolTipText(Range rae,long absolutePosition, long fileSize){ StringBuilder sb = new StringBuilder(100); sb.append("<html><body><u>"); /*int numSpace = getNumberOfDigits(fileSize) - getNumberOfDigits(absolutePosition); for (int i = 0; i < numSpace; i++) { sb.append(' '); }*/ sb.append(NumberFormat.getInstance().format((double)absolutePosition)); sb.append("</u><p>"); sb.append(NumberFormat.getInstance().format((double)fileSize)); sb.append("<p>{"); sb.append(NumberFormat.getInstance().format(rae.starting())); sb.append("-to->"); sb.append(NumberFormat.getInstance().format(rae.ending())); sb.append("}<p>{"); sb.append(NumberFormat.getInstance().format(RangeUtils.getSize(rae))); sb.append(" , "); sb.append( (float)(RangeUtils.getSize(rae)*100)/fileSize ); sb.append("% }<p>"); String x = null; if(x!=null){ sb.append(x); sb.append("<p>"); } sb.append("</body></html>"); return sb.toString(); } /*private final static int getNumberOfDigits(long num){ int ret = 0; for(;num>0;ret++)num/=10; return ret; }*/ /** * Use this to set the darkness of the darkened region. <p> * A region is darkened (or highlighted) when the mouse is over it or * such a request is made to this component. * @see #getDarknessAlpha() * @param darknessAlpha alpha of darkened/highlighted region */ public void setDarknessAlpha(int darknessAlpha) { this.darknessAlpha = darknessAlpha; } /** * Get the darkness of the darkened region. * The region is highlighted instead if the previous color was itself dark, * such that further darkening will not make any significant observable difference. * @see #setDarknessAlpha(int) * @return alpha of darkened/highlighted region */ public int getDarknessAlpha() { return darknessAlpha; } //++++++++++++ Mouse Motion Listener +++++++++ @Override public void mouseDragged(MouseEvent e) { handleMouseEvent(e); return; } @Override public void mouseMoved(MouseEvent e) { handleMouseEvent(e); return; } /** * This can be used by a JTable which tabulates the RangeArrayElements. * The RangeArrayElement which is selected in the JTable, can be * darkened (or highlighted) by setting the dark region. * @see #getActiveRegion() * @param darkRegion The range array specifying the region which has to be darkened (or highlighted) */ public void setActiveRegion(RangeArray darkRegion) { if(darkRegion.getFileSize()>array.getFileSize()) throw new IllegalArgumentException("Dark region extends beyond filesize of this range array."); this.darkRegion = darkRegion; } /** * @see #setActiveRegion(neembuu.common.RangeArray) * @return The range array specifying the region which has to be darkened (or highlighted) */ public RangeArray getActiveRegion() { return darkRegion; } public void removeAllCustomDarkRegions(){ darkRegion = null; } protected void handleMouseEvent(MouseEvent e){ long newPosition; Range newPositionElement; Range instantaneousMousePositionElement = array.get(instantaneousMousePosition); if(e.getID()==MouseEvent.MOUSE_EXITED){ newPosition=-1; newPositionElement = null; }else { newPositionElement = array.get((newPosition=getAbsolutePositionFor(e))); } if(instantaneousMousePositionElement!=newPositionElement){ Rectangle newRepaintRegion = getBoundsForRangeArrayElement(newPositionElement); Rectangle oldRepaintRegion = getBoundsForRangeArrayElement(instantaneousMousePositionElement); //if old region was darkened, it must be undarkened as soon as it is left //so we must paint both old and new region if(newRepaintRegion!=null)repaint(newRepaintRegion); if(oldRepaintRegion!=null)repaint(oldRepaintRegion); } instantaneousMousePosition = newPosition; } //------------ Mouse Motion Listener ---------- public void repaintAround(Range region){ Rectangle repaintRegion = getBoundsForRangeArrayElement(region); if(repaintRegion==null)return; repaint(repaintRegion); } public Rectangle getBoundsForRangeArrayElement(Range element){ if(element==null)return null; long fileSize = array.getFileSize(); // x*height + y offset //--------------- = -------- // width * height fileSize int x1 = (int)( ((double)element.starting()/(double)fileSize)*getWidth() ); if(x1>0)x1=x1-1; //not really requireds int x2 = (int)( ((double)element.ending()/(double)fileSize)*getWidth() ); x2=x2+1;//round off to larger integer return new Rectangle( x1, 0, x2-x1+1,//one is added as x1 is offsetted by 1 getHeight() ); } @Override public void rangeArrayModified( long modificationResultStart, long modificationResultEnd, Range elementOperated, ModificationType modificationType, boolean removed, long modCount) { rangeArrayModified(elementOperated); } public void rangeArrayModified(Range regionModified) { /*RangeArrayElement newElementCreatedAfterModification = array.get(modifyingElement.starting()); repaintAround(newElementCreatedAfterModification);*/ if(regionModified==null)return; repaintAround(regionModified); } //++++++++++++ Mouse Listener +++++++++++++ @Override public void mouseClicked(MouseEvent e) {repaint();} @Override public void mousePressed(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) { handleMouseEvent(e);} //----------- Mouse Listener ---------------- }