/* (C) 2000-2002, DIUF, http://www.unifr.ch/diuf * * 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 2 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, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package iiuf.xmillum; import iiuf.dom.DOMUtils; import iiuf.xmillum.displayable.Root; import java.awt.BorderLayout; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.Cursor; import java.awt.Point; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import javax.swing.Action; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.JPopupMenu; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.Scrollable; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import at.ac.tuwien.dbai.pdfwrap.gui.displayable.TextArea2; import at.ac.tuwien.dbai.pdfwrap.model.document.*; /** * BrowserPanel * * XMIllum browser panel. Based on XMIllum code. * * @author Tamir Hassan, hassan@dbai.tuwien.ac.at * @author DIUF, Fribourg, CH * @version GraphWrap Beta 1 */ public class BrowserPanel extends JScrollPane implements MouseListener, MouseMotionListener, AllFlagListener, DocumentChangeListener { // JPanel content; browserContentPanel content; BrowserContext context; GenericSegment selectionBBox; MouseEvent downPosition = null; boolean automaticRescale = true; public static final int SCALE_IMMEDIATE = 0; public static final int SCALE_SMART = 1; public static final double SMARTSCALE_FIT_WIDTH = 0.0d; public static final double SMARTSCALE_FIT_WINDOW = 1.0d; static RenderingHints hints; /** * Set rendering hints to speed up the rendering. */ static { hints = new RenderingHints(null); hints.put(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED); hints.put(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); } /** * Constructs a new browser panel given a context. * * @param c * The given browser context */ public BrowserPanel(BrowserContext c) { context = c; context.addDocumentChangeListener(this); context.browserPanel = this; /* * content = new JPanel() { public GenericSegment selectionBBox = null; * public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; * g2d.setRenderingHints(hints); // Clear background. Can be removed to * speed up the displaying. Rectangle r = g2d.getClipBounds(); * g2d.clearRect(r.x, r.y, r.width, r.height); * * if (selectionBBox != null) g2d.drawRect((int)selectionBBox.getX1(), * (int)selectionBBox.getY1(), (int)selectionBBox.getWidth(), * (int)selectionBBox.getHeight()); if (!disabled) { for (int i = 0; i < * layers.length; i++) { if (layers[i].isActive()) { * layers[i].getDisplayable().paint(g2d, getScale()); } } } } }; */ // content = new browserContentPanel(); // using true doesn't really do much (we've implemented our own // buffering anyway) other than slow everything else down! content = new browserContentPanel(false); setViewportView(content); addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { if (automaticRescale && scaleType == SCALE_SMART) { rescale(); } } }); content.setLayout(null); content.addMouseListener(this); content.addMouseMotionListener(this); requestFocus(); context.flagger.addAllFlagListener(this); } int scaleType = SCALE_IMMEDIATE; double scaleValue = 1.0; public void setScale(double scale) { setScale(SCALE_IMMEDIATE, scale); } public void setScale(int type, double scale) { scaleType = type; scaleValue = scale; rescale(); } /** * Enables or disables the automatic rescale. * * @param autoRescale * Automatic rescale true/false. */ public void setAutomaticRescale(boolean autoRescale) { automaticRescale = autoRescale; } /** * Overridden method. * * @return Always true */ public boolean isFocusTraversable() { return true; } /** * Overridden method. * * @return Always true */ public boolean requestDefaultFocus() { return true; } boolean disabled; List tools; void closeTools() { if (tools != null) { Iterator i = tools.iterator(); while (i.hasNext()) { Tool t = (Tool) i.next(); try { t.deactivateTool(); } catch (Error e) { e.printStackTrace(); } } tools = null; } } void openTools() { NodeList toolElements = context.getDocument().getTools(); if (toolElements != null) { tools = new LinkedList(); for (int i = 0; i < toolElements.getLength(); i++) { Element element = (Element) toolElements.item(i); Tool t = ToolFactory.getTool(element, context.getDocument() .getClassLoader()); if (t != null) { try { t.activateTool(context, element); } catch (Error e) { e.printStackTrace(); } tools.add(t); } } } } /** * Centers the view on a given Displayable. * * @param d * Displayable upon which to center the view. */ public void centerOnElement(Displayable d) { Rectangle view = getViewport().getViewRect(); Rectangle r = d.getBounds(getScale()); if (!view.contains(r)) { Dimension size = getViewport().getPreferredSize(); int x = view.x; int y = view.y; if (r.width < view.width) { // Element fits on screen if (r.x < view.x || r.x + r.width >= view.x + view.width) { x = r.x + (r.width - view.width) / 2; x = Math.min(Math.max(x, 0), size.width - view.width); } } else { // Element doesn't fit on screen if (r.x + r.width < view.x || r.x >= view.x + view.width) { x = r.x + (r.width - view.width) / 2; x = Math.min(Math.max(x, 0), size.width - view.width); } } if (r.height < view.height) { // Element fits on screen if (r.y < view.y || r.y + r.height >= view.y + view.height) { y = r.y + (r.height - view.height) / 2; y = Math.min(Math.max(y, 0), size.height - view.height); } } else { // Element doesn't fit on screen if (r.y + r.height < view.y || r.y >= view.y + view.height) { y = r.y + (r.height - view.height) / 2; y = Math.min(Math.max(y, 0), size.height - view.height); } } getViewport().setViewPosition(new Point(x, y)); } } /** * Show a popup menu. * * @param menu * Menu to pop up */ public void showPopup(JPopupMenu menu) { Point p = context.getMousePosition(); menu.show(content, p.x, p.y); } /** * Repaints a rectangular area on the browser panel. * * @param area * Rectangular area to repaint. */ public void repaintArea(Rectangle area) { getViewport().getView() .repaint(area.x, area.y, area.width, area.height); } // ////////////////scrollable methods // taken from // http://groups.google.com/group/comp.lang.java.gui/browse_thread/thread/aeccd24205adc949/523f3973093e263f?lnk=st&q=java+mouse+wheel+speed&rnum=1&hl=en#523f3973093e263f // however, they don't help // even with Scrollable interface implemented... // TODO: look into this further... public boolean getScrollableTracksViewportHeight() { return false; } public boolean getScrollableTracksViewportWidth() { return false; } public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return 30; // I don't know when this is called. } public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 30; // this is called. It means 30 pixel, i guess. } // MouseListener... // layer stuff commented out 26.04.06 as it was causing performance problems // with the graph... /** * Called whenever the mouse enters the region. We request the focus so the * user can move using the keyboard. * * @param event * Mouse event */ public void mouseEntered(MouseEvent event) { requestFocus(); } /** * Not used. * * @param event * Mouse event */ public void mouseExited(MouseEvent event) { } /** * Mouse clicked. Pass the event to the root displayable of every active * layer. * * @param event * Mouse event */ public void mouseClicked(MouseEvent event) { requestFocus(); // uncommented 12.11.06 context.setMousePosition(event); // this chunk uncommented 12.11.06 for (int i = layers.length - 1; i >= 0; i--) { if (layers[i].isActive()) { // if (layers[i].getDisplayable().mouseClicked(event, getScale())) return; if (layers[i].getDisplayable().mouseClicked(event, getScale())) { //System.out.println(); return; } } } } /** * Mouse pressed. Pass the event to the root displayable of every active * layers. * * @param event * Mouse event */ public void mousePressed(MouseEvent event) { requestFocus(); if (downPosition == null) downPosition = event; // context.setMousePosition(event); // we will try a different techinque instead :) for (int i = layers.length - 1; i >= 0; i--) { if (layers[i].isActive()) { // if (layers[i].getDisplayable().mousePressed(event, // getScale())) return; if (layers[i].getDisplayable().mousePressed(event, getScale())) { //System.out.println("mouse pressed on object..."); //System.out.println("i = " + i); //System.out.println("mouse button: " + event.getButton()); //System.out.println("source: " + event.getSource()); //System.out.println("iD: " + event.getID()); //System.out.println(event.) } } } } public void mouseReleased(MouseEvent event) { requestFocus(); Point p = context.getMousePosition(); Point distance = new Point(event.getX() - p.x, event.getY() - p.y); /* System.out.println("distance.x " + distance.x); System.out.println("distance.y " + distance.y); */ if (distance.x > 8 || distance.x < -8 || distance.y < -8 || distance.y > 8) { if (downPosition != null) { /* * context.setStatus("dragged from: " + (downPosition.getX() / * getScale()) + ", " + (downPosition.getY() / getScale()) + " to: " + * (event.getX() / getScale()) + ", " + (event.getY() / * getScale())); */ context.setMousePosition(downPosition); context.setDragDistance(distance); // 11.12.08 don't select when scrolling with middle mouse button if (event.getButton() == 1) context.setStatus("Selection made"); } } else { // just a press // todo... which button? context.setMousePosition(event); // 11.12.08 don't select when scrolling with middle mouse button if (event.getButton() == 1) context.setStatus("Node selected"); } downPosition = null; for (int i = layers.length - 1; i >= 0; i--) { if (layers[i].isActive()) { if (layers[i].getDisplayable().mouseReleased(event, getScale())) return; } } } // MouseMotionListener public void mouseMoved(MouseEvent event) { context.setMousePosition(event); // System.out.println("mouse moved"); // layer updating for mouseMotionListener disabled... /* * for (int i = layers.length-1; i >= 0; i--) { if * (layers[i].isActive()) { if * (layers[i].getDisplayable().mouseMoved(event, getScale())) return; } } */ } public void mouseDragged(MouseEvent event) { if (event.getModifiers() != 16) // second button = 8; first button = 16 { // the following is exactly as it was in the original method Point p = context.getMousePosition(); Point distance = new Point(event.getX()-p.x, event.getY()-p.y); context.setMousePosition(event); context.setDragDistance(distance); for (int i = layers.length-1; i >= 0; i--) { if (layers[i].isActive()) { if (layers[i].getDisplayable().mouseDragged(event, getScale())) return; } } } //if (event.getModifiers() == 16) // first button else { /* * context.setStatus("mouse dragged:" + " (" + event.getX() + "," + * event.getY() + ")" + " detected on "* + * event.getComponent().getClass().getName() + "\n"); * * context.setStatus("mousePosition: " + context.getMousePosition().x + ", " + * context.getMousePosition().y); */ Point p = context.getMousePosition(); // Point distance = new Point(event.getX()-p.x, event.getY()-p.y); // Point p2 = downPosition.getPoint(); Point p2 = event.getPoint(); float x1 = p.x; float y1 = p.y; // float x2 = x1 + distance.x; float y2 = x1 + distance.y; float x2 = p2.x; float y2 = p2.y; GenericSegment oldSelectionBBox = selectionBBox; selectionBBox = new GenericSegment(x1, x2, y1, y2); selectionBBox.correctNegativeDimensions(); content.setSelectionBBox(selectionBBox); // System.out.println("in mouse dragged " + selectionBBox); content.setDrawOnlySelectionBox(true); if (oldSelectionBBox != null) { // work out co-ordinates to repaint CompositeSegment containerBox = new CompositeSegment(); containerBox.getItems().add(oldSelectionBBox); containerBox.getItems().add(selectionBBox); containerBox.findBoundingBox(); containerBox.enlargeCoordinates(1.0f); content.repaint(containerBox.getBoundingRectangle()); } else { content.repaint(selectionBBox.getBoundingRectangle()); } } // content.setDrawOnlySelectionBox(false); /* * context.setStatus("distance is now: x " + (event.getX()-p.x) + " y " + * (event.getY()-p.y)); * * context.setMousePosition(event); context.setDragDistance(distance); */ // layer updating for mouseDragged disabled... /* * for (int i = layers.length-1; i >= 0; i--) { if * (layers[i].isActive()) { if * (layers[i].getDisplayable().mouseDragged(event, getScale())) return; * } } */ } // AllFlagListener public void setFlag(Element e, String name, String value) { for (int i = 0; i < layers.length; i++) { Displayable d = layers[i].getDisplayable() .getDisplayableForElement(e); if (d != null) { if (value != null) { centerOnElement(d); } repaint(); } } } public double getScale() { if (scaleType == SCALE_IMMEDIATE) { return scaleValue; } else { Rectangle size = new Rectangle(); for (int i = 0; i < layers.length; i++) { size = size.union(layers[i].getDisplayable().getBounds(1.0d)); } Rectangle vp = getViewportBorderBounds(); if (size.getWidth() == 0 || size.getHeight() == 0 || vp.getWidth() <= 0 || vp.getHeight() <= 0) { return 1.0d; } double wf = (double) getViewportBorderBounds().getWidth() / size.getWidth(); double hf = (double) getViewportBorderBounds().getHeight() / size.getHeight(); if (scaleValue == SMARTSCALE_FIT_WIDTH) { return wf; } else { return Math.min(wf, hf); } } } void rescale() { double scale = getScale(); disabled = true; Rectangle size = new Rectangle(); for (int i = 0; i < layers.length; i++) { size = size.union(layers[i].getDisplayable().getBounds(scale)); } content.setPreferredSize(size.getSize()); setViewportView(content); disabled = false; // repaint(); context.refresh(); } // DocumentChangeListener interface public void documentChanged(DocumentChangeEvent e) { switch (e.getType()) { case DocumentChangeEvent.SCALE_CHANGED: rescale(); break; case DocumentChangeEvent.LAYER_TOGGLED: { disabled = true; String layer = e.getLayer(); for (int i = 0; i < layers.length; i++) { if (layers[i].getName().equals(layer)) { layers[i].setActive(e.isActive()); } } disabled = false; repaint(); } break; case DocumentChangeEvent.REFRESH: repaint(); break; case DocumentChangeEvent.DOCUMENT_CHANGED: { Point pos = getViewport().getViewPosition(); closeTools(); String[] l = context.getDocument().getLayerNames(); LayerEntry[] ls = new LayerEntry[l.length]; for (int i = 0; i < l.length; i++) { Element browserSection = context.getDocument().getLayer(l[i]); DisplayableClass root = new Root(); root.initialize(context, browserSection); ls[i] = new LayerEntry(l[i], root .getDisplayable(browserSection)); } layers = ls; openTools(); rescale(); } break; } } /* public void highlightRegion(GenericSegment bBox) { for (int i = 0; i < layers.length; i ++) { if (layers[i].isActive()) { Displayable d = layers[i].getDisplayable(); Displayable[] c = d.childs; for (int j = 0; j < c.length; j ++) { if (c[j] instanceof TextArea2) { //foo } } } } } */ LayerEntry[] layers = new LayerEntry[0]; private class LayerEntry { String name; boolean active = true; Displayable display; public LayerEntry(String n, Displayable d) { name = n; display = d; } public String getName() { return name; } public boolean isActive() { return active; } public void setActive(boolean a) { active = a; } public Displayable getDisplayable() { return display; } } private class browserContentPanel extends JPanel { private BufferedImage theBuf; private GenericSegment selectionBBox = null; private boolean drawOnlySelectionBox; public browserContentPanel(boolean doubleBuffered) { super(doubleBuffered); BufferedImage theBuf; if (this.getWidth() > 0 && this.getHeight() > 0) { theBuf = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); } else { theBuf = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); } /* * Graphics2D g = theBuf.createGraphics(); this.paint(g); * g.dispose(); */ drawOnlySelectionBox = false; } public void setSize(Dimension d) { super.setSize(d); if (this.getWidth() > 0 && this.getHeight() > 0) { theBuf = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); theBuf.createGraphics(); } else { theBuf = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); theBuf.createGraphics(); } } public void resize(Dimension d) { super.resize(d); if (this.getWidth() > 0 && this.getHeight() > 0) { theBuf = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB); theBuf.createGraphics(); } else { theBuf = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); theBuf.createGraphics(); } } public void paint(Graphics g) { /* * Calendar c = new GregorianCalendar(); System.out.println("in * paint method " + c.getTime()); */ Graphics2D g2d = (Graphics2D) g; // System.out.println("drawOnlySelectionBox: " + // drawOnlySelectionBox); if (!drawOnlySelectionBox) { // System.out.println("drawing everything..."); g2d.setRenderingHints(hints); Graphics2D g2dbuf = (Graphics2D) theBuf.getGraphics(); g2dbuf.setRenderingHints(hints); g2dbuf.setClip(g2d.getClipBounds()); // Clear background. Can be removed to speed up the displaying. /* * Rectangle r = g2dbuf.getClipBounds(); g2dbuf.clearRect(r.x, * r.y, r.width, r.height); */ Rectangle r = g2dbuf.getClipBounds(); g2dbuf.setColor(Color.WHITE); g2dbuf.fillRect(r.x, r.y, r.width, r.height); if (!disabled) { for (int i = 0; i < layers.length; i++) { if (layers[i].isActive()) { layers[i].getDisplayable() .paint(g2dbuf, getScale()); } } } } g2d.drawImage(theBuf, null, 0, 0); paintSelectionBox(g2d); // drawOnlySelectionBox = false; } public void paintSelectionBox(Graphics g) { Graphics2D g2d = (Graphics2D) g; // System.out.println("selectionBBox: in bcp " + selectionBBox); if (selectionBBox != null) { // Rectangle rect2 = new Rectangle(100,100,200,200); // System.out.println("fofo"); g2d.setPaint(Color.gray); g2d.setStroke(new BasicStroke()); Rectangle theRect = new Rectangle((int) selectionBBox.getX1(), (int) selectionBBox.getY1(), (int) selectionBBox.getWidth(), (int) selectionBBox .getHeight()); // System.out.println(selectionBBox); // g2d.fill(theRect); g2d.draw(theRect); // g2d.fill(rect2); // g2d.draw(rect2); // repaint(); drawOnlySelectionBox = false; // theBuf = this. } } public GenericSegment getSelectionBBox() { return selectionBBox; } public void setSelectionBBox(GenericSegment selectionBBox) { this.selectionBBox = selectionBBox; } public void setDrawOnlySelectionBox(boolean drawOnlySelectionBox) { this.drawOnlySelectionBox = drawOnlySelectionBox; } public boolean isDrawOnlySelectionBox() { return drawOnlySelectionBox; } } }