/**
* Copyright 2007 Pieter-Jan Savat
*
* 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 org.skylion.mangareader.util;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.event.MouseInputListener;
import org.skylion.mangareader.mangaengine.MangaEngine;
/**
*
* The BookPanel uses the page turn effect to navigate between pages. The images used
* as pages are set by specifying their location, the basename and the extension.
* Each image should have the same basename followed by its index, ranging 1 to x.
* If a 0 index image exists at the location then it is used (even though it is not
* counted in the nrOfPages for the setPages() method).
*
* Navigating through the book can be done by clicking in the bottomright or
* bottomleft corner, by using the 4 arrow keys or by dragging and dropping
* a page.
* To turn pages programmatically either call nextPage() or previousPage(). Jumping
* to a page can be done by calling setLeftPageIndex().
*
* By default soft clipping is off and the borders at the edges of the papers are visible.
*
* TODO fade drop shadow on percentage turned (see passedThreshold for calculations)
*
* <p>Modifications made by secondary author to accommodate Manga's right to left reading style and
* to utilize the MangaEngine class. The prefetcher version is preferred.</p>
*
*
* @author Pieter-Jan Savat
* @author Aaron Gokaslan (Skylion)
*/
public class JBookPanel extends JComponent
implements MouseInputListener, ActionListener {
/**
*
*/
private static final long serialVersionUID = 1L;
protected static final double PI_DIV_2 = Math.PI / 2.0;
//protected enum AutomatedAction {AUTO_TURN, AUTO_DROP_GO_BACK, AUTO_DROP_BUT_TURN}
protected static final int AUTO_TURN=0;
protected static final int AUTO_DROP_GO_BACK=1;
protected static final int AUTO_DROP_BUT_TURN=2;
protected int rotationX;
protected double nextPageAngle;
protected double backPageAngle;
protected Timer timer;
//protected AutomatedAction action;
protected Integer action;
protected Point autoPoint;
protected Point tmpPoint;
protected int leftPageIndex;
protected Image currentLeftImage;
protected Image currentRightImage;
protected Image nextLeftImage;
protected Image nextRightImage;
protected Image previousLeftImage;
protected Image previousRightImage;
protected String pageLocation;
protected String pageName;
protected String pageExtension;
protected int nrOfPages;
protected boolean leftPageTurn;
protected int refreshSpeed;
// used to store the bounds of the book
protected Rectangle bookBounds;
protected int pageWidth;
protected int shadowWidth;
protected int cornerRegionSize;
protected boolean softClipping;
protected boolean borderLinesVisible;
// vars for optimization and reuse
protected Color shadowDarkColor;
protected Color shadowNeutralColor;
protected GeneralPath clipPath;
protected MangaEngine mangaEngine;
protected Image[] pages;
protected boolean proportionate = true;
protected boolean rightToLeft;
protected double hScale = .75, wScale = hScale;
/**
* Constructor
*/
public JBookPanel() {
super();
init();
setPages(null, null, null,
13, 210, 342);
setMargins(70, 80);
}
public JBookPanel(String[] URLs, MangaEngine mangaEngine, boolean doublePages,
boolean rightToLeft) throws IOException{
super();
init();
this.mangaEngine = mangaEngine;
this.setBookScale(.75, .75);
loadPages(URLs, doublePages, rightToLeft);
setMargins(70, 80);
}
public JBookPanel(Image[] images, boolean doublePages, boolean rightToLeft){
this();
setPages(null, null, null, 13, 210, 342);
pages = generatePages(images, doublePages, rightToLeft);
this.nrOfPages = pages.length - 1;
setMargins(70, 80);
refreshPages();
}
public JBookPanel(ImageIcon[] icons){
super();
init();
pages = new Image[icons.length];
for(int i = 0; i<pages.length; i++){
pages[i] = icons[i].getImage();
}
setPages(null, null, null,
13, 210, 342);
setMargins(70, 80);
}
//Loads the pages as such.
private void loadPages(String[] URLs, boolean doublePages, boolean rightToLeft) throws IOException{
pages = null;
this.rightToLeft = rightToLeft;
if(rightToLeft){
URLs = this.reverseArray(URLs);
}
Image[] images = new Image[URLs.length];
for(int i = 0; i<URLs.length; i++){
BufferedImage img;
try {
img = mangaEngine.getImage(URLs[i]);
} catch (Exception e) {
img = null;
// TODO Auto-generated catch block
e.printStackTrace();
}
if(img == null){
throw new IOException("Unable to load an image at index " + i + ".");
}
images[i] = img;
}
pages = generatePages(images, doublePages, false);//Manga would have already been reversed
pageWidth = getIdealPageWidth(images);//Sets PageWidth
scaleBook();
this.nrOfPages = pages.length+3;
if(rightToLeft){
leftPageIndex = pages.length-1;//Needs to be minus 7 for pages to load properly.
}
else{
leftPageIndex = 0;
}
refreshPages();
}
private int getIdealPageWidth(Image[] images){
int width = 0;
for(Image image: images){
BufferedImage img = (BufferedImage)image;
int iw = img.getWidth();
iw = iw * bookBounds.height / img.getHeight();
if(!isLandscape(img) && iw>width){
width = iw;
}
}
return width;
}
private Image[] generatePages(Image[] images, boolean doublePages, boolean rightToLeft){
List<Image> tmp = new ArrayList<Image>(images.length);
if(rightToLeft){
images = this.reverseArray(images);
}
for(int i = 0; i<images.length; i++){
BufferedImage img = (BufferedImage)images[i];
if(doublePages && this.isLandscape(img)){
boolean blankPageNeeded = (i>0 && i%2 == 0);
if(blankPageNeeded){
tmp.add(this.getBlankPage(img.getWidth(),img.getHeight()));
}
BufferedImage imgLeft =
img.getSubimage(0, 0, img.getWidth()/2, img.getHeight());
BufferedImage imgRight =
img.getSubimage(img.getWidth()/2,0,img.getWidth()/2,img.getHeight());
tmp.add(imgLeft);
tmp.add(imgRight);
}
else{
tmp.add(img);
}
}
tmp.removeAll(Collections.singleton(null));//Removes null entries
Image[] out = new Image[tmp.size()];
tmp.toArray(out);
return out;
}
/////////////////////////////////////
//Reverses the Array for Manga mode//
/////////////////////////////////////
private String[] reverseArray(String[] in){
String[] out = new String[in.length];
for(int i = 0; i<in.length; i++){
out[in.length-1-i] = in[i];
}
assert(out.length == in.length);
assert(out[0].equals(in[in.length-1]));
return out;
}
private Image[] reverseArray(Image[] in){
Image[] out = new Image[in.length];
for(int i = 0; i<in.length; i++){
out[in.length-1-i] = in[i];
}
assert(out.length == in.length);
assert(out[0].equals(in[in.length-1]));
return out;
}
///////////////////////////////////////////////////////
//Performs basic initiation common to all constructors/
///////////////////////////////////////////////////////
private void init(){
this.addMouseMotionListener(this);
this.addMouseListener(this);
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "nextPage");
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "nextPage");
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "nextPage");
this.getActionMap().put("nextPage", new AbstractAction() {
private static final long serialVersionUID = 9036593675552731266L;
public void actionPerformed(ActionEvent e) {
leftPageTurn = false;
nextPage();
}});
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "previousPage");
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "previousPage");
this.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "previousPage");
this.getActionMap().put("previousPage", new AbstractAction() {
private static final long serialVersionUID = 8254229203053136319L;
public void actionPerformed(ActionEvent e) {
previousPage();
}});
this.addComponentListener(new ComponentAdapter(){//Allows book to be rescaled.
public void componentResized(ComponentEvent ce){
scaleBook();
};
public void componentMoved(ComponentEvent ce){
scaleBook();
refreshPages();
};
});
refreshSpeed = 25;
shadowWidth = 100;
cornerRegionSize = 20;
bookBounds = new Rectangle();
softClipping = false;
borderLinesVisible = true;
clipPath = new GeneralPath();
shadowDarkColor = new Color(0,0,0,130);
shadowNeutralColor = new Color(255,255,255,0);
timer = new Timer(refreshSpeed, this);
timer.stop();
leftPageIndex = 0;
}
///////////////////
// PAINT METHODS //
///////////////////
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
setGraphicsHints(g2);
// background
g.setColor(this.getBackground());
g.fillRect(0, 0, this.getWidth(), this.getHeight());
// page 1
paintPage(g2, currentLeftImage, bookBounds.x, bookBounds.y, pageWidth, bookBounds.height, this, false);
// page 2
paintPage(g2, currentRightImage, bookBounds.x + pageWidth, bookBounds.y, pageWidth, bookBounds.height, this, true);
if (leftPageTurn) {
if (softClipping) {
paintLeftPageSoftClipped(g2);
} else {
paintLeftPage(g2);
}
} else {
if (softClipping) {
paintRightPageSoftClipped(g2);
} else {
paintRightPage(g2);
}
}
}
protected void paintLeftPage(Graphics2D g2) {
// translate to rotation point
g2.translate(bookBounds.width + bookBounds.x - rotationX + bookBounds.x,
bookBounds.y + bookBounds.height);
// rotate over back of page A angle
g2.rotate(-backPageAngle);
// translate the amount already done
int done = bookBounds.width + bookBounds.x - rotationX;
g2.translate(done - pageWidth, 0);
// page 3 (= back of page 1)
clipPath.reset();
clipPath.moveTo(pageWidth, 0);
clipPath.lineTo(pageWidth-done, 0);
clipPath.lineTo(pageWidth,
-1 * (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
Shape s = g2.getClip();
g2.clip(clipPath);
paintPage(g2, previousRightImage, 0, 0 - bookBounds.height, pageWidth, bookBounds.height, this, false);
g2.setClip(s);
// translate back
g2.translate(pageWidth - done, 0);
// rotate back
g2.rotate(backPageAngle);
// translate back
g2.translate(rotationX - bookBounds.width - bookBounds.x - bookBounds.x,
-bookBounds.y - bookBounds.height);
// page 4
clipPath.reset();
clipPath.moveTo(bookBounds.x,
bookBounds.height + bookBounds.y);
clipPath.lineTo(bookBounds.x + done,
bookBounds.height + bookBounds.y);
clipPath.lineTo(bookBounds.x,
bookBounds.height + bookBounds.y - (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
g2.clip(clipPath);
paintPage(g2, previousLeftImage, bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height, this, true);
// add drop shadow
GradientPaint grad = new GradientPaint(bookBounds.x + done,
bookBounds.height + bookBounds.y,
shadowDarkColor,
bookBounds.x + done - shadowWidth,
bookBounds.height + bookBounds.y + (int)(Math.cos(PI_DIV_2 - nextPageAngle) * shadowWidth),
shadowNeutralColor,
false);
g2.setPaint(grad);
g2.fillRect(bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height);
}
protected void paintLeftPageSoftClipped(Graphics2D g2) {
// calculate amount done (traveled)
int done = bookBounds.width + bookBounds.x - rotationX;
///////////////////////////////
// page 3 (= back of page 1) //
///////////////////////////////
// init clip path
clipPath.reset();
clipPath.moveTo(pageWidth, 0);
clipPath.lineTo(pageWidth-done, 0);
clipPath.lineTo(pageWidth,
-1 * (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
// init soft clip image
GraphicsConfiguration gc = g2.getDeviceConfiguration();
BufferedImage img = gc.createCompatibleImage(this.getWidth(), this.getHeight(), Transparency.TRANSLUCENT);
Graphics2D gImg = img.createGraphics();
// translate to rotation point
gImg.translate(bookBounds.width + bookBounds.x - rotationX + bookBounds.x,
bookBounds.y + bookBounds.height);
// rotate over back of page A angle
gImg.rotate(-backPageAngle);
// translate the amount already done
gImg.translate(done - pageWidth, 0);
// init area on which may be painted
gImg.setComposite(AlphaComposite.Clear);
gImg.fillRect(0, 0, this.getWidth(), this.getHeight());
gImg.setComposite(AlphaComposite.Src);
gImg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gImg.setColor(Color.WHITE);
gImg.fill(clipPath);
gImg.setColor(new Color(255,255,255,0));
gImg.fillRect(0, 0 - bookBounds.height - bookBounds.height,
pageWidth+10, bookBounds.height); // remove the top margin from allowed area
gImg.setComposite(AlphaComposite.SrcAtop);
// paint page
paintPage(gImg, previousRightImage, 0, 0 - bookBounds.height, pageWidth, bookBounds.height, this, false);
// translate back
gImg.translate(pageWidth - done, 0);
// rotate back
gImg.rotate(backPageAngle);
// translate back
gImg.translate(rotationX - bookBounds.width - bookBounds.x - bookBounds.x,
-bookBounds.y - bookBounds.height);
gImg.dispose();
g2.drawImage(img, 0, 0, null);
////////////
// page 4 //
////////////
// init clip path
clipPath.reset();
clipPath.moveTo(bookBounds.x,
bookBounds.height + bookBounds.y);
clipPath.lineTo(bookBounds.x + done,
bookBounds.height + bookBounds.y);
clipPath.lineTo(bookBounds.x,
bookBounds.height + bookBounds.y - (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
// init soft clip image
img = gc.createCompatibleImage(this.getWidth(), this.getHeight(), Transparency.TRANSLUCENT);
gImg = img.createGraphics();
gImg.setComposite(AlphaComposite.Clear);
gImg.fillRect(0, 0, this.getWidth(), this.getHeight());
gImg.setComposite(AlphaComposite.Src);
gImg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gImg.setColor(Color.WHITE);
gImg.fill(clipPath); // init area on which may be painted
gImg.setColor(new Color(255,255,255,0));
gImg.fillRect(0,0,this.getWidth(), bookBounds.y); // remove the top margin from allowed area
gImg.setComposite(AlphaComposite.SrcAtop);
// paint page
paintPage(gImg, previousLeftImage, bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height, this, true);
// paint shadow
GradientPaint grad = new GradientPaint(bookBounds.x + done,
bookBounds.height + bookBounds.y,
shadowDarkColor,
bookBounds.x + done - shadowWidth,
bookBounds.height + bookBounds.y + (int)(Math.cos(PI_DIV_2 - nextPageAngle) * shadowWidth),
shadowNeutralColor,
false);
gImg.setPaint(grad);
gImg.fillRect(bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height);
gImg.dispose();
g2.drawImage(img, 0, 0, null);
}
protected void paintRightPage(Graphics2D g2) {
// translate to rotation point
g2.translate(rotationX, bookBounds.y + bookBounds.height);
// rotate over back of page A angle
g2.rotate(backPageAngle);
// translate the amount already done
int done = bookBounds.width + bookBounds.x - rotationX;
g2.translate(-done, 0);
// page 3 (= back of page 1)
clipPath.reset();
clipPath.moveTo(0, 0);
clipPath.lineTo(done, 0);
clipPath.lineTo(0,
-1 * (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
Shape s = g2.getClip();
g2.clip(clipPath);
paintPage(g2, nextLeftImage, 0, 0 - bookBounds.height, pageWidth, bookBounds.height, this, false);
g2.setClip(s);
// translate back
g2.translate(done, 0);
// rotate back
g2.rotate(-backPageAngle);
// translate back
g2.translate(-rotationX, -bookBounds.y - bookBounds.height);
// page 4
clipPath.reset();
clipPath.moveTo(bookBounds.width + bookBounds.x,
bookBounds.height + bookBounds.y);
clipPath.lineTo(rotationX, bookBounds.height + bookBounds.y);
clipPath.lineTo(bookBounds.width + bookBounds.x,
bookBounds.height + bookBounds.y - (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
g2.clip(clipPath);
paintPage(g2, nextRightImage, pageWidth + bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height, this, true);
// add drop shadow
GradientPaint grad = new GradientPaint(rotationX,
bookBounds.height + bookBounds.y,
shadowDarkColor,
rotationX + shadowWidth,
bookBounds.height + bookBounds.y + (int)(Math.cos(PI_DIV_2 - nextPageAngle) * shadowWidth),
shadowNeutralColor,
false);
g2.setPaint(grad);
g2.fillRect(pageWidth + bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height);
}
protected void paintRightPageSoftClipped(Graphics2D g2) {
int done = bookBounds.width + bookBounds.x - rotationX;
///////////////////////////////
// page 3 (= back of page 1) //
///////////////////////////////
// init clip path
clipPath.reset();
clipPath.moveTo(0, 0);
clipPath.lineTo(done, 0);
clipPath.lineTo(0,
-1 * (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
// init soft clip image
GraphicsConfiguration gc = g2.getDeviceConfiguration();
BufferedImage img = gc.createCompatibleImage(this.getWidth(), this.getHeight(), Transparency.TRANSLUCENT);
Graphics2D gImg = img.createGraphics();
// translate to rotation point
gImg.translate(rotationX, bookBounds.y + bookBounds.height);
// rotate over back of page A angle
gImg.rotate(backPageAngle);
// translate the amount already done
gImg.translate(-done, 0);
// init area on which may be painted
gImg.setComposite(AlphaComposite.Clear);
gImg.fillRect(0, 0, this.getWidth(), this.getHeight());
gImg.setComposite(AlphaComposite.Src);
gImg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gImg.setColor(Color.WHITE);
gImg.fill(clipPath);
gImg.setColor(new Color(255,255,255,0));
gImg.fillRect(-10, 0 - bookBounds.height - bookBounds.height,
pageWidth+10, bookBounds.height); // remove the top margin from allowed area
gImg.setComposite(AlphaComposite.SrcAtop);
// paint page
paintPage(gImg, nextLeftImage, 0, 0 - bookBounds.height, pageWidth, bookBounds.height, this, false);
// translate back
gImg.translate(done, 0);
// rotate back
gImg.rotate(-backPageAngle);
// translate back
gImg.translate(-rotationX, -bookBounds.y - bookBounds.height);
gImg.dispose();
g2.drawImage(img, 0, 0, null);
////////////
// page 4 //
////////////
// init clip path
clipPath.reset();
clipPath.moveTo(bookBounds.width + bookBounds.x,
bookBounds.height + bookBounds.y);
clipPath.lineTo(rotationX, bookBounds.height + bookBounds.y);
clipPath.lineTo(bookBounds.width + bookBounds.x,
bookBounds.height + bookBounds.y - (int)(Math.tan(PI_DIV_2 - nextPageAngle) * done));
clipPath.closePath();
// init soft clip image
img = gc.createCompatibleImage(this.getWidth(), this.getHeight(), Transparency.TRANSLUCENT);
gImg = img.createGraphics();
gImg.setComposite(AlphaComposite.Clear);
gImg.fillRect(0, 0, this.getWidth(), this.getHeight());
gImg.setComposite(AlphaComposite.Src);
gImg.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gImg.setColor(Color.WHITE);
gImg.fill(clipPath); // init area on which may be painted
gImg.setColor(new Color(255,255,255,0));
gImg.fillRect(0,0,this.getWidth(), bookBounds.y); // remove the top margin from allowed area
gImg.setComposite(AlphaComposite.SrcAtop);
// paint page
paintPage(gImg, nextRightImage, pageWidth + bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height, this, true);
// add drop shadow
GradientPaint grad = new GradientPaint(rotationX,
bookBounds.height + bookBounds.y,
shadowDarkColor,
rotationX + shadowWidth,
bookBounds.height + bookBounds.y + (int)(Math.cos(PI_DIV_2 - nextPageAngle) * shadowWidth),
shadowNeutralColor,
false);
gImg.setPaint(grad);
gImg.fillRect(pageWidth + bookBounds.x, bookBounds.y,
pageWidth, bookBounds.height);
gImg.dispose();
g2.drawImage(img, 0, 0, null);
}
protected void paintPage(Graphics2D g, Image img, int x, int y, int w, int h, ImageObserver o,
boolean rightPage) {
if (img == null) {
Color oldColor = g.getColor();
g.setColor(this.getBackground());
g.fillRect(x, y, w+10, h+10);
g.setColor(oldColor);
return;
}
//TMP Solution
JPanel panel = new JPanel();
panel.setPreferredSize(new Dimension(w,h));
Color oldColor = g.getColor();
g.setColor(Color.WHITE);
g.fillRect(x, y, w, h);
g.setColor(oldColor);
if(proportionate){
int iw = img.getWidth(panel);
int ih = img.getHeight(panel);
//Keeps Image Proportioned
if (iw * h < ih * w) {
iw = (h * iw) / ih;
w = iw;
} else {
ih = (w * ih) / iw;
y += (h - ih) / 2;
h = ih;
}
boolean shouldShift = !rightPage;
if(leftPageTurn && img != currentRightImage && img != currentLeftImage){
shouldShift = !shouldShift;
//When the page is turned, the boolean needs to be inverted.
}
if(shouldShift){
x = x + pageWidth - w;//Centers the image in the middle of the Spine.
}
}
g.drawImage(img, x, y, w, h, o);
// stop if no borders are needed
if (!borderLinesVisible) {
return;
}
if (rightPage) {
g.setColor(Color.GRAY);
g.drawLine(x + w, y,
x + w, y + h);
g.drawLine(x, y,
x + w, y);
g.drawLine(x, y + h,
x + w, y + h);
g.setColor(Color.LIGHT_GRAY);
g.drawLine(x + w - 1, y + 1,
x + w - 1, y + h - 1);
g.drawLine(x, y + 1,
x + w - 1, y + 1);
g.drawLine(x, y + h - 1,
x + w - 1, y + h - 1);
g.drawLine(x, y + 2, x, y + h - 2);
g.setColor(oldColor);
} else {
g.setColor(Color.GRAY);
g.drawLine(x, y,
x, y + h);
g.drawLine(x, y,
x + w, y);
g.drawLine(x, y + h,
x + w, y + h);
g.setColor(Color.LIGHT_GRAY);
g.drawLine(x + 1, y + 1,
x + 1, y + h - 1);
g.drawLine(x + 1, y + 1,
x + w - 1, y + 1);
g.drawLine(x + 1, y + h - 1,
x + w - 1, y + h - 1);
g.drawLine(x + w - 1, y + 2, x + w - 1, y + h - 2);
g.setColor(oldColor);
}
}
protected void paintPageNumber(Graphics g, int index, int width, int height) {
g.setFont(new Font("Arial", Font.BOLD, 11));
int w = (int) g.getFontMetrics().getStringBounds(String.valueOf(index), g).getWidth();
int x = (index % 2 == 0)? width - 8 - w : 8;
int y = height - 10;
g.setColor(Color.GRAY);
g.drawString(String.valueOf(index), x, y);
}
///////////////////
// EVENT METHODS //
///////////////////
public void mouseEntered(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseClicked(MouseEvent e) {
if (isMouseInRegion(e)) {
nextPage();
}
}
public void mouseDragged(MouseEvent e) {
// if action busy then dont intervene
if (action != null) {
return;
}
// decide whether to grab left page or right page
if (this.rotationX == bookBounds.x + bookBounds.width) {
if (e.getPoint().x < bookBounds.x + pageWidth) {
this.leftPageTurn = true;
}
}
if (isMouseInBook(e)) {
if (this.getCursor().getType() != Cursor.HAND_CURSOR) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
calculate(e.getPoint());
}
}
public void mouseMoved(MouseEvent e) {
// if action busy then dont intervene
if (action != null) {
return;
}
if (isMouseInRegion(e)) {
int xOffset = (leftPageTurn)? 10 : -10;
Point p = new Point(e.getX() + xOffset, e.getY() - 10);
calculate(p);
} else if (rotationX != bookBounds.width + bookBounds.x) {
rotationX = bookBounds.width + bookBounds.x;
this.repaint();
}
}
public void mouseReleased(MouseEvent e) {
if (this.getCursor().getType() != Cursor.DEFAULT_CURSOR) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
// drop page back or forward depending on...
if (rotationX != (bookBounds.width + bookBounds.x)
&& rotationX != bookBounds.x) {
// base decision on amount of surface showing
if (passedThreshold()) {
System.out.println("drop turn at index " + leftPageIndex);
action = new Integer( AUTO_DROP_BUT_TURN );
autoPoint = (Point) e.getPoint().clone();
if (leftPageTurn) {
autoPoint.x = this.transformIndex(autoPoint.x);
}
tmpPoint = (Point) autoPoint.clone();
this.timer.restart();
} else {
System.out.println("drop go back");
action = new Integer( AUTO_DROP_GO_BACK );
autoPoint = (Point) e.getPoint().clone();
if (leftPageTurn) {
autoPoint.x = this.transformIndex(autoPoint.x);
}
tmpPoint = (Point) autoPoint.clone();
this.timer.restart();
}
}
}
public void actionPerformed(ActionEvent e) {
switch (action.intValue()) {
case AUTO_TURN:
// update autoPoint
double nextX = autoPoint.getX() - 15.0;
double x = nextX;
x = x - bookBounds.x - pageWidth;
double y = -1.0 * Math.pow(x / pageWidth, 2);
y += 1;
y *= 50;
if (leftPageTurn) {
nextX = this.transformIndex(nextX);
}
autoPoint.setLocation(nextX, bookBounds.y + bookBounds.height - y);
if (nextX <= bookBounds.x || nextX >= bookBounds.x + bookBounds.width) {
timer.stop();
action = null;
switchImages();
autoPoint.x = bookBounds.x;
calculate(autoPoint);
initRotationX();
this.repaint();
return;
}
// calculate using new point
calculate(autoPoint);
break;
case AUTO_DROP_GO_BACK:
int xDiff = bookBounds.width + bookBounds.x - autoPoint.x;
int stepsNeeded = 1 + (xDiff / 15);
if (stepsNeeded == 0) {
stepsNeeded = 1;
}
int yStep = (bookBounds.y + bookBounds.height - autoPoint.y) / stepsNeeded;
tmpPoint.x = tmpPoint.x + 15;
tmpPoint.y = tmpPoint.y + yStep;
if (tmpPoint.x >= bookBounds.width + bookBounds.x) {
timer.stop();
action = null;
}
Point newPoint = (Point) tmpPoint.clone();
if (leftPageTurn) {
newPoint.x = this.transformIndex(tmpPoint.x);
}
// calculate using new point
calculate(newPoint);
break;
case AUTO_DROP_BUT_TURN:
int xDiff2 = autoPoint.x - bookBounds.x;
int stepsNeeded2 = 1 + (xDiff2 / 15);
if (stepsNeeded2 == 0) {
stepsNeeded2 = 1;
}
int yStep2 = (bookBounds.y + bookBounds.height - autoPoint.y) / stepsNeeded2;
tmpPoint.x = tmpPoint.x - 15;
tmpPoint.y = tmpPoint.y + yStep2;
if (tmpPoint.x <= bookBounds.x) {
timer.stop();
action = null;
switchImages();
if (leftPageTurn) {
tmpPoint.x = this.transformIndex(bookBounds.x);
} else {
tmpPoint.x = bookBounds.x;
}
tmpPoint.y = bookBounds.y + bookBounds.height;
calculate(tmpPoint);
initRotationX();
this.repaint();
return;
}
Point newPoint2 = (Point) tmpPoint.clone();
if (leftPageTurn) {
newPoint2.x = this.transformIndex(tmpPoint.x);
}
// calculate using new point
calculate(newPoint2);
break;
}
}
////////////////////
// HELPER METHODS //
////////////////////
protected boolean isMouseInRegion(MouseEvent e) {
int x = e.getX();
int y = e.getY();
int maxX = bookBounds.x + bookBounds.width;
int minX = maxX - cornerRegionSize;
int maxY = bookBounds.y + bookBounds.height;
int minY = maxY - cornerRegionSize;
int minX2 = bookBounds.x;
int maxX2 = minX2 + cornerRegionSize;
leftPageTurn = ((x < maxX2) && (x > minX2));
return ( (y < maxY)
&& (y > minY)
&& ( ((x < maxX) && (x > minX))
|| ((x < maxX2) && (x > minX2))
) );
}
protected boolean isMouseInBook(MouseEvent e) {
return bookBounds.contains(e.getX(), e.getY());
}
protected double transformIndex(double x) {
return bookBounds.width + bookBounds.x - x + bookBounds.x;
}
protected int transformIndex(int x) {
return bookBounds.width + bookBounds.x - x + bookBounds.x;
}
protected void calculate(Point p) {
if (leftPageTurn) {
p.x = transformIndex(p.x);
}
// if no page to turn available then dont
// allow turn effect
if (currentRightImage == null && !leftPageTurn) {
rotationX = bookBounds.width + bookBounds.x;
nextPageAngle = 0;
backPageAngle = 0;
return;
} else if (currentLeftImage == null && leftPageTurn) {
rotationX = bookBounds.width + bookBounds.x;
nextPageAngle = 0;
backPageAngle = 0;
return;
}
Point cp = new Point(bookBounds.width + bookBounds.x, bookBounds.y + bookBounds.height);
double bRico = 1.0 * (cp.x - p.getX()) / (cp.y - p.getY());
bRico = -bRico;
Point mid = new Point(0,0);
mid.x = (int)((cp.x + p.getX()) / 2.0);
mid.y = (int)((cp.y + p.getY()) / 2.0);
double c = mid.y - bRico * mid.x;
rotationX = Math.max((int)((cp.y - c)/ bRico), pageWidth + bookBounds.x);
nextPageAngle = PI_DIV_2 - Math.abs(Math.atan(bRico));
backPageAngle = 2.0 * nextPageAngle;
this.repaint();
}
public void nextPage() {
action = new Integer( AUTO_TURN );
autoPoint = new Point(bookBounds.x + bookBounds.width,
bookBounds.y + bookBounds.height);
this.timer.restart();
}
public void previousPage() {
leftPageTurn = true;
nextPage();
}
protected void initRotationX() {
rotationX = bookBounds.width + bookBounds.x;
}
protected boolean passedThreshold() {
int done = bookBounds.width + bookBounds.x - rotationX;
double X = Math.min(Math.tan(PI_DIV_2 - nextPageAngle) * done, bookBounds.height);
X *= done * 2;
double threshold = bookBounds.height * pageWidth;
return X > threshold;
}
protected void switchImages() {
if (leftPageTurn) {
leftPageIndex -= 2;
currentLeftImage = previousLeftImage;
currentRightImage = previousRightImage;
} else {
leftPageIndex += 2;
currentLeftImage = nextLeftImage;
currentRightImage = nextRightImage;
}
System.out.println("NOW on page:" + leftPageIndex);
nextLeftImage = getPage(leftPageIndex + 2);
nextRightImage = getPage(leftPageIndex + 3);
previousLeftImage = getPage(leftPageIndex - 2);
previousRightImage = getPage(leftPageIndex - 1);
}
protected BufferedImage getBlankPage(int index) {
if(pageWidth<=0 || bookBounds.height <=0){
return null;
}
BufferedImage img = getBlankPage(pageWidth, bookBounds.height);
Graphics gfx = img.getGraphics();
paintPageNumber(gfx, index, img.getWidth(), img.getHeight());
return img;
}
protected BufferedImage getBlankPage(int w, int h){
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR);
Graphics gfx = img.getGraphics();
gfx.setColor(Color.WHITE);
gfx.fillRect(0, 0, img.getWidth(), img.getHeight());
return img;
}
protected void setGraphicsHints(Graphics2D g2) {
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
}
protected Image loadPage(int index) {
if(pages!=null && index>=0 ){
if(pages[index] == null){
return null;
}
else{
return pages[index];
}
}
return null;
}
protected Image getPage(int index) {
// if request goes beyond available pages return null
if (index > nrOfPages) {
// if back of existing page then return a blank
boolean isBack = ((index - 1) % 2 == 0);
isBack = rightToLeft? !isBack: isBack;
if (isBack) {
return getBlankPage(index);
} else {
return null;
}
}else if (index < 1) {
if (index == 0) {
try {
return loadPage(index);
} catch (Exception e) {
return getBlankPage(index);
}
} else if(index % 2 == -1){
return getBlankPage(index);
}
}else if(pages!=null){
if(index < pages.length){
return loadPage(index);
}
}else if (pageLocation == null
|| pageName == null
|| pageExtension == null ) {
// create the blank page
BufferedImage img = getBlankPage(index);// if no images specified return blank ones
// draw page number
Graphics gfx = img.getGraphics();
Graphics2D g2 = (Graphics2D) gfx;
setGraphicsHints(g2);
paintPageNumber(gfx, index, img.getWidth(), img.getHeight());
return img;
} else {
return new ImageIcon(getClass().getResource(pageLocation + pageName
+ index + "." + pageExtension)).getImage();
}
return null;
}
/////////////////////////
// GETTERS AND SETTERS //
/////////////////////////
public void setMargins(int top, int left) {
bookBounds.x = left;
bookBounds.y = top;
initRotationX();
}
public void setPages(int pageWidth, int pageHeight){
this.pageWidth = pageWidth;
this.bookBounds.height = pageHeight;
this.bookBounds.width = pageWidth*2;
}
public void setPages(int nrOfPages, int pageWidth, int pageHeight){
this.nrOfPages = nrOfPages;
setPages(pageWidth, pageHeight);
}
public void setPages(String pageLocation, String pageName, String pageExtension, int nrOfPages,
int pageWidth, int pageHeight) {
this.pageLocation = pageLocation;
this.pageName = pageName;
this.pageExtension = pageExtension;
if(nrOfPages!=-1){
this.nrOfPages = nrOfPages;
}
this.pageWidth = pageWidth;
bookBounds.width = 2 * pageWidth;
bookBounds.height = pageHeight;
initRotationX();
refreshPages();
centerBook();
}
public int getRefreshSpeed() {
return refreshSpeed;
}
public void setRefreshSpeed(int refreshSpeed) {
this.refreshSpeed = refreshSpeed;
}
public Color getShadowDarkColor() {
return shadowDarkColor;
}
public void setShadowDarkColor(Color shadowDarkColor) {
this.shadowDarkColor = shadowDarkColor;
}
public Color getShadowNeutralColor() {
return shadowNeutralColor;
}
public void setShadowNeutralColor(Color shadowNeutralColor) {
this.shadowNeutralColor = shadowNeutralColor;
}
public int getShadowWidth() {
return shadowWidth;
}
public void setShadowWidth(int shadowWidth) {
this.shadowWidth = shadowWidth;
}
public boolean isSoftClipping() {
return softClipping;
}
public void setSoftClipping(boolean softClipping) {
this.softClipping = softClipping;
}
public boolean isBorderLinesVisible() {
return borderLinesVisible;
}
public void setBorderLinesVisible(boolean borderLinesVisible) {
this.borderLinesVisible = borderLinesVisible;
}
public int getLeftPageIndex() {
return leftPageIndex;
}
public void setLeftPageIndex(int leftPageIndex) {
if (leftPageIndex <= -1) {
leftPageIndex = -1;
}
this.leftPageIndex = leftPageIndex;
refreshPages();
}
/**
* Set a scale factors to automatically rescale the book size relative to the component.
* Alternatively, set both values to -1 to disable this feature.
*
* <p>Example: setBookScale(.75, .75) will make the book occupy 3/4 of the components.
*
* @param hScale The factor you want to scale the height of the book to relative to the component
* @param wScale The factor you want to scale the width of the book to relative to the component
*/
public void setBookScale(double hScale, double wScale){
this.hScale = hScale;
this.wScale = wScale;
scaleBook();
}
public double getBookScaleHeight(){
return hScale;
}
public double getBookScaleWidth(){
return wScale;
}
// public void setRightToLeft(boolean rightToLeft){
// if(this.rightToLeft != rightToLeft){
// this.rightToLeft = rightToLeft;
//
// }
// }
//
// public boolean isRightToLeft(){
// return this.rightToLeft;
// }
public void setUseImageProprotions(boolean proportionate){
this.proportionate = proportionate;
}
public boolean usesImageProportions(){
return this.proportionate;
}
//////////////////////
//Formatting Methods//
//////////////////////
protected void centerBook(){
bookBounds.setRect(this.getWidth()/2-bookBounds.width/2,this.getHeight()/2
- bookBounds.height/2, bookBounds.width, bookBounds.height);
}
protected void scaleBook(){
if(!(hScale>0 && wScale>0)){
return;
}
int h = getHeight();
int w = getWidth();
if(w<=0 || h<=0){//Having this value be zero will cause problems
h = this.getPreferredSize().height;
w = this.getPreferredSize().width;
}
bookBounds.height = (int)(h*hScale+.5);
bookBounds.width = (int)(w*wScale+.5);
if(proportionate && pages!=null){
this.pageWidth = getIdealPageWidth(pages);
bookBounds.width = pageWidth*2;
}
else{
this.pageWidth = bookBounds.width/2;
}
centerBook();
}
protected boolean isLandscape(Image img){
BufferedImage bImg = (BufferedImage)img;
return bImg.getWidth()>bImg.getHeight();
}
protected void refreshPages(){
previousLeftImage = getPage(leftPageIndex - 2);
previousRightImage = getPage(leftPageIndex - 1);
currentLeftImage = getPage(leftPageIndex);
currentRightImage = getPage(leftPageIndex + 1);;
nextLeftImage = getPage(leftPageIndex + 2);
nextRightImage = getPage(leftPageIndex + 3);
}
}