package at.ac.tuwien.dbai.pdfwrap.gui.elements;
import at.ac.tuwien.dbai.pdfwrap.gui.layer.StyledSegment;
import at.ac.tuwien.dbai.pdfwrap.model.document.Page;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
/**
* A class for the panel which later displays the PDF image and other contents.
*
* @author Timo Schleicher
*
*/
@SuppressWarnings("serial")
public class PDFPanel extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener, ComponentListener {
//The PDF converted into a BufferedImage and already scaled to fit window size
private BufferedImage img;
//A list containing all elements of the PDF analysis process
private List<StyledSegment> segList;
//Factors for scaling and visualizing the output of the analysis process on the window
private float pageResizeFactor;
//Factor for scaling the image to the window
private float imageResizeFactor;
//Current value of the zoom
private float zoomValue;
//Height of the image depending on the current factors
private int imgH;
//Determines whether the background image should be printed or not
private boolean printImg;
//The origin of the screen coordinate system
private int originX, originY;
//The previous coordinates for dragging of the whole screen content
private int previousY, previousX;
//The selection box rectangle
private Rectangle2D selectionBox;
//The attribute panel of the GUI that displays all currently selected segments
private SelectionPanel attributePanel;
//Factor for constant zooming
private final float zoomFactor = 0.065f;
//The underlying page
private Page page;
/**
* The constructor for the PDFPanel object.
*
* @param dim The preferred size of this Panel
* @param img The PDF as a BufferedImage
* @param segList A list with all the elements of the PDF analysis process
* @param attributePanel A panel for displaying the attributes of the selected segments
* @param page The underlying page
*/
public PDFPanel(Dimension dim, BufferedImage img, List<StyledSegment> segList, SelectionPanel attributePanel, Page page) {
this.setSize(dim);
this.setOpaque(false);
if (img == null) {
this.img = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
} else {
this.img = img;
}
if (segList == null) {
this.segList = new ArrayList<StyledSegment>();
} else {
this.segList = segList;
}
this.attributePanel = attributePanel;
this.page = page;
this.printImg = true;
//Set default zoom factor
this.zoomValue = 1.0f;
updateResizeScaleFactor();
//Adding all listeners
addMouseListener(this);
addMouseMotionListener(this);
addMouseWheelListener(this);
addComponentListener(this);
}
@Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
//Translate coordinate system in order to perform a mouse drag
g2.translate(originX, originY);
//Draw the image PDF
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
if (printImg) {
g2.drawImage(img, 0, 0, (int)((float)this.getWidth()*zoomValue), imgH, null);
}
for (StyledSegment styl : segList){
styl.paintSegments(g2);
}
if (selectionBox != null) {
g2.setColor(Color.BLACK);
g2.draw(selectionBox);
g2.setColor(new Color(0,0,255,63));
g2.fill(selectionBox);
}
super.paint(g);
}
/**
* Getter method for the underlying page
*
* @return The underlying page object.
*/
public Page getPage() {
return page;
}
/**
* Determines whether the background image is visible or not.
*
* @param isPrintable true if the background image should be printed, false otherwise
*/
public void setImgVisible(boolean isPrintable) {
this.printImg = isPrintable;
}
/**
* Method for updating the window scaling factors
*/
public void updateResizeScaleFactor() {
//Calculate the scaling factors
imageResizeFactor = (float)this.getWidth()/(float)img.getWidth();
pageResizeFactor = ((float)this.getWidth()*zoomValue);
imgH = (int)((float)img.getHeight()*imageResizeFactor*zoomValue);
//Scale every segment according to the previously calculated page factor
for (StyledSegment seg : segList) {
seg.updateLocalCoordinates(pageResizeFactor, page.getWidth(), page.getHeight());
}
setPreferredSize(new Dimension(1, imgH));
}
/**
* Fits everything according to either the current window width or the window height
*
* @param fitWidth true if you want to fit the PDF to the window width, false otherwise (fit to window height)
*/
public void fitWindow(boolean fitWidth) {
originX = 0;
originY = 0;
if (fitWidth) {
zoomValue = 1.0f;
} else {
zoomValue = (float)getHeight()/((float)img.getHeight()*imageResizeFactor);
}
updateResizeScaleFactor();
repaint();
}
//Methods of the MouseListener
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
JPopupMenu menu = new JPopupMenu();
JMenuItem merge = new JMenuItem("Merge Segments");
//Merging of two or more segments is done here
merge.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
//Get all currently selected segments
ArrayList<StyledSegment> selectedSegs = attributePanel.getSelectedSegments();
//Check whether at least two segments are selected
if (selectedSegs.size() < 2) {
JOptionPane.showMessageDialog(PDFPanel.this, "Please select at least two segments.");
return;
}
//Check whether the class of the selected objects match
for (StyledSegment seg : selectedSegs) {
if (!seg.isClassEqual(selectedSegs.get(0))) {
JOptionPane.showMessageDialog(PDFPanel.this, "Please only select items of the same type.");
return;
}
}
//Remove all selected segment from the current page
for (StyledSegment seg : selectedSegs) {
page.getItems().remove(seg.getSegment());
}
//Choose the first selected segment to merge it with all the remaining ones
StyledSegment growingSeg = selectedSegs.get(0);
//Remove all selected segments from the display list
segList.removeAll(selectedSegs);
//Merge each segment into the first one - means that the first one grows
for (int i = 1; i < selectedSegs.size(); i++) {
growingSeg.getSegment().mergeSegment(selectedSegs.get(i).getSegment());
}
//Position the merged segment on the screen by considering the current window size
growingSeg.updateLocalCoordinates(pageResizeFactor, page.getWidth(), page.getHeight());
//Add the new segment to the current page
page.getItems().add(growingSeg.getSegment());
//Add the new segment to the display list
segList.add(growingSeg);
//Update the attribute panel for getting the information about the new created segment instead of the old ones
attributePanel.updateSegmentVisibility();
repaint();
}
});
menu.add(merge);
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
@Override
public void mouseEntered(MouseEvent e) { }
@Override
public void mouseExited(MouseEvent e) { }
@Override
public void mousePressed(MouseEvent e) {
previousX = e.getX();
previousY = e.getY();
}
@Override
public void mouseReleased(MouseEvent e) {
//Go into selection section if left mouse button was released
if(SwingUtilities.isLeftMouseButton(e)) {
ArrayList<StyledSegment> selected = new ArrayList<StyledSegment>();
//Check which segments got selected
for (StyledSegment seg : segList) {
//Check whether the selection was caused by means of the selection box or by simple clicking
boolean intersect = (selectionBox == null) ? seg.intersects((e.getX()-originX), (e.getY()-originY)) :
seg.intersects(selectionBox);
if (intersect) {
seg.setSelected(true);
selected.add(seg);
} else {
seg.setSelected(false);
}
}
//Check whether the control key is pressed and if so add or remove segments
if (e.getModifiers() == KeyEvent.VK_ALT) {
for (StyledSegment s : attributePanel.getSelectedSegments()) {
if (selectionBox == null && selected.contains(s)) {
//Remove segment if it was previously selected
s.setSelected(false);
selected.remove(s);
} else if (!selected.contains(s)) {
//Add segment if its a new previously not selected segment
s.setSelected(true);
selected.add(s);
}
}
}
//Display all selected segments within the attribute panel
attributePanel.setSelectedElements(selected);
//Reset Selection Box
selectionBox = null;
repaint();
}
}
//Methods of the MouseMotionListener
@Override
public void mouseDragged(MouseEvent e) {
//Move the coordinate system with respect to the dragging direction
if (SwingUtilities.isMiddleMouseButton(e)) {
originX -= (previousX - e.getX());
originY -= (previousY - e.getY());
previousX = e.getX();
previousY = e.getY();
//Set the selection box for segment selection
} else if (SwingUtilities.isLeftMouseButton(e)) {
selectionBox = new Rectangle2D.Float();
selectionBox.setFrameFromDiagonal(e.getX()-originX, e.getY()-originY, previousX-originX, previousY-originY);
}
repaint();
}
@Override
public void mouseMoved(MouseEvent e) { }
//Methods of the ComponentListener
@Override
public void componentHidden(ComponentEvent e) { }
@Override
public void componentMoved(ComponentEvent e) { }
@Override
public void componentResized(ComponentEvent e) {
updateResizeScaleFactor();
repaint();
}
@Override
public void componentShown(ComponentEvent e) { }
//Methods of the MouseWheelListener
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
int rotation = e.getWheelRotation();
//Check mouse position before zoom
float beforeX = (float)e.getX()*zoomValue;
float beforeY = (float)e.getY()*zoomValue;
if (rotation < 0) {
//zoom in
zoomValue *= Math.abs(rotation)*(1.0f+zoomFactor);
} else {
//zoom out
zoomValue *= rotation*(1.0f-zoomFactor);
}
//Check mouse position after zoom
float afterx = (float)e.getX()*zoomValue;
float aftery = (float)e.getY()*zoomValue;
//Mouse sensitive shift
originX -= (afterx - beforeX);
originY -= (aftery - beforeY);
updateResizeScaleFactor();
repaint();
}
}